Skip to content

Commit 8526b9d

Browse files
committed
feat: add ServiceAccount models and API methods for management
1 parent b515cb1 commit 8526b9d

File tree

4 files changed

+252
-0
lines changed

4 files changed

+252
-0
lines changed

rapyuta_io_sdk_v2/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
UserGroupCreate as UserGroupCreate,
3737
UserGroupList as UserGroupList,
3838
Daemon as Daemon,
39+
ServiceAccountList as ServiceAccountList,
40+
ServiceAccount as ServiceAccount,
41+
ServiceAccountTokenList as ServiceAccountTokenList,
42+
ServiceAccountToken as ServiceAccountToken,
43+
ServiceAccountTokenInfo as ServiceAccountTokenInfo,
3944
)
4045
from rapyuta_io_sdk_v2.utils import walk_pages as walk_pages
4146

rapyuta_io_sdk_v2/client.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@
5151
BulkRoleBindingUpdate,
5252
RoleList,
5353
OAuth2UpdateURI,
54+
ServiceAccountList,
55+
ServiceAccount,
56+
)
57+
from rapyuta_io_sdk_v2.models.serviceaccount import (
58+
ServiceAccountToken,
59+
ServiceAccountTokenInfo,
60+
ServiceAccountTokenList,
5461
)
5562
from rapyuta_io_sdk_v2.utils import handle_server_errors
5663

@@ -2067,3 +2074,151 @@ def update_role_binding(
20672074
return RoleBinding(**result.json())
20682075
except Exception:
20692076
return result.json()
2077+
2078+
# -------------------ServiceAccount-------------------
2079+
2080+
def list_service_accounts(
2081+
self,
2082+
cont: int = 0,
2083+
limit: int = 50,
2084+
label_selector: list[str] | None = None,
2085+
name: str | None = None,
2086+
regions: list[str] | None = None,
2087+
**kwargs,
2088+
) -> ServiceAccountList:
2089+
parameters: dict[str, Any] = {
2090+
"continue": cont,
2091+
"limit": limit,
2092+
}
2093+
if label_selector:
2094+
parameters["labelSelector"] = label_selector
2095+
if name:
2096+
parameters["name"] = name
2097+
if regions:
2098+
parameters["regions"] = regions
2099+
2100+
result = self.c.get(
2101+
url=f"{self.v2api_host}/v2/serviceaccounts/",
2102+
headers=self.config.get_headers(with_project=False, **kwargs),
2103+
params=parameters,
2104+
)
2105+
2106+
handle_server_errors(result)
2107+
2108+
return ServiceAccountList(**result.json())
2109+
2110+
def get_service_account(
2111+
self,
2112+
name: str,
2113+
**kwargs,
2114+
) -> ServiceAccount:
2115+
result = self.c.get(
2116+
url=f"{self.v2api_host}/v2/serviceaccounts/{name}/",
2117+
headers=self.config.get_headers(with_project=False, **kwargs),
2118+
)
2119+
2120+
handle_server_errors(result)
2121+
return ServiceAccount(**result.json())
2122+
2123+
def create_service_account(
2124+
self,
2125+
service_account: ServiceAccount | dict,
2126+
**kwargs,
2127+
) -> ServiceAccount:
2128+
if isinstance(service_account, dict):
2129+
service_account = ServiceAccount.model_validate(service_account)
2130+
result = self.c.post(
2131+
url=f"{self.v2api_host}/v2/serviceaccounts/",
2132+
headers=self.config.get_headers(with_project=False, **kwargs),
2133+
json=service_account.model_dump(by_alias=True),
2134+
)
2135+
2136+
handle_server_errors(result)
2137+
return ServiceAccount(**result.json())
2138+
2139+
def update_service_account(
2140+
self,
2141+
service_account: ServiceAccount | dict,
2142+
name: str | None,
2143+
**kwargs,
2144+
) -> ServiceAccount:
2145+
if isinstance(service_account, dict):
2146+
service_account = ServiceAccount.model_validate(service_account)
2147+
if not name:
2148+
name = service_account.metadata.name
2149+
result = self.c.put(
2150+
url=f"{self.v2api_host}/v2/serviceaccounts/{name}/",
2151+
headers=self.config.get_headers(with_project=False, **kwargs),
2152+
json=service_account.model_dump(by_alias=True),
2153+
)
2154+
2155+
handle_server_errors(result)
2156+
return ServiceAccount(**result.json())
2157+
2158+
def delete_service_account(
2159+
self,
2160+
name: str,
2161+
**kwargs,
2162+
) -> None:
2163+
result = self.c.delete(
2164+
url=f"{self.v2api_host}/v2/serviceaccounts/{name}/",
2165+
headers=self.config.get_headers(with_project=False, **kwargs),
2166+
)
2167+
2168+
handle_server_errors(result)
2169+
return None
2170+
2171+
def list_service_account_tokens(
2172+
self, name: str, cont: int = 0, limit: int = 50, **kwargs
2173+
) -> ServiceAccountTokenList:
2174+
2175+
result = self.c.get(
2176+
url=f"{self.v2api_host}/v2/serviceaccounts/{name}/token/",
2177+
headers=self.config.get_headers(with_project=False, **kwargs),
2178+
)
2179+
2180+
handle_server_errors(result)
2181+
2182+
return ServiceAccountTokenList(**result.json())
2183+
2184+
def create_service_account_token(
2185+
self, name: str, expiry_at: ServiceAccountToken | dict, **kwargs
2186+
) -> ServiceAccountTokenInfo:
2187+
if isinstance(expiry_at, dict):
2188+
expiry_at = ServiceAccountToken.model_validate(expiry_at)
2189+
2190+
result = self.c.post(
2191+
url=f"{self.v2api_host}/v2/serviceaccounts/{name}/token/",
2192+
headers=self.config.get_headers(with_project=False, **kwargs),
2193+
json=expiry_at.model_dump(by_alias=True, mode="json"),
2194+
)
2195+
2196+
handle_server_errors(result)
2197+
2198+
return ServiceAccountTokenInfo(**result.json())
2199+
2200+
def refresh_service_account_token(
2201+
self, name: str, token_id: str, expiry_at: ServiceAccountToken | dict, **kwargs
2202+
) -> ServiceAccountTokenInfo:
2203+
if isinstance(expiry_at, dict):
2204+
expiry_at = ServiceAccountToken.model_validate(expiry_at)
2205+
2206+
result = self.c.patch(
2207+
url=f"{self.v2api_host}/v2/serviceaccounts/{name}/token/{token_id}/",
2208+
headers=self.config.get_headers(with_project=False, **kwargs),
2209+
json=expiry_at.model_dump(by_alias=True, mode="json"),
2210+
)
2211+
2212+
handle_server_errors(result)
2213+
2214+
return ServiceAccountTokenInfo(**result.json())
2215+
2216+
def delete_service_account_token(self, name: str, token_id: str, **kwargs) -> None:
2217+
result = self.c.delete(
2218+
url=f"{self.v2api_host}/v2/serviceaccounts/{name}/token/{token_id}/",
2219+
headers=self.config.get_headers(with_project=False, **kwargs),
2220+
)
2221+
2222+
handle_server_errors(result)
2223+
2224+
return None

rapyuta_io_sdk_v2/models/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,11 @@
9494
)
9595

9696
from rapyuta_io_sdk_v2.models.daemons import Daemon as Daemon
97+
98+
from rapyuta_io_sdk_v2.models.serviceaccount import (
99+
ServiceAccount as ServiceAccount,
100+
ServiceAccountList as ServiceAccountList,
101+
ServiceAccountTokenList as ServiceAccountTokenList,
102+
ServiceAccountToken as ServiceAccountToken,
103+
ServiceAccountTokenInfo as ServiceAccountTokenInfo,
104+
)
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""
2+
Pydantic models for ServiceAccount resource validation.
3+
4+
This module mirrors the Go `ServiceAccount` and related types from the
5+
`package extensions` snippet provided by the user.
6+
"""
7+
8+
from typing import Literal, override
9+
from datetime import datetime
10+
11+
from pydantic import BaseModel, Field
12+
13+
from rapyuta_io_sdk_v2.models.utils import (
14+
BaseList,
15+
BaseMetadata,
16+
BaseObject,
17+
Domain,
18+
)
19+
20+
21+
class ServiceAccountBinding(BaseModel):
22+
domain: Domain
23+
role_names: list[str] = Field(default_factory=list, alias="roleNames")
24+
25+
26+
class ServiceAccountSpec(BaseModel):
27+
description: str | None = None
28+
roles: list[ServiceAccountBinding] | None = None
29+
30+
31+
class ServiceAccount(BaseObject):
32+
"""ServiceAccount model."""
33+
34+
kind: Literal["ServiceAccount"] | None = "ServiceAccount"
35+
metadata: BaseMetadata
36+
spec: ServiceAccountSpec | None = None
37+
38+
@override
39+
def list_dependencies(self) -> list[str] | None:
40+
dependencies: list[str] = []
41+
42+
# Process service account roles and their domains
43+
if self.spec and self.spec.roles is not None:
44+
for role_binding in self.spec.roles:
45+
# Add domain dependency
46+
if (
47+
role_binding.domain.kind is not None
48+
and role_binding.domain.name is not None
49+
):
50+
domain = (
51+
f"{role_binding.domain.kind.lower()}:{role_binding.domain.name}"
52+
)
53+
dependencies.append(domain)
54+
55+
# Add role dependencies
56+
if role_binding.role_names is not None:
57+
for role in role_binding.role_names:
58+
dependencies.append(f"role:{role}")
59+
60+
return dependencies
61+
62+
63+
class ServiceAccountList(BaseList[ServiceAccount]):
64+
"""List of service accounts using BaseList."""
65+
66+
pass
67+
68+
69+
class ServiceAccountTokenInfo(BaseModel):
70+
id: int | None = None
71+
token: str | None = None
72+
expiry_at: datetime | None = Field(default=None, alias="expiry_at")
73+
74+
75+
class ServiceAccountTokenList(BaseList[ServiceAccountTokenInfo]):
76+
"""List of service account tokens."""
77+
78+
pass
79+
80+
81+
class ServiceAccountToken(BaseModel):
82+
owner: str | None = None
83+
expiry_at: datetime | None = Field(default=None, alias="expiry_at")
84+

0 commit comments

Comments
 (0)