From 4b2a6a48ddf4f91217236172e39220e3aab826a1 Mon Sep 17 00:00:00 2001 From: Grzegorz Wierzowiecki Date: Mon, 23 Mar 2026 23:09:32 +0100 Subject: [PATCH 1/2] Auto power-on after resize and add 'nodisk' confirmation option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DigitalOcean leaves droplets powered off after resize, with no API flag to auto-restart. Previously, `dropkit resize` completed silently with the droplet off — users had to discover this and run `dropkit on` manually. Now the resize command: 1. Automatically powers the droplet back on after resize completes, using the same pattern as `dropkit on` (with status polling and progress messages). 2. Offers a "nodisk" answer in the confirmation prompt when disk resize would increase disk size. This supports the common workflow of temporarily scaling up CPU/RAM for heavy builds or benchmarks and scaling back down later — which requires NOT resizing the disk (disk resize is permanent and prevents future downsizing). The prompt changes from: Are you sure? [yes/no] to: Are you sure? [yes/nodisk/no] with a tip explaining the option. The "nodisk" choice only appears when relevant (disk flag is true AND new size has larger disk). Expected terminal experience after resize: ✓ Resize completed successfully Powering on droplet... ✓ Power on action started (ID: 3105287233) Waiting for droplet to power on... ✓ Droplet powered on successfully Droplet claude-code-box has been resized to s-2vcpu-4gb and is now active Co-Authored-By: Claude Opus 4.6 (1M context) --- dropkit/main.py | 67 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 8 deletions(-) diff --git a/dropkit/main.py b/dropkit/main.py index 1eeed7d..93a6a74 100644 --- a/dropkit/main.py +++ b/dropkit/main.py @@ -3483,15 +3483,38 @@ def resize( "[dim]Note: Disk will NOT be resized. You can resize it later, but it's permanent.[/dim]" ) - # Confirmation + # Confirmation — offer "nodisk" escape hatch when disk resize is + # planned and disk size would increase. This supports the common + # use case of temporarily scaling up CPU/RAM (e.g. for a heavy + # build or benchmark) and scaling back down later, which requires + # NOT resizing the disk (disk resize is permanent/irreversible and + # prevents future downsizing). console.print() - confirm = Prompt.ask( - "[yellow]Are you sure you want to resize this droplet?[/yellow]", - choices=["yes", "no"], - default="no", - ) + show_nodisk_option = disk and isinstance(disk_diff, int) and disk_diff > 0 - if confirm != "yes": + if show_nodisk_option: + console.print( + "[dim]Tip: Answer 'nodisk' to skip disk resize " + "(keeps resize reversible for temporary scale-ups)[/dim]" + ) + confirm = Prompt.ask( + "[yellow]Are you sure you want to resize this droplet?[/yellow]", + choices=["yes", "nodisk", "no"], + default="no", + ) + else: + confirm = Prompt.ask( + "[yellow]Are you sure you want to resize this droplet?[/yellow]", + choices=["yes", "no"], + default="no", + ) + + if confirm == "nodisk": + disk = False + console.print( + "[dim]Disk resize skipped — CPU/RAM only (you can resize back down later)[/dim]" + ) + elif confirm != "yes": console.print("[dim]Cancelled.[/dim]") raise typer.Exit(0) @@ -3517,9 +3540,37 @@ def resize( api.wait_for_action_complete(action_id, timeout=600) # 10 minutes console.print("[green]✓[/green] Resize completed successfully") + + # Power the droplet back on — DigitalOcean leaves it powered off + # after resize (no auto-power-on API flag exists). Since the user + # almost certainly wants their droplet running, we power it on + # automatically instead of leaving them to discover it's off. + console.print() + console.print("[dim]Powering on droplet...[/dim]") + + power_action = api.power_on_droplet(droplet_id) + power_action_id = power_action.get("id") + + if not power_action_id: + console.print( + "[yellow]Warning: Could not get power-on action ID. " + f"You may need to run: dropkit on {droplet_name}[/yellow]" + ) + else: + console.print( + f"[green]✓[/green] Power on action started (ID: [cyan]{power_action_id}[/cyan])" + ) + console.print("[dim]Waiting for droplet to power on...[/dim]") + + with console.status("[cyan]Powering on...[/cyan]"): + api.wait_for_action_complete(power_action_id, timeout=120) + + console.print("[green]✓[/green] Droplet powered on successfully") + console.print() console.print( - f"[bold green]Droplet {droplet_name} has been resized to {new_size_slug}[/bold green]" + f"[bold green]Droplet {droplet_name} has been resized to " + f"{new_size_slug} and is now active[/bold green]" ) except DigitalOceanAPIError as e: From 6a73fd8621a0c1b96f128cfde2437430c41b2448 Mon Sep 17 00:00:00 2001 From: Grzegorz Wierzowiecki Date: Tue, 31 Mar 2026 20:56:26 +0200 Subject: [PATCH 2/2] Make disk resize an interactive option when not specified via flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per reviewer feedback, instead of a "nodisk" escape hatch in the confirmation prompt, make --disk/--no-disk a tri-state (True/False/None). When neither flag is passed, the user is asked interactively — consistent with how region, size, and image are already handled. The interactive question only appears when the new size has a different disk size. It defaults to "no" (skip disk resize) since disk resize is permanent and prevents future downsizing. Flow with no flags: Changes: Disk: 25 GB → 80 GB (+55 GB) Disk resize is PERMANENT and cannot be undone. Skipping disk resize keeps the option to downsize later. Resize disk too? [yes/no] (no): Flow with --no-disk: skips the question, shows "not resized" Flow with --disk: skips the question, proceeds with disk resize Co-Authored-By: Claude Opus 4.6 (1M context) --- dropkit/main.py | 80 ++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/dropkit/main.py b/dropkit/main.py index 93a6a74..f643d7d 100644 --- a/dropkit/main.py +++ b/dropkit/main.py @@ -3263,8 +3263,8 @@ def rename( def resize( droplet_name: str = typer.Argument(..., autocompletion=complete_droplet_or_snapshot_name), size: str | None = typer.Option(None, "--size", "-s", help="New size slug (e.g., s-4vcpu-8gb)"), - disk: bool = typer.Option( - True, "--disk/--no-disk", help="Resize disk (permanent, default: True)" + disk: bool | None = typer.Option( + None, "--disk/--no-disk", help="Resize disk (permanent, asked interactively if omitted)" ), ): """ @@ -3449,12 +3449,13 @@ def resize( else 0 ) disk_change = f"{current_disk} GB → {new_disk} GB" - if disk and disk_diff > 0: + if disk is False: + # User explicitly passed --no-disk + disk_change = f"{current_disk} GB (not resized)" + elif disk_diff > 0: disk_change += f" [green](+{disk_diff} GB)[/green]" - elif disk and disk_diff < 0: + elif disk_diff < 0: disk_change += f" [yellow]({disk_diff} GB)[/yellow]" - elif not disk: - disk_change = f"{current_disk} GB (not resized)" changes_table.add_row("Disk:", disk_change) # Price @@ -3468,53 +3469,52 @@ def resize( console.print(changes_table) + # If --disk/--no-disk was not explicitly passed, ask interactively + # when the new size has a different disk. This surfaces the + # permanent/irreversible nature of disk resize at decision time, + # consistent with how region/size/image are handled interactively. + if disk is None: + if isinstance(disk_diff, int) and disk_diff != 0: + console.print() + console.print( + "[bold yellow]Disk resize is PERMANENT and cannot be undone.[/bold yellow]" + ) + console.print("[dim]Skipping disk resize keeps the option to downsize later.[/dim]") + disk_choice = Prompt.ask( + "[bold]Resize disk too?[/bold]", + choices=["yes", "no"], + default="no", + ) + disk = disk_choice == "yes" + else: + # No disk change — default to True (no-op for disk) + disk = True + # Show warnings console.print() console.print( - "[bold yellow]⚠ WARNING: This operation will cause downtime (droplet will be powered off)[/bold yellow]" + "[bold yellow]⚠ WARNING: This operation will cause downtime " + "(droplet will be powered off)[/bold yellow]" ) - if disk: + if disk and isinstance(disk_diff, int) and disk_diff > 0: console.print( "[bold red]⚠ WARNING: Disk resize is PERMANENT and cannot be undone![/bold red]" ) - else: + elif not disk: console.print( - "[dim]Note: Disk will NOT be resized. You can resize it later, but it's permanent.[/dim]" + "[dim]Note: Disk will NOT be resized. You can resize back down later.[/dim]" ) - # Confirmation — offer "nodisk" escape hatch when disk resize is - # planned and disk size would increase. This supports the common - # use case of temporarily scaling up CPU/RAM (e.g. for a heavy - # build or benchmark) and scaling back down later, which requires - # NOT resizing the disk (disk resize is permanent/irreversible and - # prevents future downsizing). + # Confirmation console.print() - show_nodisk_option = disk and isinstance(disk_diff, int) and disk_diff > 0 - - if show_nodisk_option: - console.print( - "[dim]Tip: Answer 'nodisk' to skip disk resize " - "(keeps resize reversible for temporary scale-ups)[/dim]" - ) - confirm = Prompt.ask( - "[yellow]Are you sure you want to resize this droplet?[/yellow]", - choices=["yes", "nodisk", "no"], - default="no", - ) - else: - confirm = Prompt.ask( - "[yellow]Are you sure you want to resize this droplet?[/yellow]", - choices=["yes", "no"], - default="no", - ) + confirm = Prompt.ask( + "[yellow]Are you sure you want to resize this droplet?[/yellow]", + choices=["yes", "no"], + default="no", + ) - if confirm == "nodisk": - disk = False - console.print( - "[dim]Disk resize skipped — CPU/RAM only (you can resize back down later)[/dim]" - ) - elif confirm != "yes": + if confirm != "yes": console.print("[dim]Cancelled.[/dim]") raise typer.Exit(0)