DigitalOcean has been a game-changer for me. Why create another space-hungry VM locally when you can spin up a new machine in 60 seconds on DO? And it gets better: Tugboat. Now, I can manage my droplets from the command line. Since, I typically use (and reuse) droplets like I did local VMs, I often want to set a droplet aside for a while (maybe weeks or months) and return to it later. Fortunately, DO provides a way to put snapshots into cold storage and then retrieve them later. But freezing and thawing droplets wasn’t easy enough. I made a suggestion to DO, but I doubt it’ll be implemented anytime soon (if ever), so I used Tugboat and some Groovy scripts to roll my own.
Here is what I was looking for:
$ # Assume we have a droplet foo
$ tugboat create foo
$ # Imagine you're done working with foo for now
$ freeze foo
$ # Foo is a snapshot & the droplet is destroyed.
$ # ... weeks pass and you have a hankering for foo ...
$ thaw foo
$ # Foo is back, Baby!
While it would be great to have freeze & thaw button on the DO website freeze & thaw parameters for Tugboat, I didn’t have the time to make a pull request for Tugboat… so here are the scripts:
This script will snapshot a droplet, replacing any snapshot of the same name, and destroy the droplet.
#!/usr/bin/env groovy
class TugboatException {
// Our very own little exception is born. It's a buoy!
}
def getImageInfo = {
"tugboat images".execute().text.split("\n").find{
it.startsWith(imageName+" ")
}
}
def waitFor = { imageName, to='appear' -> /* to='appear' or 'disappear' */
attempts = 0
while (true) {
attempts++
imageInfo = "tugboat images".execute().text.split("\n").find{
it.startsWith(imageName+" ")
}
if ((imageInfo && to=='appear') || (!imageInfo && to=='disappear')) break
if (imageInfo || attempts > 20) {
throw new TugboatException("$imageName did not $to within 3 min. Gave up.")
}
sleep(10000) // wait 10 seconds between checks
}
}
def cmd = { description, command ->
print description
response = command.execute().text
println "done."
}
def cli = new CliBuilder(usage:'freeze DROPLET_NAME')
cli.q(longOpt:'quiet', '')
def options = cli.parse(args)
if (!options.arguments() || options.arguments().size != 1) {
cli.usage()
System.exit(0)
}
imageName = options.arguments()[0]
if (! "tugboat droplets".execute().text ==~ /(?ims).*^$imageName\s.*/) {
println "$imageName droplet does not exist"
System.exit(1)
}
imageInfo = getImageInfo()
if (imageInfo) {
imageId = (imageInfo =~ /id:\s*(\d+)/)[0][1]
if (imageId) {
cmd("Destroying old $imageName image...", "tugboat destroy-image -c -i $imageId")
waitFor(imageName, 'disappear')
}
}
cmd('Telling droplet to halt...', "tugboat halt $imageName")
cmd('Waiting for droplet to shut down...', "tugboat wait $imageName -s off")
sleep(3000)
cmd('Taking snapshot of droplet...', "tugboat snapshot $imageName $imageName")
print "Waiting for image to complete..."
waitFor(imageName)
println "done."
cmd("Destroying $imageName droplet...", "tugboat destroy -c $imageName")
This script will restore a frozen droplet and start it up for you.
#!/usr/bin/env groovy
def getImageInfo = {
"tugboat images".execute().text.split("\n").find{
it.startsWith(imageName+" ")
}
}
def cmd = { description, command ->
print description
response = command.execute().text
println "done."
}
def cli = new CliBuilder(usage:'thaw IMAGE_NAME')
cli.q(longOpt:'quiet', '')
def options = cli.parse(args)
if (!options.arguments() || options.arguments().size != 1) {
cli.usage()
System.exit(0)
}
imageName = options.arguments()[0]
if ("tugboat droplets".execute().text ==~ /(?ims).*^$imageName\s.*/) {
println "$imageName droplet already exists"
System.exit(1)
}
imageInfo = getImageInfo()
if (!imageInfo) {
println "Image $imageName not found"
System.exit(2)
}
imageId = (imageInfo =~ /id:\s*(\d+)/)[0][1]
if (!imageId) {
println "Unable to parse $imageName image id"
System.exit(3)
}
print "Thawing image..."
response = "tugboat create $imageName -i $imageId".execute().text
println "done."
print "Waiting for droplet to start..."
response = "tugboat wait $imageName".execute().text
println "done."