Auto power-on after resize and interactive disk resize option (#50)

* Auto power-on after resize and add 'nodisk' confirmation option

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) <noreply@anthropic.com>

* Make disk resize an interactive option when not specified via flag

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) <noreply@anthropic.com>

* Move interactive disk question before table display and drop isinstance guards

Address ret2libc's review feedback:
- Ask the interactive disk question BEFORE building the changes table,
  so the displayed disk row reflects the user's actual choice
- Remove unnecessary isinstance(disk_diff, int) guards since disk_diff
  is always int (computed with an isinstance ternary that defaults to 0)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Grzegorz Wierzowiecki
2026-04-13 15:24:25 +02:00
committed by GitHub
parent 9159934207
commit b9dde90cc6
+64 -12
View File
@@ -3291,8 +3291,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)"
),
):
"""
@@ -3493,13 +3493,36 @@ def resize(
if isinstance(new_disk, int) and isinstance(current_disk, int)
else 0
)
# 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.
# Resolved BEFORE building the table so the row reflects the choice.
if disk is None:
if 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
disk_change = f"{current_disk} GB → {new_disk} GB"
if disk and disk_diff > 0:
disk_change += f" [green](+{disk_diff} GB)[/green]"
elif disk and disk_diff < 0:
disk_change += f" [yellow]({disk_diff} GB)[/yellow]"
elif not disk:
if disk is False:
disk_change = f"{current_disk} GB (not resized)"
elif disk_diff > 0:
disk_change += f" [green](+{disk_diff} GB)[/green]"
elif disk_diff < 0:
disk_change += f" [yellow]({disk_diff} GB)[/yellow]"
changes_table.add_row("Disk:", disk_change)
# Price
@@ -3516,16 +3539,17 @@ def resize(
# 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 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
@@ -3562,9 +3586,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: