|
1 | | -from typing import Any, Dict, List, Optional |
| 1 | +from typing import Any, Dict, List, Optional, Union |
2 | 2 |
|
3 | 3 | from pydantic import BaseModel, ConfigDict, Field, PrivateAttr |
4 | 4 |
|
@@ -351,6 +351,59 @@ class PodAntiAffinity(StrictBaseModel): |
351 | 351 | ) # Required rules (hard constraints, weight ignored) |
352 | 352 |
|
353 | 353 |
|
| 354 | +class NodeAffinityRule(StrictBaseModel): |
| 355 | + """Single node affinity rule configuration.""" |
| 356 | + |
| 357 | + weight: Optional[int] = None # Weight for preferred rules (1-100) |
| 358 | + matchExpressions: List[AnyDict] = Field( |
| 359 | + default_factory=list |
| 360 | + ) # Match expressions for node labels (key, operator, values) |
| 361 | + matchFields: List[AnyDict] = Field( |
| 362 | + default_factory=list |
| 363 | + ) # Match fields for node fields (key, operator, values) |
| 364 | + |
| 365 | + |
| 366 | +class NodeAffinity(StrictBaseModel): |
| 367 | + """Node affinity configuration supporting multiple rules.""" |
| 368 | + |
| 369 | + preferred: List[NodeAffinityRule] = Field( |
| 370 | + default_factory=list |
| 371 | + ) # Preferred rules (soft constraints) |
| 372 | + required: List[NodeAffinityRule] = Field( |
| 373 | + default_factory=list |
| 374 | + ) # Required rules (hard constraints, weight ignored) |
| 375 | + |
| 376 | + |
| 377 | +class PodAffinityRule(StrictBaseModel): |
| 378 | + """Single pod affinity rule configuration.""" |
| 379 | + |
| 380 | + weight: Optional[int] = None # Weight for preferred rules (1-100) |
| 381 | + topologyKey: str # Topology key (e.g., "kubernetes.io/hostname", "topology.kubernetes.io/zone") |
| 382 | + labelSelector: AnyDict = Field( |
| 383 | + default_factory=dict |
| 384 | + ) # Label selector with matchLabels or matchExpressions. |
| 385 | + namespaceSelector: AnyDict = Field(default_factory=dict) # Namespace selector (optional) |
| 386 | + |
| 387 | + |
| 388 | +class PodAffinity(StrictBaseModel): |
| 389 | + """Pod affinity configuration supporting multiple rules.""" |
| 390 | + |
| 391 | + preferred: List[PodAffinityRule] = Field( |
| 392 | + default_factory=list |
| 393 | + ) # Preferred rules (soft constraints) |
| 394 | + required: List[PodAffinityRule] = Field( |
| 395 | + default_factory=list |
| 396 | + ) # Required rules (hard constraints, weight ignored) |
| 397 | + |
| 398 | + |
| 399 | +class Affinity(StrictBaseModel): |
| 400 | + """Structured affinity configuration supporting node, pod, and pod anti-affinity.""" |
| 401 | + |
| 402 | + nodeAffinity: Optional[NodeAffinity] = None |
| 403 | + podAffinity: Optional[PodAffinity] = None |
| 404 | + podAntiAffinity: Optional[PodAntiAffinity] = None |
| 405 | + |
| 406 | + |
354 | 407 | class PodMonitoring(StrictBaseModel): |
355 | 408 | enabled: bool = False |
356 | 409 | name: Optional[str] = None |
@@ -425,7 +478,9 @@ class ServiceConfig(StrictBaseModel): |
425 | 478 | updateStrategy: UpdateStrategy = Field(default_factory=UpdateStrategy) |
426 | 479 | tolerations: List[AnyDict] = Field(default_factory=list) |
427 | 480 | nodeSelector: StrDict = Field(default_factory=dict) |
428 | | - affinity: AnyDict = Field(default_factory=dict) |
| 481 | + affinity: Union[Affinity, AnyDict, None] = Field( |
| 482 | + default=None |
| 483 | + ) # Structured affinity configuration (preferred) or legacy dict format |
429 | 484 | podAntiAffinity: Optional[PodAntiAffinity] = None |
430 | 485 | topologySpreadConstraints: List[AnyDict] = Field(default_factory=list) |
431 | 486 | podDisruptionBudget: Optional[PodDisruptionBudget] = None |
|
0 commit comments