Add --from-snapshot flag to create droplets from snapshots#53
Add --from-snapshot flag to create droplets from snapshots#53gwpl wants to merge 2 commits intotrailofbits:mainfrom
Conversation
Support creating droplets from existing snapshots without cloud-init. This enables the "golden image" workflow: configure one droplet, snapshot it, then spin up N identical clones. When --from-snapshot <id> is used: - Uses create_droplet_from_snapshot() API (no user_data sent) - Skips cloud-init rendering and completion monitoring - Still performs: wait for active, SSH config, project assignment, optional Tailscale setup Cloud-init is skipped because the current template is not idempotent — re-running it on a snapshot causes user creation failures, .zshrc overwrites, and an unconditional reboot. See issue trailofbits#52 for discussion of alternative approaches. (for Github WebUI issue linking: Closes trailofbits#52 ) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ret2libc
left a comment
There was a problem hiding this comment.
Is there any way to consolidate this with the existing "wake" code that does something very similar?
|
Always eager, Greg's AI Assistant reporting in! 🧬 Good call — there's definitely overlap between The key differences that kept us from directly reusing
That said, we could absolutely extract the shared plumbing (create-from-snapshot → wait-for-active → SSH-config → optional-Tailscale) into a helper function that both Want us to refactor it that way in this PR, or would you prefer to land the feature first and consolidate in a follow-up? |
|
Greg asked us to go ahead and refactor to consolidate the shared plumbing between |
…code Both `create --from-snapshot` and `wake` perform the same post-creation plumbing: API call, wait-for-active, IP extraction, SSH config, and optional Tailscale setup. Extract this into a shared helper to eliminate ~90 lines of duplication. * New `_create_from_snapshot_and_setup()` near other helper functions * `create --from-snapshot` delegates to helper, then handles project assignment and summary messages * `wake` delegates to helper with tailscale_enabled=False, then handles its own Tailscale re-setup logic (10s sleep, was_tailscale_locked tag) and snapshot deletion prompt No behavioral changes -- both commands produce identical output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Always eager, Greg's AI Assistant back with the refactor! 🔧 TL;DRExtracted What the helper does
Returns What remains command-specific
All 288 tests pass, ruff/ty clean. No behavioral changes. Does this align with what you had in mind @ret2libc? |
Summary
AI Agent with Greg: We wanted to clone a perfectly configured droplet 10 times — like a VM templating system, but with
dropkitergonomics. Turns outdropkit createalways injects cloud-init, which on a snapshot triggers user creation failures,.zshrccarpet-bombing, and an unconditional reboot. Now there's a--from-snapshotflag that knows when to leave well enough alone. 🧬Closes #52
--from-snapshot <snapshot-id>flag todropkit createcreate_droplet_from_snapshot()(nouser_data), skips cloud-init rendering and monitoring--image(clear error if both provided)Usage: clone workflow
Why cloud-init must be skipped
The current cloud-init template is not idempotent — re-running on a snapshot causes:
users:directive fails (user already exists)write_files:overwrites.zshrc(loses customizations)runcmd:ends with unconditionalrebootDigitalOcean assigns a new droplet ID to snapshot-based instances, so cloud-init detects a "first boot" and re-runs everything. The
--from-snapshotflag avoids this by not sendinguser_dataat all.See #52 for discussion of alternative approaches (idempotent template,
--no-cloud-initflag, dedicatedclonecommand).Test plan
uv run ruff check dropkit/main.py— passeduv run ruff format --check dropkit/main.py— passeduv run ty check dropkit/main.py— passeduv run pytest tests/ -v— 288 passed, 31% coverage (above 29% minimum)dropkit create test --from-snapshot <id>— verify droplet created, SSH config added, no cloud-initdropkit create test --from-snapshot <id> --image ubuntu-25-10-x64— verify mutual exclusion errordropkit create test(no flag) — verify normal cloud-init flow unchanged🤖 Generated with Claude Code — your AI that believes in snapshot discipline: clone responsibly, cloud-init sparingly
Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com