|
10 | 10 | import socket |
11 | 11 | from dataclasses import dataclass, field |
12 | 12 | from pathlib import Path |
| 13 | +from shutil import rmtree |
13 | 14 | from textwrap import wrap |
14 | 15 |
|
15 | 16 | import click |
|
25 | 26 | Config, |
26 | 27 | Profile, |
27 | 28 | ) |
28 | | -from .util import get_docker_client, get_latest_version, spinner, webbrowser_available |
| 29 | +from .util import ( |
| 30 | + confirm_with_value, |
| 31 | + get_docker_client, |
| 32 | + get_latest_version, |
| 33 | + spinner, |
| 34 | + webbrowser_available, |
| 35 | +) |
29 | 36 | from .version import __version__ |
30 | 37 |
|
31 | 38 | MSG_MOUNT_POINT_CONFLICT = """Warning: There is at least one other running |
@@ -638,5 +645,62 @@ def exec(ctx, profile, cmd, privileged, forward_exit_code, wait): |
638 | 645 | ctx.fail(f"Command failed with exit code: {result['ExitCode']}") |
639 | 646 |
|
640 | 647 |
|
| 648 | +@cli.command() |
| 649 | +@click.option( |
| 650 | + "--apps", |
| 651 | + is_flag=True, |
| 652 | + help="Only remove installed apps. The default apps will be installed on the next instance start.", |
| 653 | +) |
| 654 | +@click.option("--yes", is_flag=True, help="Do not ask for confirmation.") |
| 655 | +@with_profile |
| 656 | +@pass_app_state |
| 657 | +def reset(app_state, profile, apps, yes): |
| 658 | + """Reset an AiiDAlab instance.""" |
| 659 | + # Check (and abort) in case that the instance is running. |
| 660 | + instance = AiidaLabInstance(client=app_state.docker_client, profile=profile) |
| 661 | + status = asyncio.run(instance.status()) |
| 662 | + if status not in ( |
| 663 | + instance.AiidaLabInstanceStatus.DOWN, |
| 664 | + instance.AiidaLabInstanceStatus.CREATED, |
| 665 | + instance.AiidaLabInstanceStatus.EXITED, |
| 666 | + ): |
| 667 | + raise click.ClickException( |
| 668 | + f"The instance associated with profile '{profile.name}' " |
| 669 | + "is still running. Please stop it prior to reset." |
| 670 | + ) |
| 671 | + |
| 672 | + click.secho( |
| 673 | + f"Resetting instance for profile '{profile.name}'. This action cannot be undone!", |
| 674 | + err=True, |
| 675 | + fg="red", |
| 676 | + ) |
| 677 | + |
| 678 | + if not yes: |
| 679 | + confirm_with_value( |
| 680 | + profile.name, "Please enter the name of the profile to continue", abort=True |
| 681 | + ) |
| 682 | + |
| 683 | + def rmtree_(path: Path) -> None: |
| 684 | + if path.exists(): |
| 685 | + try: |
| 686 | + rmtree(path) |
| 687 | + except Exception as error: |
| 688 | + raise click.ClickException( |
| 689 | + f"Encountered error while trying to remove '{path}': {error}" |
| 690 | + ) |
| 691 | + |
| 692 | + if apps: |
| 693 | + click.echo( |
| 694 | + "Removing apps directory. Default apps will be installed on next start." |
| 695 | + ) |
| 696 | + rmtree_(Path(profile.home_dir) / "apps") |
| 697 | + else: |
| 698 | + click.echo("Removing container and associated volumes.") |
| 699 | + instance.remove() |
| 700 | + |
| 701 | + click.echo(f"Removing home directory '{profile.home_mount}'.") |
| 702 | + rmtree_(Path(profile.home_mount)) |
| 703 | + |
| 704 | + |
641 | 705 | if __name__ == "__main__": |
642 | 706 | cli() |
0 commit comments