diff --git a/src/bentoml/_internal/cloud/bento.py b/src/bentoml/_internal/cloud/bento.py index e45cf053e4e..f24018900b4 100644 --- a/src/bentoml/_internal/cloud/bento.py +++ b/src/bentoml/_internal/cloud/bento.py @@ -562,6 +562,19 @@ def list(self) -> BentoWithRepositoryListSchema: ] return res + def delete(self, name: str, version: str) -> None: + """Delete a bento from the remote bento store + + Args: + name: The name of the bento repository + version: The version of the bento to delete + """ + rest_client = self._client + res = rest_client.v1.get_bento(bento_repository_name=name, version=version) + if res is None: + raise NotFound(f'Bento "{name}:{version}" not found on remote') + rest_client.v1.delete_bento(bento_repository_name=name, version=version) + def get(self, name: str, version: str | None) -> BentoSchema: """Get a bento by name and version diff --git a/src/bentoml/_internal/cloud/client.py b/src/bentoml/_internal/cloud/client.py index 77dad1136af..1fba7a4a5f3 100644 --- a/src/bentoml/_internal/cloud/client.py +++ b/src/bentoml/_internal/cloud/client.py @@ -231,6 +231,11 @@ def finish_upload_bento( self._check_resp(resp) return schema_from_object(resp.json(), BentoSchema) + def delete_bento(self, bento_repository_name: str, version: str) -> None: + url = f"/api/v1/bento_repositories/{bento_repository_name}/bentos/{version}" + resp = self.session.delete(url) + self._check_resp(resp) + def upload_bento( self, bento_repository_name: str, version: str, data: t.IO[bytes] ) -> None: diff --git a/src/bentoml/bentos.py b/src/bentoml/bentos.py index fa46a72d9ab..2621fa3774d 100644 --- a/src/bentoml/bentos.py +++ b/src/bentoml/bentos.py @@ -76,9 +76,19 @@ def get( def delete( tag: Tag | str, *, + cloud: bool = False, _bento_store: BentoStore = Provide[BentoMLContainer.bento_store], + _cloud_client: BentoCloudClient = Provide[BentoMLContainer.bentocloud_client], ): - _bento_store.delete(tag) + if cloud: + _tag = Tag.from_taglike(tag) + if _tag.version is None: + raise BentoMLException( + f'Bento version is required for cloud deletion. Please specify a version, e.g. "{_tag.name}:"' + ) + _cloud_client.bento.delete(_tag.name, _tag.version) + else: + _bento_store.delete(tag) @inject diff --git a/src/bentoml_cli/bentos.py b/src/bentoml_cli/bentos.py index 6090b058719..eb62af42ee8 100644 --- a/src/bentoml_cli/bentos.py +++ b/src/bentoml_cli/bentos.py @@ -220,13 +220,21 @@ def list_bentos( is_flag=True, help="Skip confirmation when deleting a specific bento bundle", ) + @click.option( + "--cloud", + is_flag=True, + default=False, + help="Delete Bento from BentoCloud remote server", + ) @inject def delete( delete_targets: list[str], yes: bool, + cloud: bool, bento_store: BentoStore = Provide[BentoMLContainer.bento_store], + cloud_client: BentoCloudClient = Provide[BentoMLContainer.bentocloud_client], ) -> None: # type: ignore (not accessed) - """Delete Bento in local bento store. + """Delete Bento in local bento store or from BentoCloud. \b Examples: @@ -235,25 +243,37 @@ def delete( * Bulk delete multiple bento bundles by name and version, separated by ",", e.g.: `bentoml delete Irisclassifier:v1,MyPredictService:v2` * Bulk delete multiple bento bundles by name and version, separated by " ", e.g.: `bentoml delete Irisclassifier:v1 MyPredictService:v2` * Bulk delete without confirmation, e.g.: `bentoml delete IrisClassifier --yes` + * Delete from BentoCloud, e.g.: `bentoml delete IrisClassifier:v1 --cloud` """ def delete_target(target: str) -> None: tag = Tag.from_str(target) - if tag.version is None: - to_delete_bentos = bento_store.list(target) + if cloud: + if tag.version is None: + raise click.BadParameter( + f'Deleting all versions is not supported for cloud bentos. Please specify a version, e.g. "{tag.name}:"' + ) + if not yes: + if not click.confirm(f"delete bento {tag} from BentoCloud?"): + return + cloud_client.bento.delete(tag.name, tag.version) + rich.print(f"Bento {tag} deleted from BentoCloud.") else: - to_delete_bentos = [bento_store.get(tag)] - - for bento in to_delete_bentos: - if yes: - delete_confirmed = True + if tag.version is None: + to_delete_bentos = bento_store.list(target) else: - delete_confirmed = click.confirm(f"delete bento {bento.tag}?") + to_delete_bentos = [bento_store.get(tag)] + + for bento in to_delete_bentos: + if yes: + delete_confirmed = True + else: + delete_confirmed = click.confirm(f"delete bento {bento.tag}?") - if delete_confirmed: - bento_store.delete(bento.tag) - rich.print(f"{bento} deleted.") + if delete_confirmed: + bento_store.delete(bento.tag) + rich.print(f"{bento} deleted.") for target in delete_targets: delete_target(target)