Skip to content

feat(vks): update_nodegroup disable_auto_scale sends autoScaleConfig:null - #64

Open
vks-team wants to merge 1 commit into
mainfrom
feat/update-nodegroup-disable-autoscale
Open

feat(vks): update_nodegroup disable_auto_scale sends autoScaleConfig:null#64
vks-team wants to merge 1 commit into
mainfrom
feat/update-nodegroup-disable-autoscale

Conversation

@vks-team

@vks-team vks-team commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Mirrors the greennode-cli update-nodegroup alignment onto the MCP update_nodegroup tool. The backend's autoScaleConfig is JsonNullable, but the tool's Optional[AutoScaleConfig]=None + model_dump(exclude_none=True) could only express two states — omit (keep) and object (set). Disabling autoscaling (send autoScaleConfig: null to delete the current config) was impossible.

Add a disable_auto_scale: bool sentinel to UpdateNodeGroupDto (mirroring the CLI's --disable-auto-scale). The handler rejects it together with an autoScaleConfig object (mutually exclusive), strips the sentinel from the payload, and sets an explicit autoScaleConfig: None so the wire body carries "autoScaleConfig": null.

State Call Wire body
keep omit autoScaleConfig field absent
disable disable_auto_scale=true "autoScaleConfig": null
set autoScaleConfig={...} "autoScaleConfig": {...}

Only one state needed work

The two other gaps the CLI fix closed are already handled by the pydantic models here — no code needed:

  • Object missing minSize/maxSize → reject: AutoScaleConfig declares minSize: int = Field(...) + maxSize: int = Field(...) required, so pydantic rejects {}/partial objects at parse.
  • upgradeConfig fills defaults 1/0: UpgradeConfig declares maxSurge=Field(1,...), maxUnavailable=Field(0,...) as defaults, so an empty UpgradeConfig() already sends {maxSurge:1, maxUnavailable:0}.

Only the disable state needed code.

Why a sentinel bool, not exclude_unset/JsonNullable

model_dump(exclude_unset=True) distinguishes "unset" from "provided as None", the pydantic-native 3-state path. But it requires the LLM caller to explicitly pass autoScaleConfig=None to disable — a fragile, undocumented argument contract. A sentinel bool is an unambiguous, self-documenting argument ("disable_auto_scale": true) that matches the CLI's UX. Cost: one extra field the handler strips — trivial.

Test plan

Four new tests in test_nodegroup_tools.py pin all three states plus the mutex:

Test Verifies
disable_autoscale_sends_null wire body is {"autoScaleConfig": None}, sentinel stripped, HTTP PUT made
disable_and_object_is_mutually_exclusive mutex error returned, no HTTP call
omits_autoscale_when_unset autoScaleConfig absent from wire (keep current) — pin
sends_autoscale_object object present on wire — pin
Check Result
uv run pytest tests/ 281 passed
uv run ruff check . all checks passed
uv run ruff format --check . 45 files already formatted

Ported from

greennode-cli PR #11 (update-nodegroup --disable-auto-scale + autoscale null + upgrade defaults). The fail-fast and upgrade-defaults parts were unnecessary here because pydantic's Field(...) required-fields and Field(default) already enforce them; only the disable state ported.

…null

The backend's autoScaleConfig is JsonNullable, but the MCP tool's
Optional[AutoScaleConfig]=None + model_dump(exclude_none=True) could only
express two states — omit (keep) and object (set). Disabling autoscaling
(send autoScaleConfig: null to delete the current config) was impossible.

Add a bool disable_auto_scale sentinel to UpdateNodeGroupDto (mirroring the
greennode-cli --disable-auto-scale flag). The handler rejects it together
with an autoScaleConfig object (mutually exclusive), strips it from the
payload, and sets an explicit autoScaleConfig: None so the wire body carries
"autoScaleConfig": null.

The other two CLI gaps are already handled by the pydantic models:
AutoScaleConfig marks minSize/maxSize required (fail-fast on a partial
object at parse time) and UpgradeConfig defaults maxSurge=1/maxUnavailable=0.
Only the disable state needed work.

Tests pin all three states:
- disable alone sends "autoScaleConfig": null, strips the sentinel
- disable + object rejected before any HTTP call
- autoScaleConfig omitted keeps the field off the wire
- autoScaleConfig object set carries the object
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant