Coverage for mcp/tools/nodepools.py: 97%
52 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-15 15:07 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-15 15:07 +0000
1"""Karpenter NodePool management MCP tools (read-only)."""
3import asyncio
5import cli_runner
6from audit import audit_logged
7from server import mcp
10@mcp.tool(tags={"safe", "nodepools"})
11@audit_logged
12async def nodepools_list(region: str | None = None, cluster: str | None = None) -> str:
13 """`gco nodepools list` — list Karpenter NodePools in a cluster.
15 Args:
16 region: AWS region to query.
17 cluster: EKS cluster name (defaults to ``gco-<region>``).
18 """
19 args = ["nodepools", "list"]
20 if region:
21 args += ["-r", region]
22 if cluster:
23 args += ["--cluster", cluster]
24 return await asyncio.to_thread(cli_runner._run_cli, *args)
27@mcp.tool(tags={"safe", "nodepools"})
28@audit_logged
29async def nodepools_describe(nodepool_name: str, region: str, cluster: str | None = None) -> str:
30 """`gco nodepools describe` — describe a single NodePool.
32 Args:
33 nodepool_name: NodePool name.
34 region: AWS region.
35 cluster: EKS cluster name (defaults to ``gco-<region>``).
36 """
37 args = ["nodepools", "describe", nodepool_name, "-r", region]
38 if cluster:
39 args += ["--cluster", cluster]
40 return await asyncio.to_thread(cli_runner._run_cli, *args)
43# =============================================================================
44# Mutating tools (low-risk)
45# =============================================================================
48@mcp.tool(tags={"low-risk", "nodepools"})
49@audit_logged
50async def nodepools_create_odcr(
51 name: str,
52 region: str,
53 instance_type: str,
54 capacity_reservation_id: str,
55 cluster: str | None = None,
56 count: int = 1,
57 taints: list[str] | None = None,
58 labels: dict[str, str] | None = None,
59) -> str:
60 """`gco nodepools create-odcr` — create a Karpenter NodePool tied to an ODCR.
62 Args:
63 name: NodePool name.
64 region: AWS region.
65 instance_type: EC2 instance type the NodePool will provision.
66 capacity_reservation_id: EC2 Capacity Reservation ID (``cr-...``) or ODCR group ARN.
67 cluster: EKS cluster name (defaults to ``gco-<region>``).
68 count: Initial node count target.
69 taints: Optional taints formatted as ``key=value:effect``; one per ``--taint`` flag.
70 labels: Optional ``key=value`` labels; one per ``--label`` flag.
71 """
72 args = [
73 "nodepools",
74 "create-odcr",
75 name,
76 "-r",
77 region,
78 "--instance-type",
79 instance_type,
80 "--capacity-reservation-id",
81 capacity_reservation_id,
82 "--count",
83 str(count),
84 ]
85 if cluster:
86 args += ["--cluster", cluster]
87 if taints:
88 for taint in taints:
89 args += ["--taint", taint]
90 if labels:
91 for key, value in labels.items():
92 args += ["--label", f"{key}={value}"]
93 return await asyncio.to_thread(cli_runner._run_cli, *args)
96# =============================================================================
97# Destructive tools — gated by GCO_ENABLE_DESTRUCTIVE_OPERATIONS
98# =============================================================================
101import contextlib # noqa: E402
103from feature_flags import FLAG_DESTRUCTIVE_OPERATIONS, is_enabled # noqa: E402
106async def _ctx_warning(message: str) -> None:
107 """Emit ``ctx.warning(...)`` from inside a tool body, no-op when no Context."""
108 try:
109 from fastmcp.server.dependencies import get_context
111 ctx = get_context()
112 except Exception:
113 return
114 with contextlib.suppress(Exception):
115 await ctx.warning(message)
118if is_enabled(FLAG_DESTRUCTIVE_OPERATIONS):
120 @mcp.tool(tags={"destructive", "nodepools"})
121 @audit_logged
122 async def delete_nodepool(nodepool_name: str, region: str, cluster: str | None = None) -> str:
123 """[gated by GCO_ENABLE_DESTRUCTIVE_OPERATIONS] destructive.
125 `gco nodepools delete` — delete a Karpenter NodePool.
126 Cannot be undone — the NodePool, its EC2NodeClass, and any nodes
127 currently provisioned through it are removed.
129 Args:
130 nodepool_name: NodePool name.
131 region: AWS region.
132 cluster: EKS cluster name (defaults to ``gco-<region>``).
133 """
134 await _ctx_warning(
135 f"Deleting NodePool {nodepool_name!r} in {region} — this cannot be undone."
136 )
137 args = ["nodepools", "delete", nodepool_name, "-r", region, "-y"]
138 if cluster:
139 args += ["--cluster", cluster]
140 return await asyncio.to_thread(cli_runner._run_cli, *args)