Skip to content

Commit 340b271

Browse files
committed
✨ feat: add Pydantic models for Deployment, Disk, ManagedService, Network, Organization, Package, Project, Secret, StaticRoute, and User
1 parent ff6a951 commit 340b271

File tree

12 files changed

+1069
-0
lines changed

12 files changed

+1069
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from .secret import Secret as Secret, SecretList as SecretList
2+
from .staticroute import StaticRoute as StaticRoute, StaticRouteList as StaticRouteList
3+
from .disk import Disk as Disk, DiskList as DiskList
4+
from .package import Package as Package, PackageList as PackageList
5+
from .deployment import Deployment as Deployment, DeploymentList as DeploymentList
6+
from .project import Project as Project, ProjectList as ProjectList
7+
from .network import Network as Network, NetworkList as NetworkList
8+
from .managedservice import (
9+
ManagedService as ManagedService,
10+
ManagedServiceList as ManagedServiceList,
11+
)
12+
from .user import User as User
13+
from .organization import Organization as Organization
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
"""
2+
Pydantic models for Deployment resource validation.
3+
4+
This module contains Pydantic models that correspond to the Deployment JSON schema,
5+
providing validation for Deployment resources to help users identify missing or
6+
incorrect fields.
7+
"""
8+
9+
from typing import Literal
10+
from pydantic import BaseModel, Field, model_validator
11+
12+
from rapyuta_io_sdk_v2.pydantic_models.utils import (
13+
BaseMetadata,
14+
BaseList,
15+
Depends,
16+
DeploymentPhase,
17+
DeploymentStatusType,
18+
ExecutableStatusType,
19+
RestartPolicy,
20+
Runtime,
21+
)
22+
23+
24+
class StringMap(dict[str, str]):
25+
pass
26+
27+
28+
# --- Depends Models ---
29+
class PackageDepends(Depends):
30+
kind: Literal["package"] = Field(default="package")
31+
32+
33+
class DeploymentMetadata(BaseMetadata):
34+
"""Metadata for Deployment resource."""
35+
36+
depends: PackageDepends | None = None
37+
38+
39+
class EnvArgsSpec(BaseModel):
40+
name: str
41+
value: str | None = None
42+
43+
44+
class DeploymentVolume(BaseModel):
45+
"""Unified volume spec matching Go DeploymentVolume struct."""
46+
47+
execName: str | None = None
48+
mountPath: str | None = None
49+
subPath: str | None = None
50+
uid: int | None = None
51+
gid: int | None = None
52+
perm: int | None = None
53+
depends: Depends | None = None
54+
55+
@model_validator(mode="before")
56+
@classmethod
57+
def handle_empty_depends(cls, data):
58+
"""Handle empty depends dictionaries by converting them to None."""
59+
if isinstance(data, dict) and "depends" in data:
60+
depends = data["depends"]
61+
# If depends is an empty dictionary, set it to None
62+
if isinstance(depends, dict) and not depends:
63+
data["depends"] = None
64+
return data
65+
66+
67+
class DeploymentStaticRoute(BaseModel):
68+
"""Static route configuration matching Go DeploymentStaticRoute struct."""
69+
70+
name: str | None = None
71+
url: str | None = None
72+
depends: Depends | None = None
73+
74+
@model_validator(mode="before")
75+
@classmethod
76+
def handle_empty_depends(cls, data):
77+
"""Handle empty depends dictionaries by converting them to None."""
78+
if isinstance(data, dict) and "depends" in data:
79+
depends = data["depends"]
80+
# If depends is an empty dictionary, set it to None
81+
if isinstance(depends, dict) and not depends:
82+
data["depends"] = None
83+
return data
84+
85+
86+
class ManagedServiceSpec(BaseModel):
87+
depends: dict[str, str] | None = None
88+
89+
@model_validator(mode="before")
90+
@classmethod
91+
def handle_empty_depends(cls, data):
92+
"""Handle empty depends dictionaries by converting them to None."""
93+
if isinstance(data, dict) and "depends" in data:
94+
depends = data["depends"]
95+
# If depends is an empty dictionary, set it to None
96+
if isinstance(depends, dict) and not depends:
97+
data["depends"] = None
98+
return data
99+
100+
101+
class DeploymentROSNetwork(BaseModel):
102+
"""ROS Network configuration matching Go DeploymentROSNetwork struct."""
103+
104+
domainID: int = Field(description="ROS Domain ID")
105+
depends: Depends | None = None
106+
interface: str | None = Field(default=None, description="Network interface")
107+
108+
@model_validator(mode="before")
109+
@classmethod
110+
def handle_empty_depends(cls, data):
111+
"""Handle empty depends dictionaries by converting them to None."""
112+
if isinstance(data, dict) and "depends" in data:
113+
depends = data["depends"]
114+
# If depends is an empty dictionary, set it to None
115+
if isinstance(depends, dict) and not depends:
116+
data["depends"] = None
117+
return data
118+
119+
120+
class DeploymentParamConfig(BaseModel):
121+
"""Param configuration matching Go DeploymentParamConfig struct."""
122+
123+
enabled: bool | None = None
124+
trees: list[str] | None = None
125+
blockUntilSynced: bool | None = Field(default=False)
126+
127+
128+
class DeploymentVPNConfig(BaseModel):
129+
"""VPN configuration matching Go DeploymentVPNConfig struct."""
130+
131+
enabled: bool | None = Field(default=False)
132+
133+
134+
class DeploymentFeatures(BaseModel):
135+
"""Features configuration matching Go DeploymentFeatures struct."""
136+
137+
params: DeploymentParamConfig | None = None
138+
vpn: DeploymentVPNConfig | None = None
139+
140+
141+
class DeploymentDevice(BaseModel):
142+
"""Device configuration matching Go DeploymentDevice struct."""
143+
144+
depends: Depends | None = None
145+
146+
@model_validator(mode="before")
147+
@classmethod
148+
def handle_empty_depends(cls, data):
149+
"""Handle empty depends dictionaries by converting them to None."""
150+
if isinstance(data, dict) and "depends" in data:
151+
depends = data["depends"]
152+
# If depends is an empty dictionary, set it to None
153+
if isinstance(depends, dict) and not depends:
154+
data["depends"] = None
155+
return data
156+
157+
158+
class DeploymentSpec(BaseModel):
159+
runtime: Runtime
160+
depends: list[Depends] | None = None
161+
device: DeploymentDevice | None = None
162+
restart: RestartPolicy = Field(default="always")
163+
envArgs: list[EnvArgsSpec] | None = None
164+
volumes: list[DeploymentVolume] | None = None
165+
rosNetworks: list[DeploymentROSNetwork] | None = None
166+
features: DeploymentFeatures | None = None
167+
staticRoutes: list[DeploymentStaticRoute] | None = None
168+
managedServices: list[ManagedServiceSpec] | None = None
169+
170+
@model_validator(mode="after")
171+
def validate_runtime_and_volumes(self):
172+
"""Validate that runtime and volume configurations are compatible."""
173+
if self.runtime == "device" and self.volumes:
174+
# For device runtime, volumes should not have cloud-specific depends
175+
for volume in self.volumes:
176+
if volume.depends and hasattr(volume.depends, "kind"):
177+
# Device volumes should depend on disks, not cloud resources
178+
if volume.depends.kind in ["managedService", "cloudService"]:
179+
raise ValueError(
180+
f"Device runtime cannot use cloud volume dependency: {volume.depends.kind}"
181+
)
182+
elif self.runtime == "cloud" and self.volumes:
183+
# For cloud runtime, volumes should not have device-specific fields
184+
for volume in self.volumes:
185+
if any(
186+
[
187+
volume.uid is not None,
188+
volume.gid is not None,
189+
volume.perm is not None,
190+
]
191+
):
192+
raise ValueError(
193+
"Cloud runtime cannot use device-specific volume fields: uid, gid, perm"
194+
)
195+
return self
196+
197+
198+
class ExecutableStatus(BaseModel):
199+
name: str | None = None
200+
status: ExecutableStatusType | None = None
201+
errorCode: str | None = Field(default=None, alias="error_code")
202+
reason: str | None = None
203+
restartCount: int | None = Field(default=None, alias="restart_count") # int32 in Go
204+
exitCode: int | None = Field(default=None, alias="exit_code") # int32 in Go
205+
206+
207+
class DependentDeploymentStatus(BaseModel):
208+
name: str | None = None
209+
guid: str | None = None
210+
status: DeploymentStatusType | None = None
211+
phase: DeploymentPhase | None = None
212+
errorCodes: list[str] | None = Field(default=None, alias="error_codes")
213+
214+
215+
class DependentNetworkStatus(BaseModel):
216+
name: str | None = None
217+
guid: str | None = None
218+
status: DeploymentStatusType | None = None
219+
phase: DeploymentPhase | None = None
220+
errorCodes: list[str] | None = Field(default=None, alias="error_codes")
221+
222+
223+
class DependentDiskStatus(BaseModel):
224+
name: str | None = None
225+
guid: str | None = None
226+
status: str | None = None
227+
errorCode: str | None = Field(default=None, alias="error_codes")
228+
229+
230+
class Dependencies(BaseModel):
231+
deployments: list[DependentDeploymentStatus] | None = None
232+
networks: list[DependentNetworkStatus] | None = None
233+
disks: list[DependentDiskStatus] | None = Field(default=None, alias="disk")
234+
235+
236+
class DeploymentStatus(BaseModel):
237+
phase: DeploymentPhase | None = None
238+
status: DeploymentStatusType | None = None
239+
errorCodes: list[str] | None = Field(default=None, alias="error_codes")
240+
executablesStatus: dict[str, ExecutableStatus] | None = Field(
241+
default=None, alias="executables_status"
242+
)
243+
dependencies: Dependencies | None = None
244+
245+
246+
class Deployment(BaseModel):
247+
"""Deployment model."""
248+
249+
apiVersion: str | None = None
250+
kind: str | None = None
251+
metadata: DeploymentMetadata
252+
spec: DeploymentSpec
253+
status: DeploymentStatus | None = None
254+
255+
256+
class DeploymentList(BaseList[Deployment]):
257+
"""List of deployments using BaseList."""
258+
259+
pass
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""
2+
Pydantic models for Disk resource validation.
3+
4+
This module contains Pydantic models that correspond to the Disk JSON schema,
5+
providing validation for Disk resources to help users identify missing or
6+
incorrect fields.
7+
"""
8+
9+
from typing import Literal
10+
from pydantic import BaseModel, Field, field_validator
11+
12+
from rapyuta_io_sdk_v2.pydantic_models.utils import BaseMetadata, BaseList, Runtime
13+
14+
15+
class DiskBound(BaseModel):
16+
deployment_guid: str | None
17+
deployment_name: str | None
18+
19+
20+
class DiskSpec(BaseModel):
21+
"""Specification for Disk resource."""
22+
23+
runtime: Runtime = Field(
24+
default="cloud", description="Runtime environment for the disk"
25+
)
26+
capacity: int | float | None = Field(default=None, description="Disk capacity in GB")
27+
28+
@field_validator("capacity")
29+
@classmethod
30+
def validate_capacity(cls, v):
31+
"""Validate disk capacity against allowed values."""
32+
if v is not None:
33+
allowed_capacities = [4, 8, 16, 32, 64, 128, 256, 512]
34+
if v not in allowed_capacities:
35+
raise ValueError(
36+
f"Disk capacity must be one of: {allowed_capacities}. Got: {v}"
37+
)
38+
return v
39+
40+
41+
class DiskStatus(BaseModel):
42+
status: Literal["Available", "Bound", "Released", "Failed", "Pending"]
43+
capacityUsed: float | None = Field(
44+
default=None, description="Used disk capacity in GB"
45+
)
46+
capacityAvailable: float | None = Field(
47+
default=None, description="Available disk capacity in GB"
48+
)
49+
errorCode: str | None = Field(default=None, description="Error code if any")
50+
diskBound: DiskBound | None = Field(
51+
default=None, description="Disk bound information"
52+
)
53+
54+
55+
class Disk(BaseModel):
56+
"""Disk model."""
57+
58+
apiVersion: str | None
59+
kind: str | None
60+
metadata: BaseMetadata = Field(description="Metadata for the Disk resource")
61+
spec: DiskSpec = Field(description="Specification for the Disk resource")
62+
status: DiskStatus | None = Field(default=None)
63+
64+
65+
class DiskList(BaseList[Disk]):
66+
"""List of disks using BaseList."""
67+
68+
pass
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""Pydantic models for ManagedService resource."""
2+
3+
from typing import Any, Literal
4+
from pydantic import BaseModel, Field
5+
6+
from rapyuta_io_sdk_v2.pydantic_models.utils import BaseMetadata, BaseList
7+
8+
9+
class ManagedServiceMetadata(BaseMetadata):
10+
"""Metadata for ManagedService resource."""
11+
12+
# Inherits all common fields from BaseMetadata
13+
pass
14+
15+
16+
class ManagedServiceSpec(BaseModel):
17+
"""Specification for ManagedService resource."""
18+
19+
provider: Literal["elasticsearch", "headscalevpn"] = Field(
20+
description="The provider for the managed service"
21+
)
22+
config: dict[str, Any] = Field(
23+
description="Configuration object for the managed service"
24+
)
25+
26+
27+
class ManagedService(BaseModel):
28+
"""Managed service model."""
29+
30+
apiVersion: str | None = None
31+
kind: str | None = None
32+
metadata: BaseMetadata
33+
spec: ManagedServiceSpec
34+
35+
36+
class ManagedServiceList(BaseList[ManagedService]):
37+
"""List of managed services using BaseList."""
38+
39+
pass

0 commit comments

Comments
 (0)