Repository: deis/deis

Issue:
Implement DigitalOcean provider and flavors
Implement DigitalOcean provider and flavors

Add Deis installation documentation to DigitalOcean's community page
This would give Deis a lot of publicity from the DigitalOcean folks. Their community site is here: https://www.digitalocean.com/community

Not sure what topic it would go under. Maybe a new topic called 'PaaS', or even under 'Ubuntu' as a multi-node cluster? :smiley: 



Discussion:
i want this feature !

Seconded for digital ocean support. Awesome (and cheap) host. Well suited
for things like this which may have heavier disk workloads.
On Aug 7, 2013 11:06 AM, "Andres Vargas" notifications@github.com wrote:

> i want this feature !
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/opdemand/deis/issues/73#issuecomment-22262033
> .

Firstly, I'm renaming this from "Allow external servers" to "Implement Digital Ocean provider and flavors" to better reflect the work that needs to be done.

I've had verbal requests for Digital Ocean on the `Controller` side as well as on the `Node` side -- so I think we know which provider is coming next ;)

We will get to this eventually.  That said, if anyone is looking to become a first-time contributor, this issue is a great candidate.  You'd need to implement 5 straightforward celery tasks which are clearly outlined in our [EC2 Example](https://github.com/opdemand/deis/blob/master/celerytasks/ec2.py).  

Interested in taking this on?  Find us in IRC.

I'm maybe interested, what tasks need to be implemented?
On Aug 7, 2013 2:04 PM, "Gabriel Monroy" notifications@github.com wrote:

> Firstly, I'm renaming this from "Allow external servers" to "Implement
> Digital Ocean provider and flavors" to better reflect the work that needs
> to be done.
> 
> I've had verbal requests for Digital Ocean on the Controller side as well
> as on the Node side -- so I think we know which provider is coming next ;)
> 
> We will get to this eventually. That said, if anyone is looking to become
> a first-time contributor, this issue is a great candidate. You'd need to
> implement 5 straightforward celery tasks which are clearly outlined in our EC2
> Example https://github.com/opdemand/deis/blob/master/celerytasks/ec2.py.
> 
> Interested in taking this on? Find us in IRC.
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/opdemand/deis/issues/73#issuecomment-22275234
> .

Brian,

You can start by creating a new module under the `celerytasks` package called `digitalocean.py`, and a `digitalocean.py` under `celerytasks.tests`.  Then implement the tests and code for:
- `digitalocean.build_layer` - to create any shared infrastructure needed by the layer’s nodes
- `digitalocean.destroy_layer` - to destroy any shared infrastructure
- `digitalocean.launch_node` - to provision a node and register it with the Chef Server
- `digitalocean.terminate_node` - to destroy a node a remove its records from the Chef Server
- `digitalocean.converge_node` - to force converge a node (using SSH or other means)

You can copy the EC2 code and go from there.  Chef API calls and SSH logic should be almost identical.  Also it may turn out that `build_layer` and `destroy_layer` don't do anything -- not sure if there's "security group" equivalents, and that sort of thing on Digital Ocean.

Let me know if you have any other questions.

Budding in on this, DigitalOcean does not do security groups. You have to
do that with iptables.

On Wed, Aug 7, 2013 at 5:50 PM, Gabriel Monroy notifications@github.comwrote:

> Brian,
> 
> You can start by creating a new module under the celerytasks package
> called digitalocean.py, and a digitalocean.py under celerytasks.tests.
> Then implement the tests and code for:
> - digitalocean.build_layer - to create any shared infrastructure
>   needed by the layer’s nodes
> - digitalocean.destroy_layer - to destroy any shared infrastructure
> - digitalocean.launch_node - to provision a node and register it with
>   the Chef Server
> - digitalocean.terminate_node - to destroy a node a remove its records
>   from the Chef Server
> - digitalocean.converge_node - to force converge a node (using SSH or
>   other means)
> 
> You can copy the EC2 code and go from there. Chef API calls and SSH logic
> should be almost identical. Also it may turn out that build_layer and
> destroy_layer don't do anything -- not sure if there's "security group"
> equivalents, and that sort of thing on Digital Ocean.
> 
> Let me know if you have any other questions.
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/opdemand/deis/issues/73#issuecomment-22287076
> .

## 

If you can see this, this email was sent from Ian Carroll's personal
account.
This email is not signed. The email must be signed by Comodo to the name of
Ian Carroll.
You are responsible for any damages if you transact under this unsigned
email and I cannot sign it for you.

Just an update for anyone else who wants to take this: I'm not going to be able to do it in a reasonable timeframe, though I thought I was. Sorry!

DigitalOcean Support would be great!

+1 for the DigitalOcean support :-)

You can assign this ticket to me if you wish. I'm working on an implementation of this right now at https://github.com/bacongobbler/deis/tree/73-digitalocean-provider

How would I ensure that <code>creds</code> would hold <code>creds['client_id']</code> and <code>creds['api_key']</code> for communicating with the Digital Ocean API? Is there somewhere that these credentials are built?

I'm thinking of putting the iptables restrictions in <code>launch_node()</code>, after the droplet has been created and we can successfully SSH into the droplet. Thoughts/opinions on this? Are there other providers that work with iptables so that I can get a sense of how this was done previously?

Many thanks!

Digital Ocean droplets do not have cloud-init pre-installed, which seems to be a requirement for deis to run. Are there any other packages that are absolutely mandatory for deis that are not already listed in [contrib/prepare-ubuntu-ami.sh](https://github.com/opdemand/deis/blob/master/contrib/prepare-ubuntu-ami.sh)?

There's no way to create something like an AMI for Digital Ocean, so the build process will have to be done during launch.

EDIT: however, we could consider building a snapshot that has all of the dependencies installed, and use that as our pseudo-AMI for all other builds.

@bacongobbler as alternative something can be build using [Packer](http://www.packer.io/docs/builders/digitalocean.html)

+1 for DO and Linode would be nice. 

And hopefully CoreOS in the future : )

@bacongobbler, that's awesome! I had just started exploring DigitalOcean support in Deis, but you've made some progress. Let's consider your fork the official Deis DO effort. If there is any way we can help, just reference @mboersma or @gabrtv here, or find us on IRC freenode.net #deis, or just email me at matt@opdemand.com.

(OpDemand is in Boulder, CO, and frankly we're all in disaster recovery mode to different extents. I am the one who still has internet and only a small amount of flooding in his basement, so please forgive us if we're unresponsive for a few days.)

I am also trying to finish a CLI-driven acceptance test that would basically "certify" a backend as working successfully with Deis. I'll let you know when that is complete.

@gabrtv and I both just joined DigitalOcean recently, and it's an impressive offering. Cheap and fast and polished. I think a Heroku-like workflow on such affordable hardware could really rock.

@luislavena, Packer does look really nice for an operation like this, but I wouldn't want to add another binary dependency that we wouldn't want to support. <code>celerytasks.util.exec_ssh</code> looks very appealing to me at the moment.

@cpursley, I think you're mixing up 'providers' and 'supported Operating Systems'. :wink: I'm not completely following CoreOS development, but isn't the root filesystem read-only? Deis would have to be running in a Docker container to be able to be supported on CoreOS. If we can Dockerize the Deis controller, then that would be one step forward in the right direction.

@mboersma Thank you for the kind words! I look forward to contributing more in the future. Hope all is well with you guys in Colorado. :smiley:

@bacongobbler  Packer does look really nice for an operation like this, but I wouldn't want to add another binary dependency that we wouldn't want to support.

In the case of Amazon, you can share AMI with others, but in the case of DO, you will need to make an image of the first server, and that will require clear instructions.

What I was thinking with my comment, similar to all the `contrib` scripts was to have a Packer template (not as dependency) for you to be able to generate an image in DO and be able to reuse instead of performing ssh operations on each server.

:+1:

@luislavena you're not the first to mention Packer for our base image optimization.

As a first step, I suggest we try and Packerize the EC2 AMI (for which the scripts already exist) to help give us a feel for the workflow and benefits.

If you (or anyone else) is interested in taking this on, let's open up a new issue for it. :)

+1 for DO support. I use them for all my personal stuff, and having a heroku-like deploy process on there would be fantastic!

Here's what I have so far:

For the time being as discussed on IRC, users will have to manually create a droplet and then run an install script. This script will install the dependencies from the EC2 AMI script as well as install cloud-init, which is missing from the digital ocean droplets. After that, the user will have to save the droplet as a snapshot with a specific name (say, 'deis-base'). Once that is done, people can run scripts like <code>contrib/digitalocean/provision-digitalocean-controller.sh</code> which will run the chef installation on top of the snapshot.

I have most of this already down but not committed yet, but there are things that need to be addressed before this can happen:
- [knife-digital_ocean](https://github.com/rmoriz/knife-digital_ocean) does not have support for chef installations from snapshots (will have to make a PR for this)
- need to upload the user's SSH key to their DO account (can be done using [dop](https://github.com/ahmontero/dop) or via a cURL request)

Does this sound good as a use case for now until we have a better understanding of standardizing our base image?

@bacongobbler Too bad re: knife-do plugin, though I'm sure others would benefit from that PR.

Re: SSH keys..

For bootstrapping the controller we probably do need to upload an SSH key to DO.  However for formation nodes, SSH keys are added dynamically to cloud-init config as we bootstrap the node:
https://github.com/opdemand/deis/blob/master/api/models.py#L636

This dynamic SSH key is how the controller triggers node convergence (the initial SSH key is only used for accessing the controller).  In order for node convergence to work, we need cloud-init to run on instance boot using an EC2 user-data equivalent.  Does that exist on DO?

Otherwise this sounds like a great start.  We can streamline the workflow (i.e. Packer) once we get what you've proposed limping along.

@bacongobbler Do you have code written for knife-do's support? If not I'd be willing to write the code and submit a PR.

@wingrunr21 see rmoriz/knife-digital_ocean#23

@bacongobbler Does specifying --image when creating the node along with --bootstrap not work? I'm not seeing anything glaring right now that would prevent it from doing so.

I do remember Digital Ocean had a problem when restoring snapshots they deleted the SSH host keys and did not regenerate them during new droplet creation. You had to login to the droplet console and regen the host keys to get sshd to start again.

> Does specifying --image when creating the node along with --bootstrap not work?

Actually, that would work. I completely forgot that 'knife digital_ocean image list' lists the IDs of snapshots, backups et al, as compared to 'knife digital_ocean image list --global'.

This command seems to get the image ID quite easily...

```
knife digital_ocean image list | grep 'deis-base' | awk '{print $1}'
```

Thanks!

>  In order for node convergence to work, we need cloud-init to run on instance boot using an EC2 user-data equivalent. Does that exist on DO?

I don't see an equivalent from asking on IRC. I'll create a support ticket and see what they have to say about that.

EDIT: according to one of the community pages, the CEO of DO says that they won't support uploading metadata via the API any time soon, and for users to use puppet/chef for this. Source: http://digitalocean.uservoice.com/forums/136585-digital-ocean/suggestions/3792705-run-shell-script-on-start-or-install-software-afte

It doesn't look like there's a way we can go around this. There's no cloud-init support for Digital Ocean. We would have to switch gears to a complete knife-based installation for each node in order to support Digital Ocean,  or do a lot of hacking to get around this, which would not be that ideal.

@bacongobbler I'm leaning toward removing the `cloud-init` requirement and switching to a pure `knife bootstrap`  node installation.  This has the added benefit of allowing users to manually import nodes into a formation (see referenced issues above).

I'm working on some of the provisioning internals in another branch, so I'll take the first crack at purging cloud-init.  In the meantime, feel free to forge ahead with a `knife` based implementation with the assumption that the layer's SSH keys will be distributed properly via Chef.

Could someone please follow the instructions for https://github.com/bacongobbler/deis/tree/73-digitalocean-provider/contrib/digitalocean for that branch? I seem to be getting this error every time... Anyone have any ideas?

```
$ ./contrib/digitalocean/provision-digitalocean-controller.sh 
Provisioning a deis controller on Digital Ocean!
Creating new SSH key: deis-controller
+ ssh-keygen -f /home/bacongobbler/.ssh/deis-controller -t rsa -N '' -C deis-controller
++ cat /home/bacongobbler/.ssh/deis-controller.pub
+ python -c 'import requests;requests.get('\''https://api.digitalocean.com/ssh_keys/new/?name=deis-controller&ssh_pub_key="ssh-rsa (some ssh key) deis-controller"&client_id=xxxxxxxxxxx&api_key=xxxxxxxxxxx'\'')'
+ set +x
Saved to /home/bacongobbler/.ssh/deis-controller
Data bag deis-build already exists
Data bag deis-formations already exists
Updated data_bag_item[deis-build::gitosis]
Provisioning deis-controller with knife digital_ocean...
+ knife digital_ocean droplet create --server-name deis-controller --image 789293 --location 3 --size 63 --ssh-keys 36992 --identity-file ssh_key_path=/home/bacongobbler/.ssh/deis-controller --bootstrap --run-list 'recipe[deis::controller]'
Droplet creation for deis-controller started. Droplet-ID is 460420
Waiting for IPv4-Address.done
IPv4 address is: 198.199.113.13
Waiting for sshd:done
Bootstrapping Chef on 198.199.113.13
Failed to authenticate root - trying password auth
Enter your password: EOF
```

I ended up at the same point:

``` console
Waiting for IPv4-Address.done
IPv4 address is: 203.0.113.33
Waiting for sshd:done
Bootstrapping Chef on 203.0.113.33
Failed to authenticate root - trying password auth
Enter your password: 
```

Not sure what's going on exactly. I'll keep poking around.

While I'm at it, here are all the notes I made while going through your instructions. You are probably well aware of these things, but just for completeness:

I had to install AUFS support for 13.04 as Docker mentions:

``` console
sudo apt-get update
sudo apt-get install linux-image-extra-`uname -r`
```

If the `prepare-digitalocean-snapshot.sh` script itself could power off the droplet and create the snapshot, that would simplify things a lot. I had read the directions but of course forgot to do that.

When I ran the provision script without a `deis-base` snapshot, the error was unhelpful: "ERROR: Location cannot be empty: -L <region>" Maybe check that **$image_id** got set to something before proceeding?

The provision script created an SSH key and data bags before it gave me an error (that I didn't have a `deis-base` image).

I created my `deis-base` snapshot in my default NYC1 region, but the script assumes SFO. We should parameterize this and/or find a way to propagate `deis-base` to all regions.

> When I ran the provision script without a |deis-base| snapshot, the error was unhelpful: "ERROR: Location cannot be empty: -L"

Good point. If $image_id is never set, --image will take '--location' as the ID to look for. I can add a check for that.

> I had to install AUFS support for 13.04 as Docker mentions

Forgot that point. I assumed 13.04 had AUFS support. I'll add that in.

> If the |prepare-digitalocean-snapshot.sh| script itself could power off the droplet and create the snapshot, that would simplify things a lot.

+1. I'll look to add this in after fixing that SSH error :)

> I created my |deis-base| snapshot in my default NYC1 region, but the script assumes SFO. We should parameterize this and/or find a way to propagate |deis-base| to all regions.

I tried parameterizing this, but BASH didn't exactly enjoy "San 
Francisco 1" as input. Maybe a key-value mapping to these locations in 
the script?

On 13-09-16 09:29 AM, Matt Boersma wrote:

> I ended up at the same point:
> 
> Waiting for IPv4-Address.done
> IPv4 address is: 192.81.217.237
> Waiting for sshd:done
> Bootstrapping Chef on 192.81.217.237
> Failed to authenticate root - trying password auth
> Enter your password:
> 
> Not sure what's going on exactly. I'll keep poking around.
> 
> While I'm at it, here are all the notes I made while going through 
> your instructions. You are probably well aware of these things, but 
> just for completeness:
> 
> I had to install AUFS support for 13.04 as Docker mentions:
> 
> sudo apt-get update
> sudo apt-get install linux-image-extra-`uname -r`
> 
> If the |prepare-digitalocean-snapshot.sh| script itself could power 
> off the droplet and create the snapshot, that would simplify things a 
> lot. I had read the directions but of course forgot to do that.
> 
> When I ran the provision script without a |deis-base| snapshot, the 
> error was unhelpful: "ERROR: Location cannot be empty: -L " Maybe 
> check that _$image_id_ got set to something before proceeding?
> 
> The provision script created an SSH key, data bags, and a droplet 
> before it gave me an error (that I didn't have a |deis-base| image).
> 
> I created my |deis-base| snapshot in my default NYC1 region, but the 
> script assumes SFO. We should parameterize this and/or find a way to 
> propagate |deis-base| to all regions.
> 
> —
> Reply to this email directly or view it on GitHub 
> https://github.com/opdemand/deis/issues/73#issuecomment-24523843.

The uploaded SSH key is missing all special characters, which would explain why we can't SSH into the server:

```
# cat authorized_keys 
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUzIzPuxVa9FwKz1WZ1Ygvs8u2xnBe7ChAK8wCalhwbMQG/zETSKuWaZJs6PUq4LhSFsr1Hd/454P07H0HauLif2JT0sJ 7ScoeBdkLJPGtVQXD4wRfjz thWo S7guxzTqceZ /p/QpoNIZGX0LtD5cLnsv7mg4SFgDM1u7hP6leWJKW8Q2SVRzrTcYir3AZrxy/bg4EsGOGCCMto9mX1pCJL2gnEsaY Mt76y0nvDc/6 nSrZy26i6aw YtW3LnFitSieU0zg8VQN80FWRi4qBZvxSxJvj2wcFfar7EQQDMZKj3VGbkUlOAcN/40kBS9Sc 43DmiRGpB DlEgOQ3 deis-controller"
```

So with the new merge, should I be implementing the tasks listed at <code>deis/provider/mock.py</code>?

Yes, more specifically take a look at the new:
https://github.com/opdemand/deis/blob/master/provider/ec2.py

Key setup should be done in `build_layer` if possible.  For EC2, we use the layer's SSH private key material to create an EC2 key pair and then reference that when we launch the EC2 node.  If we can inject DO keys the same way, we can maybe avoid snapshots -- or just use them to optimize boot times.

Provisioning should be done in `build_node` where you need to return `provider_id`, `fqdn` and a `metadata` dict.  Once you return from building the node, `cm.chef.bootstrap_node` will be called, triggering a `knife bootstrap`:
https://github.com/opdemand/deis/blob/master/cm/chef.py#L63

I'll be in IRC tomorrow if you want to chat about specifics.

Well, we don't officially have DigitalOcean support, sooooo...

---

Ian Carroll
Snap Studio Design LLC

On Mon, Sep 16, 2013 at 10:02 PM, Matthew Fisher
notifications@github.comwrote:

> This would give Deis a lot of publicity from the Digital Ocean folks.
> Their community site is here: https://www.digitalocean.com/community
> 
> Not sure what topic it would go under. Maybe a new topic called 'PaaS', or
> even under 'Ubuntu' as a multi-node cluster? [image: :smiley:]
> 
> —
> Reply to this email directly or view it on GitHubhttps://github.com/opdemand/deis/issues/178
> .

Not yet, but I'm working on it! I almost have the controller set up and ready. Just creating this ticket for future reference.


Resolving patch:
diff --git a/api/models.py b/api/models.py
--- a/api/models.py
+++ b/api/models.py
@@ -109,6 +109,7 @@ class Provider(UuidAuditedModel):
         ('mock', 'Mock Reference Provider'),
         ('rackspace', 'Rackspace Open Cloud'),
         ('static', 'Static Node'),
+        ('digitalocean', 'Digital Ocean'),
     )
 
     owner = models.ForeignKey(settings.AUTH_USER_MODEL)
diff --git a/client/deis.py b/client/deis.py
--- a/client/deis.py
+++ b/client/deis.py
@@ -1657,6 +1657,14 @@ def providers_create(self, args):
                 'api_key': os.environ['RACKSPACE_API_KEY'],
                 'identity_type': os.environ.get('CLOUD_ID_TYPE', 'rackspace'),
             }
+        elif type == 'digitalocean':
+            # read creds from envvars
+            for k in ('DIGITALOCEAN_CLIENT_ID', 'DIGITALOCEAN_API_KEY'):
+                if not k in os.environ:
+                    msg = "Missing environment variable: {}".format(k)
+                    raise EnvironmentError(msg)
+            creds = {'client_id': os.environ['DIGITALOCEAN_CLIENT_ID'],
+                     'api_key': os.environ['DIGITALOCEAN_API_KEY']}
         else:
             creds = json.loads(args.get('<creds>'))
         id = args.get('<id>')  # @ReservedAssignment
@@ -1701,7 +1709,7 @@ def providers_discover(self, args):
                 else:
                     raise ResponseError(response)
         else:
-            print('No credentials discovered, did you install the EC2 Command Line tools?')
+            print('No EC2 credentials discovered. Did you install the EC2 Command Line tools?')
         if 'RACKSPACE_API_KEY' in os.environ and 'RACKSPACE_USERNAME' in os.environ:
             print("Found Rackspace credentials: {}".format(os.environ['RACKSPACE_API_KEY']))
             inp = raw_input('Import these credentials? (y/n) : ')
@@ -1721,7 +1729,27 @@ def providers_discover(self, args):
                 else:
                     raise ResponseError(response)
         else:
-            print('No Rackspace credentials discovered')
+            print('No Rackspace credentials discovered.')
+        if 'DIGITALOCEAN_API_KEY' in os.environ and 'DIGITALOCEAN_CLIENT_ID' in os.environ:
+            print("Found Digitalocean credentials: {}".format(os.environ['DIGITALOCEAN_CLIENT_ID']))
+            inp = raw_input('Import these credentials? (y/n) : ')
+            if inp.lower().strip('\n') != 'y':
+                print('Aborting.')
+            else:
+                creds = {'client_id': os.environ['DIGITALOCEAN_CLIENT_ID'],
+                         'api_key': os.environ['DIGITALOCEAN_API_KEY'],
+                }
+                body = {'creds': json.dumps(creds)}
+                sys.stdout.write('Uploading Digitalocean credentials... ')
+                sys.stdout.flush()
+                response = self._dispatch('patch', 'api/providers/digitalocean',
+                                          json.dumps(body))
+                if response.status_code == requests.codes.ok:  # @UndefinedVariable
+                    print('done')
+                else:
+                    raise ResponseError(response)
+        else:
+            print('No Digitalocean credentials discovered.')
 
     def providers_info(self, args):
         """
diff --git a/provider/digitalocean.py b/provider/digitalocean.py
new file mode 100644
--- /dev/null
+++ b/provider/digitalocean.py
@@ -0,0 +1,125 @@
+"""
+Deis cloud provider implementation for Digital Ocean.
+"""
+
+from __future__ import unicode_literals
+
+import json
+import time
+
+from dop import client
+from api.models import Layer
+
+
+REGION_LIST = {
+    'san-francisco-1': 'San Francisco 1',
+    'amsterdam-1': 'Amsterdam 1',
+    'new-york-1': 'New York 1',
+    'new-york-2': 'New York 2'
+}
+
+def seed_flavors():
+    """Seed the database with default Flavors for digital ocean"""
+    flavors = []
+    for r in REGION_LIST.keys():
+        flavors.append({
+            'id': 'digitalocean-{}'.format(r),
+            'provider': 'digitalocean',
+            'params': json.dumps({
+                'region': REGION_LIST[r],
+                'size': '4GB',
+                'image': 'deis-base'
+            })
+        })
+    return flavors
+
+
+def build_layer(layer):
+    conn = _create_digitalocean_connection(layer['creds'])
+    # create a new SSH key
+    name = "deis-{formation}-{id}".format(**layer)
+    # import a new keypair to the Digital Ocean Control Panel
+    params = { 'name': name, 'ssh_pub_key': layer['ssh_public_key'] }
+    conn.request('/ssh_keys/new/', method='GET', params=params)
+    # Digital Ocean images only have the root user created by default
+    l = Layer.objects.get(id=layer['id'])
+    l.ssh_username = 'root'
+    l.save()
+
+
+def destroy_layer(layer):
+    conn = _create_digitalocean_connection(layer['creds'])
+    name = "deis-{formation}-{id}".format(**layer)
+    # retrieve and delete the SSH key created
+    for k in conn.all_ssh_keys():
+        if k.name == name:
+            conn.destroy_ssh_key(k.id)
+
+
+def build_node(node):
+    conn = _create_digitalocean_connection(node['creds'])
+    # get digital ocean config for a new node
+    kwargs = _get_droplet_kwargs(node, conn)
+    # spawn the droplet
+    droplet = conn.create_droplet(**kwargs)
+    # wait until the droplet has become active
+    while conn.show_droplet(droplet.id).status != 'active':
+        time.sleep(1)
+    provider_id = 'digitalocean'
+    # the data in the old droplet is not there, so
+    # we just create a new Droplet object
+    fqdn = str(conn.show_droplet(droplet.id).ip_address)
+    metadata = _get_droplet_metadata(conn.show_droplet(droplet.id))
+    return provider_id, fqdn, metadata
+
+
+def destroy_node(node):
+    params, creds = node['params'], node['creds']
+    conn = _create_digitalocean_connection(creds)
+    name = node['id']
+    for i in conn.images():
+        if i.name == name:
+            conn.destroy_droplet(i.id)
+    return
+
+
+def _get_droplet_kwargs(node, conn):
+    params = node['params']
+    return {
+        'name': node['id'],
+        'size_id': _get_id(conn.sizes(), params.get('size', '4GB')),
+        'image_id': _get_id(conn.images(show_all=False), params.get('image', 'deis-base')),
+        'region_id': _get_id(conn.regions(), params.get('region', 'San Francisco 1')),
+        'ssh_key_ids': [str(_get_id(conn.all_ssh_keys(), "deis-{formation}-{layer}".format(**node)))],
+        'virtio': True
+    }
+
+def _get_droplet_metadata(droplet):
+    return {
+        'id': droplet.id,
+        'name': droplet.name,
+        'size_id': droplet.size_id,
+        'image_id': droplet.image_id,
+        'region_id': droplet.region_id,
+        'event_id': droplet.event_id,
+        'backups_active': droplet.backups_active,
+        'status': droplet.status,
+        'ip_address': droplet.ip_address
+    }
+
+def _create_digitalocean_connection(creds):
+    if not creds:
+        raise EnvironmentError('No credentials provided')
+    return client.Client(creds['client_id'], creds['api_key'])
+
+def _get_id(lst, name):
+    for i in lst:
+        if i.name == name:
+            return i.id
+    return None
+
+def _get_name(lst, name):
+    for i in lst:
+        if i.name == name:
+            return i.name
+    return None


Test patch: