├── requirements.txt ├── setup.py ├── README.md └── makedo ├── __init__.py └── core.py /requirements.txt: -------------------------------------------------------------------------------- 1 | poseidon 2 | click 3 | simplejson 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name="MakeDo", 5 | version="0.1", 6 | py_modules=['makedo'], 7 | install_requires=[ 8 | 'click', 9 | 'poseidon', 10 | 'simplejson', 11 | ], 12 | entry_points=''' 13 | [console_scripts] 14 | makedo=makedo:cli 15 | ''' 16 | ) 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MakeDO 2 | ====== 3 | 4 | A simple command line interface for DigitalOcean based on [poseidon](https://github.com/changhiskhan/poseidon) and [click](http://click.pocoo.org). 5 | 6 | This project was developed under Python 2.7. Python 3.X support is not guaranteed. 7 | 8 | 9 | A work in progress.. 10 | 11 | Install 12 | ------ 13 | Clone the repository, and run one of the following commands in the project folder: 14 | 15 | ``` 16 | pip install . 17 | ``` 18 | 19 | ``` 20 | python setup.py 21 | ``` 22 | 23 | Usage 24 | ------- 25 | Get help and find all available commands: 26 | 27 | ``` 28 | makedo --help 29 | ``` 30 | 31 | Get detailed argument descriptions for a subcommand (e.g. list): 32 | 33 | ``` 34 | makedo list --help 35 | ``` 36 | 37 | Examples 38 | -------- 39 | Make a snapshot named **snap1** and then destory the droplet **test**: 40 | 41 | ``` 42 | makedo destroy --snapshot snap1 test 43 | ``` 44 | -------------------------------------------------------------------------------- /makedo/__init__.py: -------------------------------------------------------------------------------- 1 | import pprint 2 | 3 | import click 4 | 5 | from core import Core 6 | 7 | core = None 8 | 9 | @click.group() 10 | @click.option('--verbose', is_flag=True) 11 | def cli(verbose): 12 | """ 13 | Command line interface for DigitalOcean. \n 14 | Important: Provide the API key in environment variable DIGITALOCEAN_API_KEY 15 | """ 16 | global core 17 | core = Core() 18 | 19 | 20 | @cli.command() 21 | @click.argument('droplet_name') 22 | @click.argument('snapshot_name') 23 | @click.option('--region', default='sfo1', help="The region to create the droplet in") 24 | @click.option('--size', default='1gb', help="The size of the droplet") 25 | @click.option('--ssh-key', help="The name of the ssh-key to bind the droplet with") 26 | def create(droplet_name, snapshot_name, region, size, ssh_key): 27 | """Create a droplet from a snapshot.""" 28 | # TODO: Accept a list of the names of ssh-keys 29 | click.echo("Creating the droplet...") 30 | if ssh_key is not None: 31 | ssh_keys = [ssh_key] 32 | else: 33 | ssh_keys = None 34 | droplet = core.create_droplet_from_snapshot(droplet_name, region, 35 | snapshot_name, ssh_keys=ssh_keys, size=size) 36 | click.echo("Droplet created.") 37 | click.echo("Droplet IP: " + droplet.ip_address) 38 | 39 | 40 | @cli.command() 41 | @click.argument('droplet_name') 42 | @click.option('--snapshot', help="The name of the snapshot " \ 43 | "if you want to make a snapshot before destroying the droplet") 44 | def destroy(droplet_name, snapshot): 45 | """(Optionally) create a snapshot and destroy a droplet""" 46 | if snapshot: 47 | core.snapshot_and_destroy(droplet_name, snapshot) 48 | else: 49 | core.destroy(droplet_name) 50 | 51 | 52 | @click.group() 53 | def remove(): 54 | """Remove snapshots or ssh keys""" 55 | pass 56 | 57 | 58 | @remove.command() 59 | @click.argument('snapshot_name') 60 | def snapshot(snapshot_name): 61 | """Remove a snapshot from the account""" 62 | response = core.remove_snapshot(snapshot_name) 63 | if response is not None: 64 | click.echo("Snapshot removed!") 65 | else: 66 | click.echo("Failed to remove the snapshot!") 67 | 68 | 69 | @click.group() 70 | def list(): 71 | """List resources/configurations available on the DigitalOcean account""" 72 | pass 73 | 74 | 75 | @list.command() 76 | def keys(): 77 | """List ssh keys""" 78 | click.echo(pprint.pformat(core.list_ssh_keys())) 79 | 80 | 81 | @list.command() 82 | def sizes(): 83 | """List usable sizes of droplets""" 84 | click.echo(pprint.pformat(core.list_sizes())) 85 | 86 | 87 | @list.command() 88 | def snapshots(): 89 | """List snapshots/private images""" 90 | click.echo(pprint.pformat(core.list_snapshots())) 91 | 92 | 93 | cli.add_command(list) 94 | cli.add_command(remove) 95 | -------------------------------------------------------------------------------- /makedo/core.py: -------------------------------------------------------------------------------- 1 | import poseidon as po 2 | 3 | class Core: 4 | def __init__(self): 5 | # The key should be stored in DIGITALOCEAN_API_KEY environment variable 6 | self._client = po.client.connect() 7 | 8 | def _snapshot(self, droplet, snapshot_name): 9 | droplet.power_off() # snapshots are only allowed while powered off 10 | # TODO: check if the snapshot name already exists? 11 | droplet.take_snapshot(snapshot_name) 12 | print "Creating snapshot..." 13 | if snapshot_name in [x['name'] for x in droplet.snapshots()]: 14 | return True 15 | else: 16 | raise RuntimeError("Snapshot failed.") 17 | 18 | def destroy(self, droplet_name): 19 | droplet = self._client.droplets.by_name(droplet_name) 20 | return droplet.delete() 21 | 22 | def remove_snapshot(self, snapshot_name): 23 | snapshot = self.find_snapshot(snapshot_name) 24 | while True: 25 | check = raw_input("Are you sure you want to delete snapshot %s?(yes/no)" % snapshot_name) 26 | if check != "yes" and check != "no": 27 | print "Please answer 'yes' or 'no'." 28 | elif check == "yes": 29 | return self._client.images.delete(snapshot['id']) 30 | else: 31 | return False 32 | 33 | 34 | def snapshot_and_destroy(self, droplet_name, snapshot_name): 35 | # Raise KeyError if can't find a droplet by name 36 | droplet = self._client.droplets.by_name(droplet_name) 37 | self._snapshot(droplet, snapshot_name) 38 | return droplet.delete() 39 | 40 | def find_snapshot(self, snapshot_name): 41 | # https://developers.digitalocean.com/#retrieve-an-existing-image-by-slug 42 | # Doesn't get implemented in poseidon 43 | for snapshot in self.list_snapshots(): 44 | if snapshot['name'] == snapshot_name: 45 | return snapshot 46 | raise ValueError("Snapshot not found.") 47 | 48 | def create_droplet_from_snapshot(self, droplet_name, region, 49 | snapshot_name, ssh_keys=None, size="512mb"): 50 | image = self.find_snapshot(snapshot_name)['id'] 51 | if ssh_keys is not None: 52 | keys = [] 53 | for name in ssh_keys: 54 | key = self.find_ssh_key(name) 55 | if key is None: 56 | raise NameError("Cannot find ssh key named " + name) 57 | keys.append(key['id']) 58 | ssh_keys = keys 59 | return self._client.droplets.create(droplet_name, region, size, 60 | image, ssh_keys=ssh_keys) 61 | def list_ssh_keys(self): 62 | return self._client.keys.list() 63 | 64 | def find_ssh_key(self, name): 65 | for key in self.list_ssh_keys(): 66 | if key['name'] == name: 67 | return key 68 | return None 69 | 70 | def list_snapshots(self): 71 | images = self._client.images.list() 72 | snapshots = [] 73 | for image in images: 74 | if not image['public']: 75 | snapshots.append(image) 76 | return snapshots 77 | 78 | def list_sizes(self): 79 | return self._client.sizes.list() 80 | --------------------------------------------------------------------------------