diff --git a/.changeset/new-yaks-sneeze.md b/.changeset/new-yaks-sneeze.md new file mode 100644 index 0000000000..733d443fa5 --- /dev/null +++ b/.changeset/new-yaks-sneeze.md @@ -0,0 +1,5 @@ +--- +'e2b': patch +--- + +Add v2 template update endpoint with TemplateUpdateResponse containing namespaced names diff --git a/.changeset/nine-lies-show.md b/.changeset/nine-lies-show.md new file mode 100644 index 0000000000..b08f4542f2 --- /dev/null +++ b/.changeset/nine-lies-show.md @@ -0,0 +1,5 @@ +--- +'@e2b/cli': minor +--- + +Use v2 template update endpoint and display namespaced template name after publishing diff --git a/packages/cli/src/commands/template/publish.ts b/packages/cli/src/commands/template/publish.ts index b8a853765a..a6649f3a1f 100644 --- a/packages/cli/src/commands/template/publish.ts +++ b/packages/cli/src/commands/template/publish.ts @@ -25,7 +25,7 @@ import { handleE2BRequestError } from '../../utils/errors' import { getUserConfig } from 'src/user' async function publishTemplate(templateID: string, publish: boolean) { - const res = await client.api.PATCH('/templates/{templateID}', { + const res = await client.api.PATCH('/v2/templates/{templateID}', { params: { path: { templateID, @@ -40,7 +40,8 @@ async function publishTemplate(templateID: string, publish: boolean) { res, `Error ${publish ? 'publishing' : 'unpublishing'} sandbox template` ) - return + + return res.data?.names ?? [] } async function templateAction( @@ -181,7 +182,10 @@ async function templateAction( e.configPath )}` ) - await publishTemplate(e.template_id, publish) + const names = await publishTemplate(e.template_id, publish) + if (publish && names.length > 0) { + console.log(` Published as: ${asBold(names.join(', '))}`) + } }) ) process.stdout.write('\n') diff --git a/packages/js-sdk/src/api/schema.gen.ts b/packages/js-sdk/src/api/schema.gen.ts index f735216cef..70c98a1919 100644 --- a/packages/js-sdk/src/api/schema.gen.ts +++ b/packages/js-sdk/src/api/schema.gen.ts @@ -806,7 +806,10 @@ export interface paths { }; options?: never; head?: never; - /** @description Update template */ + /** + * @deprecated + * @description Update template + */ patch: { parameters: { query?: never; @@ -1226,6 +1229,51 @@ export interface paths { patch?: never; trace?: never; }; + "/v2/templates/{templateID}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** @description Update template */ + patch: { + parameters: { + query?: never; + header?: never; + path: { + templateID: components["parameters"]["templateID"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TemplateUpdateRequest"]; + }; + }; + responses: { + /** @description The template was updated successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TemplateUpdateResponse"]; + }; + }; + 400: components["responses"]["400"]; + 401: components["responses"]["401"]; + 500: components["responses"]["500"]; + }; + }; + trace?: never; + }; "/v2/templates/{templateID}/builds/{buildID}": { parameters: { query?: never; @@ -1961,7 +2009,10 @@ export interface components { id: string; }; Template: { - /** @description Aliases of the template */ + /** + * @deprecated + * @description Aliases of the template + */ aliases: string[]; /** * Format: int32 @@ -1986,6 +2037,8 @@ export interface components { */ lastSpawnedAt: string | null; memoryMB: components["schemas"]["MemoryMB"]; + /** @description Names of the template (namespace/alias format when namespaced) */ + names: string[]; /** @description Whether the template is public or only accessible by the team */ public: boolean; /** @@ -2212,8 +2265,15 @@ export interface components { /** @description Whether the template is public or only accessible by the team */ public?: boolean; }; + TemplateUpdateResponse: { + /** @description Names of the template (namespace/alias format when namespaced) */ + names: string[]; + }; TemplateWithBuilds: { - /** @description Aliases of the template */ + /** + * @deprecated + * @description Aliases of the template + */ aliases: string[]; /** @description List of builds for the template */ builds: components["schemas"]["TemplateBuild"][]; @@ -2227,6 +2287,8 @@ export interface components { * @description Time when the template was last used */ lastSpawnedAt: string | null; + /** @description Names of the template (namespace/alias format when namespaced) */ + names: string[]; /** @description Whether the template is public or only accessible by the team */ public: boolean; /** diff --git a/packages/python-sdk/e2b/api/client/api/templates/patch_v_2_templates_template_id.py b/packages/python-sdk/e2b/api/client/api/templates/patch_v_2_templates_template_id.py new file mode 100644 index 0000000000..b262332534 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/api/templates/patch_v_2_templates_template_id.py @@ -0,0 +1,185 @@ +from http import HTTPStatus +from typing import Any, Optional, Union + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.error import Error +from ...models.template_update_request import TemplateUpdateRequest +from ...models.template_update_response import TemplateUpdateResponse +from ...types import Response + + +def _get_kwargs( + template_id: str, + *, + body: TemplateUpdateRequest, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + + _kwargs: dict[str, Any] = { + "method": "patch", + "url": f"/v2/templates/{template_id}", + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Optional[Union[Error, TemplateUpdateResponse]]: + if response.status_code == 200: + response_200 = TemplateUpdateResponse.from_dict(response.json()) + + return response_200 + if response.status_code == 400: + response_400 = Error.from_dict(response.json()) + + return response_400 + if response.status_code == 401: + response_401 = Error.from_dict(response.json()) + + return response_401 + if response.status_code == 500: + response_500 = Error.from_dict(response.json()) + + return response_500 + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Response[Union[Error, TemplateUpdateResponse]]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + template_id: str, + *, + client: AuthenticatedClient, + body: TemplateUpdateRequest, +) -> Response[Union[Error, TemplateUpdateResponse]]: + """Update template + + Args: + template_id (str): + body (TemplateUpdateRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Union[Error, TemplateUpdateResponse]] + """ + + kwargs = _get_kwargs( + template_id=template_id, + body=body, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + template_id: str, + *, + client: AuthenticatedClient, + body: TemplateUpdateRequest, +) -> Optional[Union[Error, TemplateUpdateResponse]]: + """Update template + + Args: + template_id (str): + body (TemplateUpdateRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Union[Error, TemplateUpdateResponse] + """ + + return sync_detailed( + template_id=template_id, + client=client, + body=body, + ).parsed + + +async def asyncio_detailed( + template_id: str, + *, + client: AuthenticatedClient, + body: TemplateUpdateRequest, +) -> Response[Union[Error, TemplateUpdateResponse]]: + """Update template + + Args: + template_id (str): + body (TemplateUpdateRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Union[Error, TemplateUpdateResponse]] + """ + + kwargs = _get_kwargs( + template_id=template_id, + body=body, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + template_id: str, + *, + client: AuthenticatedClient, + body: TemplateUpdateRequest, +) -> Optional[Union[Error, TemplateUpdateResponse]]: + """Update template + + Args: + template_id (str): + body (TemplateUpdateRequest): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Union[Error, TemplateUpdateResponse] + """ + + return ( + await asyncio_detailed( + template_id=template_id, + client=client, + body=body, + ) + ).parsed diff --git a/packages/python-sdk/e2b/api/client/models/__init__.py b/packages/python-sdk/e2b/api/client/models/__init__.py index 49391c3a39..163e03f977 100644 --- a/packages/python-sdk/e2b/api/client/models/__init__.py +++ b/packages/python-sdk/e2b/api/client/models/__init__.py @@ -67,6 +67,7 @@ from .template_request_response_v3 import TemplateRequestResponseV3 from .template_step import TemplateStep from .template_update_request import TemplateUpdateRequest +from .template_update_response import TemplateUpdateResponse from .template_with_builds import TemplateWithBuilds from .update_team_api_key import UpdateTeamAPIKey @@ -136,6 +137,7 @@ "TemplateRequestResponseV3", "TemplateStep", "TemplateUpdateRequest", + "TemplateUpdateResponse", "TemplateWithBuilds", "UpdateTeamAPIKey", ) diff --git a/packages/python-sdk/e2b/api/client/models/template.py b/packages/python-sdk/e2b/api/client/models/template.py index f29b16f975..c75d4d8e09 100644 --- a/packages/python-sdk/e2b/api/client/models/template.py +++ b/packages/python-sdk/e2b/api/client/models/template.py @@ -30,6 +30,7 @@ class Template: envd_version (str): Version of the envd running in the sandbox last_spawned_at (Union[None, datetime.datetime]): Time when the template was last used memory_mb (int): Memory for the sandbox in MiB + names (list[str]): Names of the template (namespace/alias format when namespaced) public (bool): Whether the template is public or only accessible by the team spawn_count (int): Number of times the template was used template_id (str): Identifier of the template @@ -47,6 +48,7 @@ class Template: envd_version: str last_spawned_at: Union[None, datetime.datetime] memory_mb: int + names: list[str] public: bool spawn_count: int template_id: str @@ -86,6 +88,8 @@ def to_dict(self) -> dict[str, Any]: memory_mb = self.memory_mb + names = self.names + public = self.public spawn_count = self.spawn_count @@ -109,6 +113,7 @@ def to_dict(self) -> dict[str, Any]: "envdVersion": envd_version, "lastSpawnedAt": last_spawned_at, "memoryMB": memory_mb, + "names": names, "public": public, "spawnCount": spawn_count, "templateID": template_id, @@ -171,6 +176,8 @@ def _parse_last_spawned_at(data: object) -> Union[None, datetime.datetime]: memory_mb = d.pop("memoryMB") + names = cast(list[str], d.pop("names")) + public = d.pop("public") spawn_count = d.pop("spawnCount") @@ -191,6 +198,7 @@ def _parse_last_spawned_at(data: object) -> Union[None, datetime.datetime]: envd_version=envd_version, last_spawned_at=last_spawned_at, memory_mb=memory_mb, + names=names, public=public, spawn_count=spawn_count, template_id=template_id, diff --git a/packages/python-sdk/e2b/api/client/models/template_update_response.py b/packages/python-sdk/e2b/api/client/models/template_update_response.py new file mode 100644 index 0000000000..7a273c84d0 --- /dev/null +++ b/packages/python-sdk/e2b/api/client/models/template_update_response.py @@ -0,0 +1,59 @@ +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="TemplateUpdateResponse") + + +@_attrs_define +class TemplateUpdateResponse: + """ + Attributes: + names (list[str]): Names of the template (namespace/alias format when namespaced) + """ + + names: list[str] + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + names = self.names + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "names": names, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + names = cast(list[str], d.pop("names")) + + template_update_response = cls( + names=names, + ) + + template_update_response.additional_properties = d + return template_update_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/packages/python-sdk/e2b/api/client/models/template_with_builds.py b/packages/python-sdk/e2b/api/client/models/template_with_builds.py index e047ce1a3e..90c0a63445 100644 --- a/packages/python-sdk/e2b/api/client/models/template_with_builds.py +++ b/packages/python-sdk/e2b/api/client/models/template_with_builds.py @@ -21,6 +21,7 @@ class TemplateWithBuilds: builds (list['TemplateBuild']): List of builds for the template created_at (datetime.datetime): Time when the template was created last_spawned_at (Union[None, datetime.datetime]): Time when the template was last used + names (list[str]): Names of the template (namespace/alias format when namespaced) public (bool): Whether the template is public or only accessible by the team spawn_count (int): Number of times the template was used template_id (str): Identifier of the template @@ -31,6 +32,7 @@ class TemplateWithBuilds: builds: list["TemplateBuild"] created_at: datetime.datetime last_spawned_at: Union[None, datetime.datetime] + names: list[str] public: bool spawn_count: int template_id: str @@ -53,6 +55,8 @@ def to_dict(self) -> dict[str, Any]: else: last_spawned_at = self.last_spawned_at + names = self.names + public = self.public spawn_count = self.spawn_count @@ -69,6 +73,7 @@ def to_dict(self) -> dict[str, Any]: "builds": builds, "createdAt": created_at, "lastSpawnedAt": last_spawned_at, + "names": names, "public": public, "spawnCount": spawn_count, "templateID": template_id, @@ -109,6 +114,8 @@ def _parse_last_spawned_at(data: object) -> Union[None, datetime.datetime]: last_spawned_at = _parse_last_spawned_at(d.pop("lastSpawnedAt")) + names = cast(list[str], d.pop("names")) + public = d.pop("public") spawn_count = d.pop("spawnCount") @@ -122,6 +129,7 @@ def _parse_last_spawned_at(data: object) -> Union[None, datetime.datetime]: builds=builds, created_at=created_at, last_spawned_at=last_spawned_at, + names=names, public=public, spawn_count=spawn_count, template_id=template_id, diff --git a/spec/openapi.yml b/spec/openapi.yml index ea86cc5956..dab7c95535 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -179,6 +179,16 @@ components: type: boolean description: Whether the template is public or only accessible by the team + TemplateUpdateResponse: + required: + - names + properties: + names: + type: array + description: Names of the template (namespace/alias format when namespaced) + items: + type: string + CPUCount: type: integer format: int32 @@ -617,6 +627,7 @@ components: - buildCount - envdVersion - aliases + - names - buildStatus properties: templateID: @@ -637,6 +648,12 @@ components: aliases: type: array description: Aliases of the template + deprecated: true + items: + type: string + names: + type: array + description: Names of the template (namespace/alias format when namespaced) items: type: string createdAt: @@ -810,6 +827,7 @@ components: - templateID - public - aliases + - names - createdAt - updatedAt - lastSpawnedAt @@ -825,6 +843,12 @@ components: aliases: type: array description: Aliases of the template + deprecated: true + items: + type: string + names: + type: array + description: Names of the template (namespace/alias format when namespaced) items: type: string createdAt: @@ -2354,6 +2378,7 @@ paths: $ref: "#/components/responses/500" patch: description: Update template + deprecated: true tags: [templates] security: - ApiKeyAuth: [] @@ -2423,6 +2448,37 @@ paths: "500": $ref: "#/components/responses/500" + /v2/templates/{templateID}: + patch: + description: Update template + tags: [templates] + security: + - ApiKeyAuth: [] + - AccessTokenAuth: [] + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] + parameters: + - $ref: "#/components/parameters/templateID" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/TemplateUpdateRequest" + responses: + "200": + description: The template was updated successfully + content: + application/json: + schema: + $ref: "#/components/schemas/TemplateUpdateResponse" + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" + /templates/{templateID}/builds/{buildID}/status: get: description: Get template build info