diff --git a/.gitignore b/.gitignore index ee4b57c..7fc3fcc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ *.pyc dist build +.coverage +coverage.bash +coverage.xml +webfaction-cli +*.py~ +*/*.py~ +*/*/*.py~ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..891f8fb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +sudo: false +dist: trusty +language: python +addons: + apt: + update: true +virtualenv: + system_site_packages: true +python: + - "2.7" + - "3.4" +install: + - sudo apt-get update + - pip install --upgrade pip setuptools + - pip install --upgrade pip virtualenv + - pip install coverage -U + - pip install pyflakes -U + - pip install nose -U + - pip install tox-travis + - pip install codecov -U + - pip install --upgrade pytest +script: + - pip install . + - pip install -r requirements.txt + - sh run_travis.sh +after_success: + - codecov +notifications: + - email: false diff --git a/README b/README deleted file mode 100644 index 723c4ec..0000000 --- a/README +++ /dev/null @@ -1,7 +0,0 @@ -This is a port of https://pypi.python.org/pypi/webf/ -- the repo exists because - -A. There doesn't seem to be a good source listing anymore (original site is dead) -B. I wanted to modify it to accept passed-in credentials instead of having one config file - -Original post: http://forum.webfaction.com/viewtopic.php?id=1513 [dead] -WebFaction API Docs: https://docs.webfaction.com/xmlrpc-api/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e321ad0 --- /dev/null +++ b/README.md @@ -0,0 +1,114 @@ +Webfaction-Python-API +--------------------- + +[![Build Status](https://travis-ci.org/bieli/Webfaction-Python-API.svg?branch=master)](http://travis-ci.org/bieli/Webfaction-Python-API) + +[![codecov](https://codecov.io/gh/bieli/Webfaction-Python-API/branch/master/graph/badge.svg)](https://codecov.io/gh/bieli/Webfaction-Python-API) + + +Webfaction API client with command line tool for calling API methods. +Python 2 and 3 API is supported. + + +How to run +---------- +1) checkout source +```bash +git clone https://github.com/bieli/Webfaction-Python-API.git +``` + +2) create configuration file ~/.webfrc with your Webfaction's username and password +```bash +cat << 'EOF' > ~/.webfrc +username="username" +password="strong_password" + +EOF + +``` + +2) use command line with webfaction in place (without installing in OS) +```bash +cd Webfaction-Python-API +cp bin/webfaction ./webfaction-cli +chmod +x ./webfaction-cli +./webfaction-cli --system="uname -a" +Linux web616.webfaction.com 3.10.0-693.17.1.el7.x86_64 #1 SMP Thu Jan 25 20:13:58 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux + +``` + +3) enjoy with Webfaction API commands +```bash +./webfaction-cli --help +Usage: webfaction-cli [options] + +Options: + -h, --help show this help message and exit + --version Show this program's version and exit. + --debug Show debugging information + + WebFaction Commands: + Options for calling WebFaction XML-RPC API methods + + --create-app=NAME TYPE AUTOSTART EXTRA_INFO + Create application + --delete-app=NAME Delete application + --create-cronjob=CRONJOB + Create cronjob + --delete-cronjob=CRONJOB + Delete cronjob + --create-db=NAME DB_TYPE PASSWORD + Create Database + --delete-db=NAME DB_TYPE + Delete Database + --create-domain=DOMAIN SUBDOMAIN + Create domain + --create-website=NAME IP HTTPS SUBDOMAINS *SITE_APPS + Create website + --create-dns-override=DOMAIN A_IP CNAME MX_NAME MX_PRIORITY SPF_RECORD + Create DNS override + --delete-dns-override=DOMAIN A_IP CNAME MX_NAME MX_PRIORITY SPF_RECORD + Delete DNS override + --create-email=EMAIL_ADDRESS TARGETS AR_ON AR_SUBJECT AR_MSG AR_FROM + Create email address + --delete-email=EMAIL_ADDRESS + Delete email address + --create-mailbox=MBOX_NAME SPAM_PROT SHARE SPAM_LEARN HAM_LEARN + Create mailbox + --delete-mailbox=MBOX_NAME + Delete mailbox + --list-disk-usage=LIST_DISK_USAGE + List disk usage + --list-bandwidth-usage=LIST_BANDWIDTH_USAGE + List bandwidth usage + --list-machines=LIST_MACHINES + List machines + --set-apache-acl=PATHS PERMS RECURSIVE + Set Apache ACL + --system=CMD Run system command. + --write-file=FILENAME STRING MODE + Write file + +``` + + + +TODO +----- +- (X) support for Pytohn 3 +- (X) implement support for list usage disks and bandwidth +- (X) adding Travis CI build in multiple Python versions +- (X) some unit tests +- (_) verify compatibility for Python 2 and Python 3 + + + +Original README description (from forked repository) +-------------- +This is a port of https://pypi.python.org/pypi/webf/ -- the repo exists because + +A. There doesn't seem to be a good source listing anymore (original site is dead) +B. I wanted to modify it to accept passed-in credentials instead of having one config file + +Original post: http://forum.webfaction.com/viewtopic.php?id=1513 [dead] +WebFaction API Docs: https://docs.webfaction.com/xmlrpc-api/ diff --git a/bin/webfaction b/bin/webfaction index c70a94f..631c88a 100755 --- a/bin/webfaction +++ b/bin/webfaction @@ -1,6 +1,6 @@ -#!/usr/bin/env python -# pylint: disable-msg=W0142 -''' +#!/usr/bin/env python3 +# pylint: disable=W0142,E501 +""" cli.py ====== @@ -8,15 +8,15 @@ cli.py Command-line tool for WebFaction's XML-RPC API http://api.webfaction.com/ Author: Rob Cakebread +Author: Marcin Bielak License : MIT -''' +""" __docformat__ = 'epytext' __revision__ = '$Revision: $'[11:-1].strip() - import sys import logging import optparse @@ -26,7 +26,6 @@ from webfaction import webflib class WebFaction(object): - '''``WebFaction`` class''' def __init__(self): @@ -58,9 +57,9 @@ class WebFaction(object): if self.options.webfaction_version: return webfaction_version() - #Go through list of options in ``group_api`` and execute - #activated callable for enabled option - #Looks a bit magic, but saves a ton of if/then branching + # Go through list of options in ``group_api`` and execute + # activated callable for enabled option + # Looks a bit magic, but saves a ton of if/then branching for action in [p for p in self.group_api.option_list]: action = str(action)[2:].replace('-', '_') if getattr(self.options, action): @@ -84,146 +83,171 @@ class WebFaction(object): usage = 'usage: %prog [options]' opt_parser = optparse.OptionParser(usage=usage) group_api = optparse.OptionGroup(opt_parser, - 'WebFaction Commands', - 'Options for calling WebFaction XML-RPC API methods') + 'WebFaction Commands', + 'Options for calling WebFaction XML-RPC API methods') group_api.add_option('--create-app', - action='store', - dest='create_app', - nargs=4, - help='Create application', - metavar='NAME TYPE AUTOSTART EXTRA_INFO' - ) + action='store', + dest='create_app', + nargs=4, + help='Create application', + metavar='NAME TYPE AUTOSTART EXTRA_INFO' + ) group_api.add_option('--delete-app', - action='store', - dest='delete_app', - help='Delete application', - metavar='NAME' - ) + action='store', + dest='delete_app', + help='Delete application', + metavar='NAME' + ) group_api.add_option('--create-cronjob', - action='store', - dest='create_cronjob', - help='Create cronjob', - metavar='CRONJOB' - ) + action='store', + dest='create_cronjob', + help='Create cronjob', + metavar='CRONJOB' + ) group_api.add_option('--delete-cronjob', - action='store', - dest='delete_cronjob', - help='Delete cronjob', - metavar='CRONJOB' - ) + action='store', + dest='delete_cronjob', + help='Delete cronjob', + metavar='CRONJOB' + ) group_api.add_option('--create-db', - action='store', - dest='create_db', - nargs=3, - help='Create Database', - metavar='NAME DB_TYPE PASSWORD' - ) + action='store', + dest='create_db', + nargs=3, + help='Create Database', + metavar='NAME DB_TYPE PASSWORD' + ) group_api.add_option('--delete-db', - action='store', - dest='delete_db', - nargs=2, - help='Delete Database', - metavar='NAME DB_TYPE' - ) + action='store', + dest='delete_db', + nargs=2, + help='Delete Database', + metavar='NAME DB_TYPE' + ) group_api.add_option('--create-domain', - action='store', - dest='create_domain', - help='Create domain', - nargs=2, - metavar='DOMAIN SUBDOMAIN' - ) + action='store', + dest='create_domain', + help='Create domain', + nargs=2, + metavar='DOMAIN SUBDOMAIN' + ) group_api.add_option('--create-website', - action='store', - dest='create_website', - help='Create website', - nargs=5, - metavar='NAME IP HTTPS SUBDOMAINS *SITE_APPS' - ) + action='store', + dest='create_website', + help='Create website', + nargs=5, + metavar='NAME IP HTTPS SUBDOMAINS *SITE_APPS' + ) group_api.add_option('--create-dns-override', - action='store', - dest='create_dns_override', - help='Create DNS override', - nargs=6, - metavar='DOMAIN A_IP CNAME MX_NAME MX_PRIORITY SPF_RECORD' - ) + action='store', + dest='create_dns_override', + help='Create DNS override', + nargs=6, + metavar='DOMAIN A_IP CNAME MX_NAME MX_PRIORITY SPF_RECORD' + ) group_api.add_option('--delete-dns-override', - action='store', - dest='delete_dns_override', - help='Delete DNS override', - nargs=6, - metavar='DOMAIN A_IP CNAME MX_NAME MX_PRIORITY SPF_RECORD' - ) + action='store', + dest='delete_dns_override', + help='Delete DNS override', + nargs=6, + metavar='DOMAIN A_IP CNAME MX_NAME MX_PRIORITY SPF_RECORD' + ) group_api.add_option('--create-email', - action='store', - dest='create_email', - help='Create email address', - nargs=6, - metavar='EMAIL_ADDRESS TARGETS AR_ON AR_SUBJECT AR_MSG AR_FROM' - ) + action='store', + dest='create_email', + help='Create email address', + nargs=6, + metavar='EMAIL_ADDRESS TARGETS AR_ON AR_SUBJECT AR_MSG AR_FROM' + ) group_api.add_option('--delete-email', - action='store', - dest='delete_email', - help='Delete email address', - metavar='EMAIL_ADDRESS' - ) + action='store', + dest='delete_email', + help='Delete email address', + metavar='EMAIL_ADDRESS' + ) group_api.add_option('--create-mailbox', - action='store', - dest='create_mailbox', - help='Create mailbox', - nargs=5, - metavar='MBOX_NAME SPAM_PROT SHARE SPAM_LEARN HAM_LEARN' - ) + action='store', + dest='create_mailbox', + help='Create mailbox', + nargs=5, + metavar='MBOX_NAME SPAM_PROT SHARE SPAM_LEARN HAM_LEARN' + ) group_api.add_option('--delete-mailbox', - action='store', - dest='delete_mailbox', - help='Delete mailbox', - metavar='MBOX_NAME' - ) + action='store', + dest='delete_mailbox', + help='Delete mailbox', + metavar='MBOX_NAME' + ) + + group_api.add_option('--list-disk-usage', + action='store', + dest='list_disk_usage', + help='List disk usage', + nargs=0, + metavar='' + ) + + group_api.add_option('--list-bandwidth-usage', + action='store', + dest='list_bandwidth_usage', + help='List bandwidth usage', + nargs=0, + metavar='' + ) + + group_api.add_option('--list-machines', + action='store', + dest='list_machines', + help='List machines', + nargs=0, + metavar='' + ) group_api.add_option('--set-apache-acl', - action='store', - dest='set_apache_acl', - nargs=3, - help='Set Apache ACL', - metavar='PATHS PERMS RECURSIVE' - ) + action='store', + dest='set_apache_acl', + nargs=3, + help='Set Apache ACL', + metavar='PATHS PERMS RECURSIVE' + ) group_api.add_option('--system', - action='store', - dest='system', - help='Run system command.', - metavar='CMD' - ) + action='store', + dest='system', + help='Run system command.', + metavar='CMD' + ) group_api.add_option('--write-file', - action='store', - dest='write_file', - help='Write file', - metavar='FILENAME STRING MODE' - ) + action='store', + dest='write_file', + help='Write file', + metavar='FILENAME STRING MODE' + ) self.group_api = group_api opt_parser.add_option_group(group_api) - #Common options + + # Common options opt_parser.add_option('--version', action='store_true', dest= - 'webfaction_version', default=False, help= + 'webfaction_version', default=False, help= "Show this program's version and exit.") opt_parser.add_option('--debug', action='store_true', dest= - 'debug', default=False, help= + 'debug', default=False, help= 'Show debugging information') return opt_parser @@ -231,7 +255,8 @@ class WebFaction(object): def webfaction_version(): '''Print webfaction version''' - print VERSION + print(VERSION) + def main(): '''Let's do it.''' @@ -241,4 +266,3 @@ def main(): if __name__ == '__main__': sys.exit(main()) - diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..2b5037e --- /dev/null +++ b/codecov.yml @@ -0,0 +1,26 @@ +codecov: + notify: + require_ci_to_pass: yes + +coverage: + precision: 2 + round: down + range: "70...100" + + status: + project: yes + patch: yes + changes: no + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "header, diff" + behavior: default + require_changes: no diff --git a/requirements.txt b/requirements.txt index 46809bb..eed614d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,7 @@ -configobj>=5.0.2 \ No newline at end of file +configobj>=5.0.2 +#xmlrpc +#httplib +pyflakes +coverage +nose +virtualenv diff --git a/run_travis.sh b/run_travis.sh new file mode 100644 index 0000000..8a07ba2 --- /dev/null +++ b/run_travis.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +python -m unittest discover ./tests/ "*test.py" +nosetests tests -v --with-coverage +python -m pyflakes . diff --git a/setup.py b/setup.py index 2c39d96..a2c74c4 100644 --- a/setup.py +++ b/setup.py @@ -1,34 +1,37 @@ -#!/usr/bin/env python -from distutils.core import setup, Extension +#!/usr/bin/env python3 +# pylint: disable=E501 + +from distutils.core import setup webfaction_classifiers = [ -"Programming Language :: Python :: 2", -"Programming Language :: Python :: 3", -"Intended Audience :: Developers", -"License :: OSI Approved :: MIT License", -"Topic :: Software Development :: Libraries", -"Topic :: Utilities", -"Topic :: Software Development :: Libraries :: Application Frameworks", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Topic :: Software Development :: Libraries", + "Topic :: Utilities", + "Topic :: Software Development :: Libraries :: Application Frameworks", ] long_description = """Command-line tool and library for the WebFaction API https://docs.webfaction.com/xmlrpc-api/ -Uses a Python XML-RPC instance to communicate with the Webfaction API. Most API commands have been turned into Python commands +Uses a Python XML-RPC instance to communicate with the Webfaction API. +Most API commands have been turned into Python commands. """ setup( name='webfaction', - description='Webfaction Python API', + description='Webfaction Python API (supported Python 2 and 3)', long_description=long_description, - url="https://github.com/tclancy/Webfaction-Python-API", - author="Patrick Robertson", - author_email="webfaction-python@patjack.uk", + url="https://github.com/bieli/Webfaction-Python-API", + author="Marcin Bielak", + author_email="marcin.bieli@gmail.com", license="MIT", packages=['webfaction'], platforms=["all"], - version="1.0", + version="1.1", scripts=['bin/webfaction'], - install_requires=['configobj',], + install_requires=['configobj', ], classifiers=webfaction_classifiers, ) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/webflib_test.py b/tests/webflib_test.py new file mode 100644 index 0000000..388358c --- /dev/null +++ b/tests/webflib_test.py @@ -0,0 +1,47 @@ +import unittest +from unittest.mock import patch, MagicMock, call + +from webfaction.webflib import WebFactionDBUser, WebFactionXmlRpc, API_URL + + +class WebflibTest(unittest.TestCase): + def test_should_store_creadentials_in_dedicated_object(self): + username = "username" + password = "password" + db_type = "mysql" + + unit = WebFactionDBUser(username, password, db_type) + + self.assertEqual(username, unit.username) + self.assertEqual(password, unit.password) + self.assertEqual(db_type, unit.db_type) + + @patch('xmlrpc.client.Server') + def test_should_init_WebFactionXmlRpc_object_with_defined_user_and_pass_and_call_login(self, mock_xmlrpc): + username = "usr1" + password = "pwd1" + server_login_mock = MagicMock() + server_login_mock.login.return_value = (None, username) + mock_xmlrpc.return_value = server_login_mock + + unit = WebFactionXmlRpc(user=username, password=password) + mock_xmlrpc.assert_called_with(API_URL, transport=None) + self.assertEqual(unit.account, username) + + @patch('xmlrpc.client.Server') + def test_should_call_list_app(self, mock_xmlrpc): + username = "usr1" + password = "pwd1" + server_login_mock = MagicMock() + server_login_mock.login.return_value = (None, username) + mock_xmlrpc.return_value = server_login_mock + mock_xmlrpc.list_app.return_value = None + + expected_calls = [ + call('https://api.webfaction.com/', transport=None), + call().login('usr1', 'pwd1'), + call().list_apps(None) + ] + unit = WebFactionXmlRpc(user=username, password=password) + unit.list_apps() + mock_xmlrpc.assert_has_calls(expected_calls, any_order=False) diff --git a/webfaction/__init__.py b/webfaction/__init__.py index ac7fef9..f16c657 100644 --- a/webfaction/__init__.py +++ b/webfaction/__init__.py @@ -1,5 +1,5 @@ - -''' +# pylint: disable=E501 +""" webf ==== @@ -11,12 +11,11 @@ ----------- webfaction is a command-line client and library for webfaction.com's XML-RPC API -''' +""" -__version__ = '1.0' +__version__ = '1.1' __docformat__ = 'epytext' __author__ = 'Tom Clancy @' __contributors__ = 'Rob Cakebread @, Patrick Robertson @' __copyright__ = '(C) 2008 Rob Cakebread' __license__ = 'MIT' - diff --git a/webfaction/webflib.py b/webfaction/webflib.py index ce756b8..7f5b4e6 100644 --- a/webfaction/webflib.py +++ b/webfaction/webflib.py @@ -1,93 +1,102 @@ +# pylint: disable=R0913,W0511,E1103,E501 -# pylint: disable-msg=R0913,W0511,E1103 - -''' +""" webflib ======= WebFaction XML-RPC API library -''' +""" -import xmlrpclib +import sys import logging import os.path -import httplib -from configobj import ConfigObj +try: + from configobj import ConfigObj +except ImportError: + pass + +if sys.version_info[0] > 2: + from xmlrpc import client as xmlrpclib + from http import client as httplib +else: + import xmlrpclib + import httplib -class WebFactionDBUser(object): +class WebFactionDBUser: def __init__(self, username, password, db_type): - super(WebFactionDBUser, self).__init__() self.username = username self.password = password self.db_type = db_type - + + API_URL = 'https://api.webfaction.com/' CONF = os.path.expanduser('~/.webfrc') -class WebFactionXmlRpc(object): - '''WebFaction XML-RPC server proxy class''' + +class WebFactionXmlRpc: + """WebFaction XML-RPC server proxy class""" def __init__(self, user=None, password=None, machine=None): self.log = logging.getLogger("webf") self.session_id = None + self.account = None self.server = None if not (user and password): try: user, password = WebFactionXmlRpc.get_config() except NotImplementedError as e: - self.log.error("You must set a username and password. Either by passing them to __init__ or setting up your config file") + self.log.error("You must set a username and password. " + "Either by passing them to __init__ or setting up your config file") raise e self.username = user self.password = password self.machine = machine self.login() - + @staticmethod def get_config(): - '''Get configuration file from user's directory''' + """Get configuration file from user's directory""" if not os.path.exists(CONF): - err = u"\n".join([ - u" Set your username/password in %s" % CONF, - u" The format is:", - u" username=", - u" password=", - ]) + err = "\n".join([ + " Set your username/password in %s" % CONF, + " The format is:", + " username=", + " password=", + ]) raise NotImplementedError(err) config = ConfigObj(CONF) username = config['username'] password = config['password'] - return (username, password) + return username, password def login(self): - '''Login to WebFaction and get a session_id''' + """Login to WebFaction and get a session_id""" try: http_proxy = os.environ['http_proxy'] except KeyError: http_proxy = None self.server = xmlrpclib.Server(API_URL, transport=http_proxy) extra_args = [self.machine] if self.machine else [] - self.session_id, account = self.server.login(self.username, self.password, *extra_args) - - self.log.debug("self.session_id %s account %s", self.session_id, - account) + self.session_id, self.account = self.server.login(self.username, self.password, *extra_args) + self.log.debug("self.session_id: %s self.account: %s", self.session_id, self.account) def create_app(self, app_name, app_type, autostart, extra_info): - '''Create new application''' + """Create new application""" if extra_info.lower() == 'none': extra_info = '' try: result = self.server.create_app( - self.session_id, - app_name, - app_type, - autostart, - extra_info - ) + self.session_id, + app_name, + app_type, + autostart, + extra_info + ) self.log.debug(result) return True except xmlrpclib.Fault: @@ -98,29 +107,33 @@ def create_app(self, app_name, app_type, autostart, extra_info): return False def delete_app(self, app_name): - '''Create new application''' + """Create new application""" try: result = self.server.delete_app( - self.session_id, - app_name - ) + self.session_id, + app_name + ) self.log.debug(result) except xmlrpclib.Fault: self.log.exception("Error deleting app") return 1 - + def list_apps(self): - '''List all existing webfaction apps + """List all existing webfaction apps https://docs.webfaction.com/xmlrpc-api/apiref.html#method-list_apps - Returns a list of dicts''' + Returns a list of dicts""" try: return self.server.list_apps(self.session_id) except xmlrpclib.Fault: self.log.exception("Problem listing apps") return [] - + + def _validate_db_type(self, db_type): + assert db_type in ["mysql", "postgres"], \ + "Invalid db_type '%s' - try mysql or postgres !" % db_type + def delete_db(self, name, db_type): - ''' + """ Delete database @param name: name of database @@ -128,21 +141,21 @@ def delete_db(self, name, db_type): @param db_type: mysql or postgres @type db_type: string - ''' - #XXX: Validate db_type + """ + self._validate_db_type(db_type) try: result = self.server.delete_db( - self.session_id, - name, - db_type - ) + self.session_id, + name, + db_type + ) self.log.debug(result) except xmlrpclib.Fault: self.log.exception("Error deleting database %s of type %s", name, db_type) return 1 def create_db(self, name, db_type, password): - ''' + """ Create database @param name: name of database @@ -156,17 +169,16 @@ def create_db(self, name, db_type, password): @returns: Nothing @rtype: None on success or 1 on failure - - ''' - #XXX: Validate db_type - #XXX: Use interactive method to get password? + """ + self._validate_db_type(db_type) + # TODO: Use interactive method to get password? try: result = self.server.create_db( - self.session_id, - name, - db_type, - password - ) + self.session_id, + name, + db_type, + password + ) self.log.debug(result) return True except xmlrpclib.Fault: @@ -175,64 +187,65 @@ def create_db(self, name, db_type, password): except httplib.ResponseNotReady: self.log.exception("Response not ready") return False - + def list_dbs(self): try: return self.server.list_dbs(self.session_id) except xmlrpclib.Fault: self.log.exception("Error listing databases") return [] - + def list_db_users(self): try: return self.server.list_db_users(self.session_id) except xmlrpclib.Fault: self.log.exception("Error listing database users") return [] - + def create_db_user(self, username, password, db_type): try: response = self.server.create_db_user(self.session_id, username, password, db_type) self.log.debug(response) return WebFactionDBUser(username, password, db_type) - + except xmlrpclib.Fault: self.log.exception("Error creating database user") return None - - def delete_db_user(self, db_user): + + def _check_db_user_instance(self, db_user): if not isinstance(db_user, WebFactionDBUser): raise ValueError("db_user must be an instance of WebFactionDBUser") - + + def delete_db_user(self, db_user): + self._check_db_user_instance(db_user) try: self.server.delete_db_user( - self.session_id, - db_user.username, - db_user.db_type - ) + self.session_id, + db_user.username, + db_user.db_type + ) return True except xmlrpclib.Fault: self.log.exception("Error deleting database user") return False - + def grant_db_permissions(self, db_user, database): - if not isinstance(db_user, WebFactionDBUser): - raise ValueError("db_user must be an instance of WebFactionDBUser") - + self._check_db_user_instance(db_user) + try: self.server.grant_db_permissions( - self.session_id, - db_user.username, - database, - db_user.db_type - ) + self.session_id, + db_user.username, + database, + db_user.db_type + ) return True except xmlrpclib.Fault: self.log.exception("Error granting database permissions") return False - + def create_cronjob(self, line): - ''' + """ Create a cronjob @param line: A line you want in your cronjob @@ -240,20 +253,19 @@ def create_cronjob(self, line): @returns: Nothing @rtype: None on success or 1 on failure - - ''' + """ try: result = self.server.create_cronjob( - self.session_id, - line - ) + self.session_id, + line + ) self.log.debug(result) except xmlrpclib.Fault: self.log.exception("Error creating cron job") return 1 def delete_cronjob(self, line): - ''' + """ Delete a cronjob @param line: A line you want removed from your cronjob @@ -261,13 +273,12 @@ def delete_cronjob(self, line): @returns: Nothing @rtype: None on success or 1 on failure - - ''' + """ try: result = self.server.delete_cronjob( - self.session_id, - line - ) + self.session_id, + line + ) self.log.debug(result) except xmlrpclib.Fault: self.log.exception("Error deleting cron job") @@ -275,7 +286,7 @@ def delete_cronjob(self, line): # pylint: disable-msg=C0103 def create_website(self, website_name, ip, https, subdomains, site_apps): - ''' + """ Create a website @param website_name: Name of website @@ -290,52 +301,50 @@ def create_website(self, website_name, ip, https, subdomains, site_apps): @param subdomains: List of subdomains for this website @type subdomains: list - @param site_apps: + @param site_apps: @type site_apps: list @returns: Nothing @rtype: None on success or 1 on failure - - ''' + """ if https.lower() == 'true': https = True else: https = False subdomains = subdomains.split(',') - print subdomains - #XXX: Limitation of only one site_app + print(subdomains) + # TODO: Limitation of only one site_app site_apps = site_apps.split(',') - print site_apps + print(site_apps) try: result = self.server.create_website( - self.session_id, - website_name, - ip, - https, - subdomains, - site_apps - ) + self.session_id, + website_name, + ip, + https, + subdomains, + site_apps + ) self.log.debug(result) except xmlrpclib.Fault: self.log.exception("Error creating website") return 1 def create_email(self, email_address, targets, autoresponder_on=False, - autoresponder_subject='', autoresponder_message='', - autoresponder_from=''): - ''' + autoresponder_subject='', autoresponder_message='', + autoresponder_from=''): + """ Create an email address for a mailbox - @param email_address: + @param email_address: @type email_address: string @param targets: mailbox names @type targets: string - + @returns: Success code @rtype: None on success or 1 on failure - - ''' + """ if autoresponder_on.lower() == 'true': autoresponder_on = True else: @@ -348,21 +357,21 @@ def create_email(self, email_address, targets, autoresponder_on=False, autoresponder_from = '' try: result = self.server.create_email( - self.session_id, - email_address, - targets, - autoresponder_on, - autoresponder_subject, - autoresponder_message, - autoresponder_from, - ) + self.session_id, + email_address, + targets, + autoresponder_on, + autoresponder_subject, + autoresponder_message, + autoresponder_from, + ) self.log.debug(result) except xmlrpclib.Fault: self.log.exception("Error creating email") return 1 def delete_email(self, email_address): - ''' + """ Delete an email address @param email_address: An email address you want removed from a mailbox @@ -370,22 +379,21 @@ def delete_email(self, email_address): @returns: Success code @rtype: None on success or 1 on failure - - ''' + """ try: result = self.server.delete_email( - self.session_id, - email_address - ) + self.session_id, + email_address + ) self.log.debug(result) except xmlrpclib.Fault: self.log.exception("Error deleting email") return 1 def create_mailbox(self, mailbox, enable_spam_protection=True, share=False, - spam_to_learn_folder='spam_to_learn', - ham_to_learn_folder='ham_to_learn'): - ''' + spam_to_learn_folder='spam_to_learn', + ham_to_learn_folder='ham_to_learn'): + """ Delete a mailbox @param mailbox: Mailbox name you want to create @@ -399,33 +407,31 @@ def create_mailbox(self, mailbox, enable_spam_protection=True, share=False, @returns: Success code @rtype: None on success or 1 on failure - - ''' + """ if enable_spam_protection.lower() == 'true': enable_spam_protection = True elif enable_spam_protection.lower() == 'false': enable_spam_protection = False else: - self.log.error(\ - "Error: enable_spam_protection must be True or False") + self.log.error("Error: enable_spam_protection must be True or False") return 1 try: result = self.server.create_mailbox( - self.session_id, - mailbox, - enable_spam_protection, - share, - spam_to_learn_folder, - ham_to_learn_folder - ) + self.session_id, + mailbox, + enable_spam_protection, + share, + spam_to_learn_folder, + ham_to_learn_folder + ) self.log.debug(result) - print "Password for the new mailbox is: %s" % result['password'] + print("Password for the new mailbox is: {}".format(result['password'])) except xmlrpclib.Fault: self.log.exception("Error creating mailbox") return 1 def delete_mailbox(self, mailbox): - ''' + """ Delete a mailbox @param mailbox: A mailbox name you wanted deleted @@ -433,20 +439,19 @@ def delete_mailbox(self, mailbox): @returns: Success code @rtype: None on success or 1 on failure - - ''' + """ try: result = self.server.delete_mailbox( - self.session_id, - mailbox - ) + self.session_id, + mailbox + ) self.log.debug(result) except xmlrpclib.Fault: self.log.exception("Error deleting mailbox") return 1 def set_apache_acl(self, paths, permission='rwx', recursive=False): - ''' + """ Set Apache ACL @param paths: @@ -460,22 +465,21 @@ def set_apache_acl(self, paths, permission='rwx', recursive=False): @returns: Success code @rtype: None on success or 1 on failure - - ''' + """ try: result = self.server.set_apache_acl( - self.session_id, - paths, - permission, - recursive, - ) + self.session_id, + paths, + permission, + recursive, + ) self.log.debug(result) except xmlrpclib.Fault: self.log.exception("Error setting apache acl") return 1 def system(self, cmd): - ''' + """ Runs a Linux command in your homedir and prints the result @param cmd: Command you want to run @@ -483,26 +487,25 @@ def system(self, cmd): @returns: Success code @rtype: None on success or 1 on failure - - ''' + """ try: result = self.server.system( - self.session_id, - cmd - ) - print result + self.session_id, + cmd + ) + print(result) except xmlrpclib.Fault: self.log.exception("Error running system command %s", cmd) return 1 - def list_disk_usage(self): - ''' + def list_disk_usage(self, debug=False): + """ List disk space usage statistics about your account http://docs.webfaction.com/xmlrpc-api/apiref.html#method-list_disk_usage - + @returns: Structure containing all disk usage members (see link for details) @rtype: None on success or 1 on failure - ''' + """ try: result = self.server.list_disk_usage(self.session_id) self.log.debug(result) @@ -511,14 +514,14 @@ def list_disk_usage(self): self.log.exception("Error listing disk usage") return 1 - def list_bandwidth_usage(self): - ''' + def list_bandwidth_usage(self, debug=False): + """ List bandwidth usage statistics for your websites http://docs.webfaction.com/xmlrpc-api/apiref.html#method-list_bandwidth_usage - + @returns: Structure containing two members, daily and monthly @rtype: None on success or 1 on failure - ''' + """ try: result = self.server.list_bandwidth_usage(self.session_id) self.log.debug(result) @@ -526,3 +529,20 @@ def list_bandwidth_usage(self): except xmlrpclib.Fault: self.log.exception("Error listing bandwidth usage") return 1 + + def list_machines(self, debug=False): + """ + Get information about the account's machines. + This method returns an array of structs with the following key-value pairs + https://docs.webfaction.com/xmlrpc-api/apiref.html#method-list_machines + + @returns: Structure returns an array of structs with the following keys: name, operating_system, location, id + @rtype: None on success or 1 on failure + """ + try: + result = self.server.list_machines(self.session_id) + self.log.debug(result) + return result + except xmlrpclib.Fault: + self.log.exception("Error listing machines") + return 1