Skip to content

Commit

Permalink
feat(package): supports delete multiple packages
Browse files Browse the repository at this point in the history
This commits adds the option to delete multiple packages by name or
regex along with a specific version of packages.
  • Loading branch information
pallabpain committed Jan 24, 2024
1 parent 2dd2789 commit 67c0b58
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 14 deletions.
105 changes: 92 additions & 13 deletions riocli/package/delete.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2021 Rapyuta Robotics
# Copyright 2023 Rapyuta Robotics
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -11,30 +11,109 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import functools
from concurrent.futures import ThreadPoolExecutor
from queue import Queue

import click
from click_spinner import spinner
from rapyuta_io.clients.package import Package
from yaspin.api import Yaspin

from riocli.config import new_client
from riocli.package.util import name_to_guid
from riocli.constants import Symbols, Colors
from riocli.package.util import fetch_packages, print_packages_for_confirmation
from riocli.utils import tabulate_data
from riocli.utils.spinner import with_spinner


@click.command('delete')
@click.option('--force', '-f', is_flag=True, default=False, help='Skip confirmation')
@click.option('--all', 'delete_all', is_flag=True, default=False, help='Deletes all packages in the project')
@click.option('--version', 'package_version', type=str,
help='Semantic version of the Package, only used when name is used instead of GUID')
@click.argument('package-name', type=str)
@name_to_guid
def delete_package(force: bool, package_name: str, package_guid: str) -> None:
@click.option('--workers', '-w',
help="Number of parallel workers while running deleting packages. Defaults to 10",
type=int, default=10)
@click.argument('package-name-or-regex', type=str, default="")
@with_spinner(text="Deleting package(s)...")
def delete_package(
package_name_or_regex: str,
package_version: str,
force: bool = False,
delete_all: bool = False,
workers: int = 10,
spinner: Yaspin = None,
) -> None:
"""
Delete the package from the Platform
"""
client = new_client()

if not (package_name_or_regex or delete_all):
spinner.text = "Nothing to delete"
spinner.green.ok(Symbols.SUCCESS)
return

try:
packages = fetch_packages(client, package_name_or_regex, delete_all, package_version)
except Exception as e:
spinner.text = click.style(
'Failed to find package(s): {}'.format(e), Colors.RED)
spinner.red.fail(Symbols.ERROR)
raise SystemExit(1) from e

if not packages:
spinner.text = "Nothing to delete"
spinner.green.ok(Symbols.SUCCESS)
return

with spinner.hidden():
print_packages_for_confirmation(packages)

spinner.write('')

if not force:
click.confirm('Deleting {} ({}) package'.format(package_name, package_guid), abort=True)
with spinner.hidden():
click.confirm('Do you want to delete the above package(s)?', default=True, abort=True)

try:
client = new_client()
with spinner():
client.delete_package(package_guid)
click.secho('Package deleted successfully!', fg='green')
result = Queue()
func = functools.partial(_apply_delete, result)
with ThreadPoolExecutor(max_workers=workers) as executor:
executor.map(func, packages)

result = sorted(list(result.queue), key=lambda x: x[0])
data, statuses = [], []
for name, status in result:
fg = Colors.GREEN if status else Colors.RED
icon = Symbols.SUCCESS if status else Symbols.ERROR
statuses.append(status)
data.append([
click.style(name, fg),
click.style(icon, fg)
])

with spinner.hidden():
tabulate_data(data, headers=['Name', 'Status'])

icon = Symbols.SUCCESS if all(statuses) else Symbols.WARNING
fg = Colors.GREEN if all(statuses) else Colors.YELLOW
text = "successfully" if all(statuses) else "partially"

spinner.text = click.style(
'Package(s) deleted {}.'.format(text), fg)
spinner.ok(click.style(icon, fg))
except Exception as e:
click.secho(str(e), fg='red')
raise SystemExit(1)
spinner.text = click.style(
'Failed to delete package(s): {}'.format(e), Colors.RED)
spinner.red.fail(Symbols.ERROR)
raise SystemExit(1) from e


def _apply_delete(result: Queue, package: Package) -> None:
name_version = "{}@{}".format(package.packageName, package.packageVersion)
try:
package.delete()
result.put((name_version, True))
except Exception:
result.put((name_version, False))
33 changes: 32 additions & 1 deletion riocli/package/util.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2021 Rapyuta Robotics
# Copyright 2023 Rapyuta Robotics
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -12,12 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import functools
import re
import typing
from typing import List

import click
from rapyuta_io import Client
from rapyuta_io.clients.package import Package

from riocli.config import new_client
from riocli.utils import tabulate_data
from riocli.utils.selector import show_selection


Expand Down Expand Up @@ -71,3 +75,30 @@ def find_package_guid(client: Client, name: str, version: str = None) -> str:

choice = show_selection(options, header='Following packages were found with the same name')
return choice


def fetch_packages(
client: Client,
package_name_or_regex: str,
include_all: bool,
version: str = None
) -> List[Package]:
packages = client.get_all_packages(version=version)

result = []
for pkg in packages:
# We cannot delete public packages. Skip them instead.
if 'io-public' in pkg.packageId:
continue

if include_all or re.search(package_name_or_regex, pkg.packageName):
result.append(pkg)

return result


def print_packages_for_confirmation(packages: List[Package]) -> None:
headers = ['Name', 'Version']
data = [[p.packageName, p.packageVersion] for p in packages]

tabulate_data(data, headers)

0 comments on commit 67c0b58

Please sign in to comment.