diff --git a/.gitignore b/.gitignore index c15c68f..35531c0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ Lib Scripts *.egg-info *.pyc + +venv diff --git a/.travis.yml b/.travis.yml index 5feb263..58b89a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,12 @@ language: python python: - "2.7" - - "3.2" - - "3.3" - "3.4" + - "3.5" + - "3.6" + - "3.7" + - "3.8" - "nightly" install: - "pip install ." -script: "python setup.py develop && python setup.py test" \ No newline at end of file +script: "python setup.py develop && python setup.py test" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3368817..d5a6625 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,10 +17,15 @@ Please send an email to darrel.opry@spry-group.com if you are interested in beco ## Development ``` - pip install setuptools virtualenv pep8 wheel twine +# dependencies +pip install -r requirements.txt +# development dependencies +pip install setuptools virtualenv pep8 wheel twine ``` +When makeing commit messages please follow the [Angular Git Commit Guidelines](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines) + ## Testing Tests spawn and destroy instances labelled 'python-vultr: test' @@ -39,10 +44,10 @@ python setup.py test Releases are tracked by creating a pull request from master to release. Ensure the version has been properly upticked before creating the release candidate pull request. The merged commit should be tagged with the proper version and built and uploaded to pypi. Currently the release process is manual. Once a more mature testing suite in place, it should be automated with TravisCI. ``` +rm -rf dist/ python setup.py sdist python setup.py bdist_wheel -python setup.py sdist upload -r pypi -python setup.py bdist_wheel upload -r pypi +twine upload -r pypi .\dist\vultr* ``` based on: [Sharing Your Labor of Love: PyPI Quick and Dirty](https://hynek.me/articles/sharing-your-labor-of-love-pypi-quick-and-dirty/) diff --git a/LICENSE b/LICENSE index bfa56f5..e7a140e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 devo.ps +Copyright (c) 2018 The Spry Group, LLC. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md new file mode 100644 index 0000000..b1e091b --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# Vultr + +[![build status](https://travis-ci.org/spry-group/python-vultr.svg?branch=master)](https://travis-ci.org/spry-group/python-vultr) + +Vultr provides a client library to the [Vultr.com](http://www.vultr.com/?ref=6989379-3B) API. + +## Usage + +```python +api_key = 'XXXXXXXXX' +vultr = Vultr(api_key) +plans_json = vultr.plans.list() +``` + +## Testing + +From the repo root directory +Performs generic, unauthenticated tests + +```shell +python -m unittest -v tests.test_vultr.UnauthenticatedTests +``` + +Requires the environment variable VULTR_KEY to be set + +```shell +python -m unittest -v tests.test_vultr.AuthenticatedTests +``` + +## Support + +Python Vultr is supported on a volunteer basis. + +* [Open an Issue](https://github.com/spry-group/python-vultr/issues/new) +* [![Gitter.im](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/spry-group/python-vultr) + + +## API + +* def account.info(self, params=None): +* def app.list(self, params=None): +* def backup.list(self, params=None): +* def dns.create_domain(self, domain, ipaddr, params=None): +* def dns.create_record(self, domain, name, _type, data, params=None): +* def dns.delete_domain(self, domain, params=None): +* def dns.delete_record(self, domain, recordid, params=None): +* def dns.list(self, params=None): +* def dns.records(self, domain, params=None): +* def dns.update_record(self, domain, recordid, params=None): +* def iso.list(self, params=None): +* def iso.create_from_url(self, url, params=None) +* def os.list(self, params=None): +* def plans.list(self, params=None): +* def regions.availability(self, dcid, params=None): +* def regions.list(self, params=None): +* def server.ipv4.create(self, subid, params=None): +* def server.ipv4.destroy(self, subid, ipaddr, params=None): +* def server.ipv4.list(self, subid, params=None): +* def server.ipv4.reverse_default(self, subid, ipaddr, params=None): +* def server.ipv4.reverse_set(self, subid, ipaddr, entry, params=None): +* def server.ipv6.list_ipv6(self, subid, params=None): +* def server.ipv6.reverse_delete_ipv6(self, subid, ipaddr, params=None): +* def server.ipv6.reverse_list_ipv6(self, subid, params=None): +* def server.ipv6.reverse_set_ipv6(self, subid, ipaddr, entry, params=None): +* def server.bandwidth(self, subid, params=None): +* def server.create(self, dcid, vpsplanid, osid, params=None): +* def server.destroy(self, subid, params=None): +* def server.get_user_data(self, subid, params=None): +* def server.halt(self, subid, params=None): +* def server.label_set(self, subid, label, params=None): +* def server.list(self, subid=None, params=None): +* def server.neighbors(self, subid, params=None): +* def server.os_change(self, subid, osid, params=None): +* def server.os_change_list(self, subid, params=None): +* def server.reboot(self, subid, params=None): +* def server.reinstall(self, subid, params=None): +* def server.restore_backup(self, subid, backupid, params=None): +* def server.restore_snapshot(self, subid, snapshotid, params=None): +* def server.set_user_data(self, subid, userdata, params=None): +* def server.start(self, subid, params=None): +* def server.upgrade_plan(self, subid, vpsplanid, params=None): +* def server.upgrade_plan_list(self, subid, params=None): +* def snapshot.create(self, subid, params=None): +* def snapshot.destroy(self, snapshotid, params=None): +* def snapshot.list(self, params=None): +* def sshkey.create(self, name, ssh_key, params=None): +* def sshkey.destroy(self, sshkeyid, params=None): +* def sshkey.list(self, params=None): +* def sshkey.update(self, sshkeyid, params=None): +* def startupscript.create(self, name, script, params=None): +* def startupscript.destroy(self, scriptid, params=None): +* def startupscript.list(self, params=None): +* def startupscript.update(self, scriptid, params=None): diff --git a/README.rst b/README.rst deleted file mode 100644 index 43b89ff..0000000 --- a/README.rst +++ /dev/null @@ -1,99 +0,0 @@ -Vultr -===== -.. image:: https://travis-ci.org/spry-group/python-vultr.svg?branch=master - :target: https://travis-ci.org/spry-group/python-vultr - -Vultr provides a client library to the Vultr.com API. - -**Usage** - -.. code:: python - - api_key = 'XXXXXXXXX' - vultr = Vultr(api_key) - plans_json = vultr.plans.list() - - - -**Testing** - - From the repo root directory - Performs generic, unauthenticated tests - -.. code:: shell - - python -m unittest -v tests.test_vultr.UnauthenticatedTests - - -Requires the environment variable VULTR_KEY to be set - -.. code:: shell - - python -m unittest -v tests.test_vultr.AuthenticatedTests - - -**Support** - - -Python Vultr is supported on a volunteer basis. - -* `Open an Issue `_ - -* .. image:: https://badges.gitter.im/Join%20Chat.svg - :target: https://gitter.im/spry-group/python-vultr - - -**API** - - -* def __init__(self, api_key): -* def snapshot.list(self): -* def snapshot.destroy(self, snapshotid): -* def snapshot.create(self, subid): -* def iso.list(self): -* def plans.list(self): -* def regions.list(self): -* def regions.availability(self, dcid): -* def startupscript.list(self): -* def startupscript.destroy(self, scriptid): -* def startupscript.create(self, name, script): -* def startupscript.update(self, scriptid, name, script): -* def dns.list(self): -* def dns.records(self, domain): -* def dns.create_domain(self, domain, serverip): -* def dns.delete_domain(self, domain): -* def dns.delete_record(self, domain, recordid): -* def dns.create_record(self, domain, name, type, data, ttl=None, -* def sshkey.list(self): -* def sshkey.destroy(self, sshkeyid): -* def sshkey.create(self): -* def sshkey.update(self, sshkeyid, name=None, ssh_key=None): -* def backup.list(self): -* def server.list(self, subid): -* def server.bandwidth(self): -* def server.reboot(self): -* def server.halt(self): -* def server.start(self): -* def server.destroy(self): -* def server.reinstall(self): -* def server.restore_snapshot(self, subid, snapshotid): -* def server.restore_backup(self, subid, backupid): -* def server.create(self, dcid, vpsplanid, osid, ipxe_chain_url=None, -* def server.list_ipv4(self, subid): -* def server.reverse_set_ipv4(self): -* def server.reverse_default_ipv4(self, subid, ip): -* def server.list_ipv6(self): -* def server.reverse_list_ipv6(self): -* def server.reverse_set_ipv6(self, subid, ip, entry): -* def server.reverse_delete_ipv6(self, subid, ip): -* def server.label_set(self, subid, label): -* def server.create_ipv4(self, subid, reboot): -* def server.destroy_ipv4(self, subid, ip): -* def server.os_change_list(self): -* def server.os_change(self, subid, osid): -* def server.upgrade_plan_list(self): -* def server.upgrade_plan(self, subid, vpsplanid): -* def app.list(self): -* def account.info(self): -* def os.list(self): -* def request(self, path, params={}, method='GET'): diff --git a/examples/basic_haltRunning.py b/examples/basic_haltRunning.py new file mode 100644 index 0000000..7521992 --- /dev/null +++ b/examples/basic_haltRunning.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +'''Basic app to halt all running servers''' + +import logging +from os import environ +from json import dumps, load +from vultr import Vultr, VultrError + +# Looks for an environment variable named "VULTR_KEY" +API_KEY = environ.get('VULTR_KEY') +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s %(levelname)s [%(funcName)s():%(lineno)d] %(message)s' +) +logging.getLogger("requests").setLevel(logging.WARNING) + +def halt_running(): + '''Halts all running servers''' + vultr = Vultr(API_KEY) + + try: + serverList = vultr.server.list() + #logging.info('Listing servers:\n%s', dumps( + #serverList, indent=2 + #)) + except VultrError as ex: + logging.error('VultrError: %s', ex) + + for serverID in serverList: + if serverList[serverID]['power_status'] == 'running': + logging.info(serverList[serverID]['label'] + " will be gracefully shutdown.") + vultr.server.halt(serverID) + +def main(): + '''Entry point''' + logging.info('Vultr API Client Python Library') + logging.info('URL: https://www.vultr.com/api/') + halt_running() + +main() diff --git a/examples/basic_running.py b/examples/basic_running.py new file mode 100644 index 0000000..b688c28 --- /dev/null +++ b/examples/basic_running.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +'''Basic app to list all running servers''' + +import logging +from os import environ +from json import dumps, load +from vultr import Vultr, VultrError + +# Looks for an environment variable named "VULTR_KEY" +API_KEY = environ.get('VULTR_KEY') +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s %(levelname)s [%(funcName)s():%(lineno)d] %(message)s' +) +logging.getLogger("requests").setLevel(logging.WARNING) + +def servers_running(): + '''Shows running servers''' + vultr = Vultr(API_KEY) + + try: + serverList = vultr.server.list() + #logging.info('Listing servers:\n%s', dumps( + #serverList, indent=2 + #)) + except VultrError as ex: + logging.error('VultrError: %s', ex) + + for serverID in serverList: + if serverList[serverID]['power_status'] == 'running': + logging.info(serverList[serverID]['label'] + " is up and running.") + +def main(): + '''Entry point''' + logging.info('Vultr API Client Python Library') + logging.info('URL: https://www.vultr.com/api/') + servers_running() + +main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4598c01 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +certifi==2018.4.16 +chardet==3.0.4 +idna==2.6 +requests==2.20.0 +urllib3==1.22 diff --git a/setup.cfg b/setup.cfg index d7de2b1..008ce81 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,5 @@ -[wheel] -universal=1 \ No newline at end of file +[bdist_wheel] +universal=1 + +[metadata] +license_file=LICENSE \ No newline at end of file diff --git a/setup.py b/setup.py index dfdb766..4ce4c62 100644 --- a/setup.py +++ b/setup.py @@ -13,12 +13,13 @@ def read(filename): setup( name='vultr', - version='1.0.0rc1', + version='1.0.1', install_requires=[ "requests" ], description='Vultr.com API Client', - long_description=(read('README.rst')), + long_description=(read('README.md')), + long_description_content_type='text/markdown', url='http://github.com/spry-group/python-vultr', author='Darrel O\'Pry', author_email='darrel.opry@spry-group.com', @@ -33,6 +34,8 @@ def read(filename): 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Topic :: Software Development :: Libraries :: Python Modules' ], license=read('LICENSE'), diff --git a/tests/test_vultr.py b/tests/test_vultr.py index f68696b..93a0891 100644 --- a/tests/test_vultr.py +++ b/tests/test_vultr.py @@ -3,6 +3,7 @@ import os from time import sleep import warnings +import pprint from vultr import Vultr, VultrError VULTR_TEST_LABEL = 'python-vultr: test' @@ -48,6 +49,10 @@ def test_dns_list(self): '''List DNS records''' self.vultr.dns.list() + def test_firewall_group_list(self): + '''List firewall groups''' + self.vultr.firewall.group_list() + def test_iso_list(self): '''List ISOs / images''' self.vultr.iso.list() @@ -86,7 +91,7 @@ def test_server_create(self): response = self.vultr.server.create( 1, # DCID (New Jersey, USA) 29, # VPSPLANID (768 MB RAM,15 GB SSD,1.00 TB BW) - 191, # OSID (Ubuntu 15.04 x64) + 216, # OSID (Ubuntu 16.04 i386) { 'label': VULTR_TEST_LABEL } @@ -100,7 +105,6 @@ def test_server_create(self): def test_server_list(self): '''List servers''' AuthenticatedTests.server_list = self.vultr.server.list() - warnings.warn(str(AuthenticatedTests.server_list)) def test_server_list_by_subid(self): '''List server by SUBID''' diff --git a/vultr/v1_firewall.py b/vultr/v1_firewall.py new file mode 100644 index 0000000..2b20c03 --- /dev/null +++ b/vultr/v1_firewall.py @@ -0,0 +1,18 @@ +'''Partial class to handle Vultr Firewall API calls''' +from .utils import VultrBase + + +class VultrFirewall(VultrBase): + '''Handles Vultr Firewall API calls''' + def __init__(self, api_key): + VultrBase.__init__(self, api_key) + + def group_list(self, params=None): + ''' /v1/firewall/group_list + GET - account + List all firewall groups on the current account. + + Link: https://www.vultr.com/api/#firewall_group_list + ''' + params = params if params else dict() + return self.request('/v1/firewall/group_list', params, 'GET') diff --git a/vultr/v1_iso.py b/vultr/v1_iso.py index e77364e..1c5f190 100644 --- a/vultr/v1_iso.py +++ b/vultr/v1_iso.py @@ -1,5 +1,5 @@ '''Partial class to handle Vultr ISO API calls''' -from .utils import VultrBase +from .utils import VultrBase, update_params class VultrISO(VultrBase): @@ -16,3 +16,18 @@ def list(self, params=None): ''' params = params if params else dict() return self.request('/v1/iso/list', params, 'GET') + + def create_from_url(self, url, params=None): + ''' /vi/iso/create_from_url + POST - account + Create a new ISO image on the current account. + The ISO image will be downloaded from a given URL. + Download status can be checked with the v1/iso/list call. + + Link: https://www.vultr.com/api/#iso_create_from_url + ''' + params = update_params(params, { + 'url': url, + }) + return self.request('/v1/iso/create_from_url', params, 'POST') + diff --git a/vultr/v1_reservedip.py b/vultr/v1_reservedip.py new file mode 100644 index 0000000..c869b33 --- /dev/null +++ b/vultr/v1_reservedip.py @@ -0,0 +1,22 @@ +'''Partial class to handle Vultr ReservedIP API calls''' +from .utils import VultrBase, update_params + + +class VultrReservedIP(VultrBase): + '''Handles Vultr ReservedIP API calls''' + def __init__(self, api_key): + VultrBase.__init__(self, api_key) + + def create(self, dcid, ip_type, params=None): + ''' /v1/reservedip/create + POST - account + Create a new reserved IP. Reserved IPs can only be used within the + same datacenter for which they were created. + + Link: https://www.vultr.com/api/#reservedip_create + ''' + params = update_params(params, { + 'DCID': dcid, + 'ip_type': ip_type + }) + return self.request('/v1/reservedip/create', params, 'POST') diff --git a/vultr/vultr.py b/vultr/vultr.py index c7670d4..87d78db 100644 --- a/vultr/vultr.py +++ b/vultr/vultr.py @@ -4,10 +4,12 @@ from .v1_app import VultrApp from .v1_backup import VultrBackup from .v1_dns import VultrDNS +from .v1_firewall import VultrFirewall from .v1_iso import VultrISO from .v1_os import VultrOS from .v1_plans import VultrPlans from .v1_regions import VultrRegions +from .v1_reservedip import VultrReservedIP from .v1_server import VultrServer from .v1_snapshot import VultrSnapshot from .v1_sshkey import VultrSSHKey @@ -25,12 +27,14 @@ def __init__(self, api_key): self.app = VultrApp(api_key) self.backup = VultrBackup(api_key) self.dns = VultrDNS(api_key) + self.firewall = VultrFirewall(api_key) self.iso = VultrISO(api_key) # pylint: disable=invalid-name # OS is the Vultr API namespace name self.os = VultrOS(api_key) self.plans = VultrPlans(api_key) self.regions = VultrRegions(api_key) + self.reservedip = VultrReservedIP(api_key) self.server = VultrServer(api_key) self.snapshot = VultrSnapshot(api_key) self.sshkey = VultrSSHKey(api_key)