From 82e5f6de48e246a49b25e7d4ea7be65122e8772c Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 14 Feb 2026 23:42:01 +0100 Subject: [PATCH] feat: add Kubernetes operator and instance settings YAML editor (#7836) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add windmill-operator crate for Kubernetes CRD-based instance config Introduces a new `windmill-operator` crate that enables declarative management of Windmill instance configuration via a Kubernetes `WindmillInstance` CRD. The operator watches CRD resources and performs full declarative sync of global_settings and worker configs to the database, supporting GitOps workflows for instance-level configuration. Co-Authored-By: Claude Opus 4.6 * Add tests for windmill-operator CRD and db_sync - 9 unit tests for CRD serialization, deserialization, metadata, and status field behavior - 15 integration tests for db_sync using #[sqlx::test] with full declarative sync coverage: upsert, delete, protected keys, idempotency, worker config prefix handling, and end-to-end sync Co-Authored-By: Claude Opus 4.6 * Replace untyped BTreeMap CRD fields with typed structs for schema validation GlobalSettings, SmtpSettings, IndexerSettings, and WorkerGroupConfig now have explicit typed fields with serde(flatten) catch-all for forward compatibility. The generated CRD YAML includes a full OpenAPI v3 schema that Kubernetes validates on kubectl apply. Co-Authored-By: Claude Opus 4.6 * Type opaque serde_json::Value CRD fields with real structs Replace most remaining serde_json::Value fields in WindmillInstance CRD with properly typed structs derived from the codebase: - oauths: BTreeMap - otel: OtelSettings - otel_tracing_proxy: OtelTracingProxySettings with ScriptLang enum - critical_error_channels: Vec (untagged enum) - critical_alerts_on_db_oversize: DbOversizeAlert - ducklake_settings: DucklakeSettings with nested catalog/storage types - custom_instance_pg_databases: CustomInstancePgDatabases - autoscaling (worker config): AutoscalingConfig with integration struct - custom_tags, default_tags_workspaces: Vec - default_tags_per_workspace: bool Still opaque (serde_json::Value): object_store_cache_config (kube-core can't generate schemas for internally-tagged enums), secret_backend (EE-private), slack, teams (no clear struct definitions). Regenerated CRD YAML with full OpenAPI schema (352→703 lines). Co-Authored-By: Claude Opus 4.6 * Unify instance config types and add bulk GET/PUT API Move all typed settings (GlobalSettings, WorkerGroupConfig, etc.) from windmill-operator/crd.rs into windmill-common/instance_config.rs so both the API server and operator share a single source of truth. Add diff/apply logic (Merge mode for UI, Replace mode for operator) and InstanceConfig::from_db(). Add GET/PUT /settings/instance_config endpoints so the frontend loads all settings in 1 call instead of 42, and saves with a single bulk PUT. The backend handles the diff internally, running pre-write hooks for changed keys. Refactor windmill-operator/db_sync.rs to use the shared diff+apply functions and slim crd.rs down to the CRD wrapper with re-exports. Includes 32 unit tests and 30 integration tests covering serialization, diff logic, DB roundtrips, protected settings, and edge cases. Co-Authored-By: Claude Opus 4.6 * feat: add Form/YAML toggle to instance settings UI Co-Authored-By: Claude Opus 4.5 * fix: show Form/YAML toggle regardless of hideTabs prop Co-Authored-By: Claude Opus 4.5 * refactor: replace toggle button group with simple YAML toggle Co-Authored-By: Claude Opus 4.5 * feat: mask sensitive fields in YAML view with show/hide toggle Co-Authored-By: Claude Opus 4.5 * feat: hide internal settings and mask sensitive fields in YAML view Co-Authored-By: Claude Opus 4.5 * feat: hide jwt_secret and min_keep_alive_version from API and config exports Co-Authored-By: Claude Opus 4.5 * all * feat: add secretKeyRef support for sensitive fields in operator CRD Allow sensitive fields (license_key, hub_api_secret, scim_token, smtp_password, OAuthClient.secret, custom PG user_pwd) to reference Kubernetes Secrets via the standard secretKeyRef pattern instead of inlining values as plaintext YAML. The reconciler resolves all refs by reading K8s Secrets before syncing to the database. Co-Authored-By: Claude Opus 4.6 * all * all * all * fix: merge main and update dev environment docs Resolve merge conflicts from origin/main, fix duplicate UV_INDEX_STRATEGY_SETTING import, and add Playwright MCP testing instructions to CLAUDE.md. Co-Authored-By: Claude Opus 4.6 * all * fix: init tracing for CLI subcommands and deduplicate setting side-effects Initialize tracing subscriber before early-return CLI paths (sync-config, operator) so tracing calls are not silently dropped. Refactor set_global_setting_internal to call run_setting_pre_write_hook instead of duplicating the side-effect logic. Co-Authored-By: Claude Opus 4.6 * feat: add `wmill instance get-config` CLI command Dumps the current instance config (global settings + worker configs) as YAML. Supports --output-file to write to a file instead of stdout. Co-Authored-By: Claude Opus 4.6 * all --------- Co-authored-by: Claude Opus 4.6 --- .mcp.json | 4 + CLAUDE.md | 20 + ...1f7f387f5055c47f493271d26731336257384.json | 10 +- ...bc7e9ee85289ff5c52af43166928337c257e7.json | 3 +- ...7b23d6dcf2e5364852a5e85b3c41c417bace2.json | 3 +- ...5f40754826db0ee1194409227597a98603e92.json | 26 - ...9e8c0bb132b70668301b535872d1632753e30.json | 3 +- ...66f38ffe2f3a0958adea53e67757c6ac463ec.json | 3 +- ...243ad2e0aef485a780daea5d76b0be2bb2ea2.json | 3 +- ...adbb2860ce52d37a56b2861de1215260ecff8.json | 6 +- ...fbe7db4887c4ac5295052c796bd78a7edc50b.json | 3 +- ...f518b42f4cdb33e034441015494bd26c172d2.json | 3 +- ...5da40d29e20fd16b02355ba073f420c03a767.json | 26 + ...cad6d8995242dc20a4e14c1a8fae4fc6a9fd2.json | 3 +- ...2f7175cc6f2e02d9c565bb5159cae3e0b6835.json | 3 +- ...08532a1020cabaf5e22325a1e05f0491d785c.json | 3 +- ...e35adf640d44d607e62a1fb296e38ac3fdd36.json | 6 +- ...153c43903f929ae5d62fbba12610f89c36d55.json | 2 +- ...c6c70f7f3958564a79bb0c6b3863b1ebb0cbf.json | 3 +- ...1a803e1bced2f8e1a12f8933b7598ee85a147.json | 3 +- ...4f412c57af931f80621aee5012e9cb3535626.json | 3 +- ...8d92a983470adcd3cc850734960c905e61e83.json | 3 +- ...ca846dd5e7fe7ceb1aa8b3fcd26a2bd30eb2c.json | 3 +- ...a22e4276c1da653475628889ce17dc904fbaa.json | 3 +- ...6eb9016207975af1e37042d81df8cb6ae2c53.json | 3 +- ...506bf08c28efe82b68b3d44bafbd3d0e91c29.json | 3 +- ...d2393e297043e4e58555d872b5c4dd89c196a.json | 3 +- ...3a960b89e02ec40b292f3b5198baf2a1d3dbf.json | 3 +- ...fbafe5dc3cdecc17a3d5a7476b160c1b6e7e1.json | 3 +- ...2ede46b4471e21b11f607573a844013911abe.json | 3 +- ...aeda0704770eb200bae635f1933eece90c9d6.json | 20 - ...e33244fe8ab9a803248fc23f334034d24aad4.json | 3 +- ...bb6a8f961c119516e4c33dc928cd3b4f4edbc.json | 3 +- ...9e637e9e156cd85b69126c87dfff89a47711d.json | 3 +- ...9746147aa9b8d0a81642a7b7cb4738a0cad66.json | 3 +- ...d434f860922fbc021185beffb9827647feb8e.json | 3 +- ...13f1f882b695f896f94e5cf91d205633355a1.json | 3 +- ...e1ea2fe96a1b94f5a92953fb1c1642d15c016.json | 3 +- ...1a8a9cc35f362197c0766a5787436af141106.json | 3 +- ...9352d4d1e3cb3a8a08f1c9c03055a1cab1235.json | 3 +- ...f03f72ef81db302d727e18e63a307ac902022.json | 3 +- ...c35c7342ac9a814c73f6290e1d6ebd3a55423.json | 3 +- ...50d6a6935420fbedc83aa3ad1e9be7465c8dd.json | 3 +- backend/Cargo.lock | 23 + backend/Cargo.toml | 9 +- backend/src/main.rs | 70 +- backend/src/monitor.rs | 5 +- backend/tests/instance_config.rs | 994 +++++++++ backend/tests/operator_db_sync.rs | 448 ++++ backend/windmill-api-settings/src/lib.rs | 154 +- backend/windmill-api-users/src/users.rs | 2 +- .../windmill-api-workspaces/src/workspaces.rs | 4 +- backend/windmill-api/openapi.yaml | 59 + backend/windmill-common/Cargo.toml | 2 + .../windmill-common/src/instance_config.rs | 1913 +++++++++++++++++ backend/windmill-common/src/lib.rs | 1 + backend/windmill-operator/Cargo.toml | 30 + backend/windmill-operator/manifests/crd.yaml | 703 ++++++ backend/windmill-operator/src/crd.rs | 218 ++ backend/windmill-operator/src/db_sync.rs | 52 + backend/windmill-operator/src/lib.rs | 13 + backend/windmill-operator/src/reconciler.rs | 179 ++ backend/windmill-operator/src/resolve.rs | 126 ++ cli/src/commands/instance/instance.ts | 22 +- .../deploy/instance-config-iac/.env.example | 6 + examples/deploy/instance-config-iac/README.md | 348 +++ .../instance-config-iac/docker-compose.yml | 90 + .../instance-config-iac/k8s-secrets.yaml | 15 + .../k8s-windmill-instance.yaml | 67 + .../instance-config-iac/windmill-config.yaml | 42 + frontend/package-lock.json | 46 +- frontend/src/lib/components/DiffEditor.svelte | 10 +- .../src/lib/components/InstanceSetting.svelte | 10 +- .../lib/components/InstanceSettings.svelte | 586 ++++- .../lib/components/SuperadminSettings.svelte | 162 +- .../components/SuperadminSettingsInner.svelte | 98 +- .../src/lib/components/instanceSettings.ts | 2 +- .../lib/components/sidebar/MenuButton.svelte | 1 + .../lib/components/sidebar/MenuLink.svelte | 1 + 79 files changed, 6340 insertions(+), 393 deletions(-) delete mode 100644 backend/.sqlx/query-11e24f758a70cd5f3a240bc81a05f40754826db0ee1194409227597a98603e92.json create mode 100644 backend/.sqlx/query-26b35cf50959b1b1fd7e1cb33c65da40d29e20fd16b02355ba073f420c03a767.json delete mode 100644 backend/.sqlx/query-a264bbd8dbabb03854bd25350a7aeda0704770eb200bae635f1933eece90c9d6.json create mode 100644 backend/tests/instance_config.rs create mode 100644 backend/tests/operator_db_sync.rs create mode 100644 backend/windmill-common/src/instance_config.rs create mode 100644 backend/windmill-operator/Cargo.toml create mode 100644 backend/windmill-operator/manifests/crd.yaml create mode 100644 backend/windmill-operator/src/crd.rs create mode 100644 backend/windmill-operator/src/db_sync.rs create mode 100644 backend/windmill-operator/src/lib.rs create mode 100644 backend/windmill-operator/src/reconciler.rs create mode 100644 backend/windmill-operator/src/resolve.rs create mode 100644 examples/deploy/instance-config-iac/.env.example create mode 100644 examples/deploy/instance-config-iac/README.md create mode 100644 examples/deploy/instance-config-iac/docker-compose.yml create mode 100644 examples/deploy/instance-config-iac/k8s-secrets.yaml create mode 100644 examples/deploy/instance-config-iac/k8s-windmill-instance.yaml create mode 100644 examples/deploy/instance-config-iac/windmill-config.yaml diff --git a/.mcp.json b/.mcp.json index 8a587025cd..d71108f978 100644 --- a/.mcp.json +++ b/.mcp.json @@ -3,6 +3,10 @@ "svelte": { "type": "http", "url": "https://mcp.svelte.dev/mcp" + }, + "playwright": { + "command": "npx", + "args": ["@playwright/mcp@latest"] } } } \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index bea5fc855e..7a5c365101 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,6 +20,26 @@ When implementing new features in Windmill, follow these best practices: - Backend (Rust): see `backend/CLAUDE.md` and the `rust-backend` skill: `.claude/skills/rust-backend/SKILL.md` - Frontend (Svelte 5): see `frontend/CLAUDE.md` and the `svelte-frontend` skill: `.claude/skills/svelte-frontend/SKILL.md` +## Dev Environment + +- **Backend**: `cargo run` from `backend/` (API at http://localhost:8000) +- **Frontend**: `REMOTE=http://localhost:8000 npm run dev` from `frontend/` + - The `REMOTE` env var configures the Vite proxy target. Without it, API calls proxy to `https://app.windmill.dev` instead of the local backend. + - The dev server starts on port 3000 (or 3001+ if 3000 is in use). +- **Default login**: `admin@windmill.dev` / `changeme` +- **Instance settings**: navigate to `/#superadmin-settings` (opens the drawer overlay) + +## UI Testing with Playwright MCP + +When testing the frontend with the Playwright MCP tools: + +1. **Start servers**: Launch backend (`cargo run`) and frontend (`REMOTE=http://localhost:8000 npm run dev`) as background tasks +2. **Wait for readiness**: Backend takes ~60s to compile; check output for `health check completed`. Frontend starts in ~5s. +3. **Login flow**: Navigate to `/user/login`, click "Log in without third-party", fill email/password, submit +4. **Instance settings drawer**: Navigate to `/#superadmin-settings` to open the drawer directly +5. **Toggle components**: The YAML toggle uses a custom `` component where the checkbox is visually hidden (`sr-only`). Click the wrapper `