From 6f913f935cf69dadd0a902b7fc025f93211a544e Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 16:19:09 +0200 Subject: [PATCH 01/19] adding Python 3 support --- bin/webfaction | 8 +++++--- requirements.txt | 5 ++++- setup.py | 17 ++++++++++------- webfaction/webflib.py | 24 ++++++++++++++++-------- 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/bin/webfaction b/bin/webfaction index c70a94f..e976ccc 100755 --- a/bin/webfaction +++ b/bin/webfaction @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # pylint: disable-msg=W0142 ''' @@ -8,6 +8,7 @@ cli.py Command-line tool for WebFaction's XML-RPC API http://api.webfaction.com/ Author: Rob Cakebread +Author: Marcin Bielak License : MIT @@ -217,7 +218,8 @@ class WebFaction(object): 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= "Show this program's version and exit.") @@ -231,7 +233,7 @@ class WebFaction(object): def webfaction_version(): '''Print webfaction version''' - print VERSION + print(VERSION) def main(): '''Let's do it.''' diff --git a/requirements.txt b/requirements.txt index 46809bb..8758197 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,4 @@ -configobj>=5.0.2 \ No newline at end of file +configobj>=5.0.2 +#xmlrpc +httplib + diff --git a/setup.py b/setup.py index 2c39d96..b0630f0 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 + from distutils.core import setup, Extension webfaction_classifiers = [ @@ -14,21 +15,23 @@ 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',], classifiers=webfaction_classifiers, ) + diff --git a/webfaction/webflib.py b/webfaction/webflib.py index ce756b8..6cb1b9c 100644 --- a/webfaction/webflib.py +++ b/webfaction/webflib.py @@ -1,4 +1,3 @@ - # pylint: disable-msg=R0913,W0511,E1103 ''' @@ -10,10 +9,16 @@ ''' -import xmlrpclib +import sys import logging import os.path -import httplib + +if sys.version_info[0] > 2: + from xmlrpc import client as xmlrpclib + from http import client as httplib +else: + import xmlrpclib + import httplib from configobj import ConfigObj @@ -27,7 +32,9 @@ def __init__(self, username, password, db_type): API_URL = 'https://api.webfaction.com/' CONF = os.path.expanduser('~/.webfrc') -class WebFactionXmlRpc(object): + + +class WebFactionXmlRpc: '''WebFaction XML-RPC server proxy class''' @@ -302,10 +309,10 @@ def create_website(self, website_name, ip, https, subdomains, site_apps): else: https = False subdomains = subdomains.split(',') - print subdomains + print(subdomains) #XXX: 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, @@ -419,7 +426,7 @@ def create_mailbox(self, mailbox, enable_spam_protection=True, share=False, 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 @@ -490,7 +497,7 @@ def system(self, cmd): self.session_id, cmd ) - print result + print(result) except xmlrpclib.Fault: self.log.exception("Error running system command %s", cmd) return 1 @@ -526,3 +533,4 @@ def list_bandwidth_usage(self): except xmlrpclib.Fault: self.log.exception("Error listing bandwidth usage") return 1 + From 97043eb92ce917cd0600ec1bf298c8214ae5f820 Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 16:21:54 +0200 Subject: [PATCH 02/19] adding support for list commands --list-disk-usage and --list-bandwidth-usage --- bin/webfaction | 16 ++++++++++++++++ webfaction/__init__.py | 2 +- webfaction/webflib.py | 4 ++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/bin/webfaction b/bin/webfaction index e976ccc..3965880 100755 --- a/bin/webfaction +++ b/bin/webfaction @@ -195,6 +195,22 @@ class WebFaction(object): 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('--set-apache-acl', action='store', dest='set_apache_acl', diff --git a/webfaction/__init__.py b/webfaction/__init__.py index ac7fef9..3b7b598 100644 --- a/webfaction/__init__.py +++ b/webfaction/__init__.py @@ -13,7 +13,7 @@ ''' -__version__ = '1.0' +__version__ = '1.1' __docformat__ = 'epytext' __author__ = 'Tom Clancy @' __contributors__ = 'Rob Cakebread @, Patrick Robertson @' diff --git a/webfaction/webflib.py b/webfaction/webflib.py index 6cb1b9c..f1d0dbc 100644 --- a/webfaction/webflib.py +++ b/webfaction/webflib.py @@ -502,7 +502,7 @@ def system(self, cmd): 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 @@ -518,7 +518,7 @@ 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 From 49ee2b674b6f114abd881bc1e101e29a05e03641 Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 16:39:31 +0200 Subject: [PATCH 03/19] adding support for list commands --list-machines and README.md --- README => README.md | 16 ++++ bin/webfaction | 52 ++++++----- webfaction/webflib.py | 213 +++++++++++++++++++++++------------------- 3 files changed, 161 insertions(+), 120 deletions(-) rename README => README.md (59%) diff --git a/README b/README.md similarity index 59% rename from README rename to README.md index 723c4ec..0fc789d 100644 --- a/README +++ b/README.md @@ -1,3 +1,10 @@ +Webfaction-Python-API +--------------------- + +Webfaction Python 2 and 3 API is supported. + + + 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) @@ -5,3 +12,12 @@ B. I wanted to modify it to accept passed-in credentials instead of having one c Original post: http://forum.webfaction.com/viewtopic.php?id=1513 [dead] WebFaction API Docs: https://docs.webfaction.com/xmlrpc-api/ + + +TODO +----- +- [X] support for Pytohn 3 +- [X] implement support for list usage disks and bandwidth +- [_] unit tests +- [_] verify compatibility for Python 2 and Python 3 +- diff --git a/bin/webfaction b/bin/webfaction index 3965880..6a93158 100755 --- a/bin/webfaction +++ b/bin/webfaction @@ -89,7 +89,7 @@ class WebFaction(object): 'Options for calling WebFaction XML-RPC API methods') group_api.add_option('--create-app', - action='store', + action='store', dest='create_app', nargs=4, help='Create application', @@ -97,28 +97,28 @@ class WebFaction(object): ) group_api.add_option('--delete-app', - action='store', + action='store', dest='delete_app', help='Delete application', metavar='NAME' ) group_api.add_option('--create-cronjob', - action='store', + action='store', dest='create_cronjob', help='Create cronjob', metavar='CRONJOB' ) group_api.add_option('--delete-cronjob', - action='store', + action='store', dest='delete_cronjob', help='Delete cronjob', metavar='CRONJOB' ) group_api.add_option('--create-db', - action='store', + action='store', dest='create_db', nargs=3, help='Create Database', @@ -126,7 +126,7 @@ class WebFaction(object): ) group_api.add_option('--delete-db', - action='store', + action='store', dest='delete_db', nargs=2, help='Delete Database', @@ -134,7 +134,7 @@ class WebFaction(object): ) group_api.add_option('--create-domain', - action='store', + action='store', dest='create_domain', help='Create domain', nargs=2, @@ -142,7 +142,7 @@ class WebFaction(object): ) group_api.add_option('--create-website', - action='store', + action='store', dest='create_website', help='Create website', nargs=5, @@ -150,7 +150,7 @@ class WebFaction(object): ) group_api.add_option('--create-dns-override', - action='store', + action='store', dest='create_dns_override', help='Create DNS override', nargs=6, @@ -158,7 +158,7 @@ class WebFaction(object): ) group_api.add_option('--delete-dns-override', - action='store', + action='store', dest='delete_dns_override', help='Delete DNS override', nargs=6, @@ -166,7 +166,7 @@ class WebFaction(object): ) group_api.add_option('--create-email', - action='store', + action='store', dest='create_email', help='Create email address', nargs=6, @@ -174,14 +174,14 @@ class WebFaction(object): ) group_api.add_option('--delete-email', - action='store', + action='store', dest='delete_email', help='Delete email address', metavar='EMAIL_ADDRESS' ) group_api.add_option('--create-mailbox', - action='store', + action='store', dest='create_mailbox', help='Create mailbox', nargs=5, @@ -189,30 +189,38 @@ class WebFaction(object): ) group_api.add_option('--delete-mailbox', - action='store', + action='store', dest='delete_mailbox', help='Delete mailbox', metavar='MBOX_NAME' ) group_api.add_option('--list-disk-usage', - action='store', + action='store', dest='list_disk_usage', help='List disk usage', - nargs=0, + nargs=0, metavar='' ) group_api.add_option('--list-bandwidth-usage', - action='store', + action='store', dest='list_bandwidth_usage', help='List bandwidth usage', - nargs=0, + 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', + action='store', dest='set_apache_acl', nargs=3, help='Set Apache ACL', @@ -220,13 +228,13 @@ class WebFaction(object): ) group_api.add_option('--system', - action='store', + action='store', dest='system', help='Run system command.', metavar='CMD' ) group_api.add_option('--write-file', - action='store', + action='store', dest='write_file', help='Write file', metavar='FILENAME STRING MODE' @@ -234,7 +242,7 @@ class WebFaction(object): self.group_api = group_api opt_parser.add_option_group(group_api) - + # Common options opt_parser.add_option('--version', action='store_true', dest= 'webfaction_version', default=False, help= diff --git a/webfaction/webflib.py b/webfaction/webflib.py index f1d0dbc..40266a5 100644 --- a/webfaction/webflib.py +++ b/webfaction/webflib.py @@ -14,10 +14,10 @@ import os.path if sys.version_info[0] > 2: - from xmlrpc import client as xmlrpclib + from xmlrpc import client as xmlrpclib from http import client as httplib else: - import xmlrpclib + import xmlrpclib import httplib from configobj import ConfigObj @@ -29,13 +29,13 @@ def __init__(self, username, password, db_type): self.username = username self.password = password self.db_type = db_type - + + API_URL = 'https://api.webfaction.com/' CONF = os.path.expanduser('~/.webfrc') class WebFactionXmlRpc: - '''WebFaction XML-RPC server proxy class''' def __init__(self, user=None, password=None, machine=None): @@ -46,14 +46,15 @@ def __init__(self, user=None, password=None, machine=None): 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''' @@ -63,7 +64,7 @@ def get_config(): u" The format is:", u" username=", u" password=", - ]) + ]) raise NotImplementedError(err) config = ConfigObj(CONF) username = config['username'] @@ -81,7 +82,7 @@ def login(self): 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) + account) def create_app(self, app_name, app_type, autostart, extra_info): '''Create new application''' @@ -89,12 +90,12 @@ def create_app(self, app_name, app_type, autostart, extra_info): 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: @@ -108,14 +109,14 @@ def delete_app(self, app_name): '''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 https://docs.webfaction.com/xmlrpc-api/apiref.html#method-list_apps @@ -125,7 +126,7 @@ def list_apps(self): except xmlrpclib.Fault: self.log.exception("Problem listing apps") return [] - + def delete_db(self, name, db_type): ''' Delete database @@ -136,13 +137,13 @@ def delete_db(self, name, db_type): @param db_type: mysql or postgres @type db_type: string ''' - #XXX: Validate db_type + # XXX: Validate 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) @@ -165,15 +166,15 @@ def create_db(self, name, db_type, password): @rtype: None on success or 1 on failure ''' - #XXX: Validate db_type - #XXX: Use interactive method to get password? + # XXX: Validate db_type + # XXX: 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: @@ -182,62 +183,62 @@ 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): if not isinstance(db_user, WebFactionDBUser): raise ValueError("db_user must be an instance of WebFactionDBUser") - + 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") - + 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 @@ -251,9 +252,9 @@ def create_cronjob(self, line): ''' 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") @@ -272,9 +273,9 @@ def delete_cronjob(self, line): ''' 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") @@ -310,26 +311,26 @@ def create_website(self, website_name, ip, https, subdomains, site_apps): https = False subdomains = subdomains.split(',') print(subdomains) - #XXX: Limitation of only one site_app + # XXX: Limitation of only one site_app site_apps = site_apps.split(',') 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 @@ -355,14 +356,14 @@ 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") @@ -381,17 +382,17 @@ def delete_email(self, email_address): ''' 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 @@ -413,18 +414,18 @@ def create_mailbox(self, mailbox, enable_spam_protection=True, share=False, 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: {}".format(result['password'])) except xmlrpclib.Fault: @@ -444,9 +445,9 @@ def delete_mailbox(self, mailbox): ''' 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") @@ -471,11 +472,11 @@ def set_apache_acl(self, paths, permission='rwx', recursive=False): ''' 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") @@ -494,9 +495,9 @@ def system(self, cmd): ''' try: result = self.server.system( - self.session_id, - cmd - ) + self.session_id, + cmd + ) print(result) except xmlrpclib.Fault: self.log.exception("Error running system command %s", cmd) @@ -534,3 +535,19 @@ def list_bandwidth_usage(self, debug=False): 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 From 7c6972858d8d1b5cc9bd9f1db444004ea0a3ffb3 Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 17:24:08 +0200 Subject: [PATCH 04/19] adding Travis CI support --- .travis.yml | 23 ++++ README.md | 2 +- bin/webfaction | 250 ++++++++++++++++++++--------------------- codecov.yml | 26 +++++ requirements.txt | 3 + run_travis.sh | 5 + setup.py | 22 ++-- tests/__init__.py | 0 tests/webflib_test.py | 16 +++ webfaction/__init__.py | 7 +- webfaction/webflib.py | 129 ++++++++++----------- 11 files changed, 271 insertions(+), 212 deletions(-) create mode 100644 .travis.yml create mode 100644 codecov.yml create mode 100644 run_travis.sh create mode 100644 tests/__init__.py create mode 100644 tests/webflib_test.py diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..fd7e0a4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +sudo: false +dist: xenial +language: python +virtualenv: + system_site_packages: true +python: + - "2.6" + - "2.7" + - "3.4" + - "3.5" + - "3.6" + # PyPy versions + - "pypy2.7" + - "pypy3.5" +install: + - pip install . + - pip install -r requirements.txt +script: sh run_travis.sh +after_success: + #- export CODECOV_TOKEN=":uuid-repo-token" + - bash < (curl -s https://codecov.io/bash) +notifications: + - email: false diff --git a/README.md b/README.md index 0fc789d..0c82564 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Webfaction-Python-API +Webfaction-Python-API [Travis CI](https://travis-ci.org/bieli/Webfaction-Python-API.svg?branch=master) --------------------- Webfaction Python 2 and 3 API is supported. diff --git a/bin/webfaction b/bin/webfaction index 6a93158..631c88a 100755 --- a/bin/webfaction +++ b/bin/webfaction @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# pylint: disable-msg=W0142 -''' +# pylint: disable=W0142,E501 +""" cli.py ====== @@ -12,12 +12,11 @@ Author: Marcin Bielak License : MIT -''' +""" __docformat__ = 'epytext' __revision__ = '$Revision: $'[11:-1].strip() - import sys import logging import optparse @@ -27,7 +26,6 @@ from webfaction import webflib class WebFaction(object): - '''``WebFaction`` class''' def __init__(self): @@ -59,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): @@ -85,171 +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='' - ) + 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='' - ) + 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='' - ) + 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 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 @@ -259,6 +257,7 @@ def webfaction_version(): '''Print webfaction version''' print(VERSION) + def main(): '''Let's do it.''' client = WebFaction() @@ -267,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 8758197..1863d7a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,7 @@ configobj>=5.0.2 #xmlrpc httplib +pyflakes +coverage +nose diff --git a/run_travis.sh b/run_travis.sh new file mode 100644 index 0000000..e925d1c --- /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 +python3 -m pyflakes diff --git a/setup.py b/setup.py index b0630f0..a2c74c4 100644 --- a/setup.py +++ b/setup.py @@ -1,21 +1,22 @@ #!/usr/bin/env python3 +# pylint: disable=E501 -from distutils.core import setup, Extension +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. +Uses a Python XML-RPC instance to communicate with the Webfaction API. Most API commands have been turned into Python commands. """ @@ -31,7 +32,6 @@ platforms=["all"], 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..f4eed9d --- /dev/null +++ b/tests/webflib_test.py @@ -0,0 +1,16 @@ +import unittest + +from webfaction.webflib import WebFactionDBUser + + +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) diff --git a/webfaction/__init__.py b/webfaction/__init__.py index 3b7b598..f16c657 100644 --- a/webfaction/__init__.py +++ b/webfaction/__init__.py @@ -1,5 +1,5 @@ - -''' +# pylint: disable=E501 +""" webf ==== @@ -11,7 +11,7 @@ ----------- webfaction is a command-line client and library for webfaction.com's XML-RPC API -''' +""" __version__ = '1.1' __docformat__ = 'epytext' @@ -19,4 +19,3 @@ __contributors__ = 'Rob Cakebread @, Patrick Robertson @' __copyright__ = '(C) 2008 Rob Cakebread' __license__ = 'MIT' - diff --git a/webfaction/webflib.py b/webfaction/webflib.py index 40266a5..da538f3 100644 --- a/webfaction/webflib.py +++ b/webfaction/webflib.py @@ -1,18 +1,20 @@ -# pylint: disable-msg=R0913,W0511,E1103 +# pylint: disable=R0913,W0511,E1103,E501 -''' +""" webflib ======= WebFaction XML-RPC API library -''' +""" import sys import logging import os.path +from configobj import ConfigObj + if sys.version_info[0] > 2: from xmlrpc import client as xmlrpclib from http import client as httplib @@ -20,10 +22,8 @@ import xmlrpclib import httplib -from configobj import ConfigObj - -class WebFactionDBUser(object): +class WebFactionDBUser: def __init__(self, username, password, db_type): super(WebFactionDBUser, self).__init__() self.username = username @@ -36,7 +36,7 @@ def __init__(self, username, password, db_type): class WebFactionXmlRpc: - '''WebFaction XML-RPC server proxy class''' + """WebFaction XML-RPC server proxy class""" def __init__(self, user=None, password=None, machine=None): self.log = logging.getLogger("webf") @@ -57,22 +57,22 @@ def __init__(self, user=None, password=None, machine=None): @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: @@ -85,7 +85,7 @@ def login(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: @@ -106,7 +106,7 @@ 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, @@ -118,9 +118,9 @@ def delete_app(self, app_name): 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: @@ -128,7 +128,7 @@ def list_apps(self): return [] def delete_db(self, name, db_type): - ''' + """ Delete database @param name: name of database @@ -136,8 +136,8 @@ def delete_db(self, name, db_type): @param db_type: mysql or postgres @type db_type: string - ''' - # XXX: Validate db_type + """ + # TODO: Validate db_type try: result = self.server.delete_db( self.session_id, @@ -150,7 +150,7 @@ def delete_db(self, name, db_type): return 1 def create_db(self, name, db_type, password): - ''' + """ Create database @param name: name of database @@ -164,10 +164,9 @@ 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? + """ + # TODO: Validate db_type + # TODO: Use interactive method to get password? try: result = self.server.create_db( self.session_id, @@ -240,7 +239,7 @@ def grant_db_permissions(self, db_user, database): return False def create_cronjob(self, line): - ''' + """ Create a cronjob @param line: A line you want in your cronjob @@ -248,8 +247,7 @@ def create_cronjob(self, line): @returns: Nothing @rtype: None on success or 1 on failure - - ''' + """ try: result = self.server.create_cronjob( self.session_id, @@ -261,7 +259,7 @@ def create_cronjob(self, line): return 1 def delete_cronjob(self, line): - ''' + """ Delete a cronjob @param line: A line you want removed from your cronjob @@ -269,8 +267,7 @@ def delete_cronjob(self, line): @returns: Nothing @rtype: None on success or 1 on failure - - ''' + """ try: result = self.server.delete_cronjob( self.session_id, @@ -283,7 +280,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 @@ -298,20 +295,19 @@ 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 + # TODO: Limitation of only one site_app site_apps = site_apps.split(',') print(site_apps) try: @@ -331,19 +327,18 @@ def create_website(self, website_name, ip, https, subdomains, site_apps): def create_email(self, email_address, targets, autoresponder_on=False, 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: @@ -370,7 +365,7 @@ def create_email(self, email_address, targets, autoresponder_on=False, return 1 def delete_email(self, email_address): - ''' + """ Delete an email address @param email_address: An email address you want removed from a mailbox @@ -378,8 +373,7 @@ 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, @@ -393,7 +387,7 @@ def delete_email(self, email_address): 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'): - ''' + """ Delete a mailbox @param mailbox: Mailbox name you want to create @@ -407,15 +401,13 @@ 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( @@ -433,7 +425,7 @@ def create_mailbox(self, mailbox, enable_spam_protection=True, share=False, return 1 def delete_mailbox(self, mailbox): - ''' + """ Delete a mailbox @param mailbox: A mailbox name you wanted deleted @@ -441,8 +433,7 @@ 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, @@ -454,7 +445,7 @@ def delete_mailbox(self, mailbox): return 1 def set_apache_acl(self, paths, permission='rwx', recursive=False): - ''' + """ Set Apache ACL @param paths: @@ -468,8 +459,7 @@ 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, @@ -483,7 +473,7 @@ def set_apache_acl(self, paths, permission='rwx', recursive=False): return 1 def system(self, cmd): - ''' + """ Runs a Linux command in your homedir and prints the result @param cmd: Command you want to run @@ -491,8 +481,7 @@ def system(self, cmd): @returns: Success code @rtype: None on success or 1 on failure - - ''' + """ try: result = self.server.system( self.session_id, @@ -504,13 +493,13 @@ def system(self, cmd): return 1 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) @@ -520,13 +509,13 @@ def list_disk_usage(self, debug=False): return 1 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) @@ -536,14 +525,14 @@ def list_bandwidth_usage(self, debug=False): return 1 def list_machines(self, debug=False): - ''' - Get information about the account’s machines. + """ + 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) From 8efbf8dba3a41e70aee99da60d1669e04dbb76a3 Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 17:28:19 +0200 Subject: [PATCH 05/19] adding Travis CI support --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index fd7e0a4..0b376c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,9 @@ sudo: false dist: xenial language: python +addons: + apt: + update: true virtualenv: system_site_packages: true python: From d12139608638bef39ab20b414137d242192a7130 Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 17:36:23 +0200 Subject: [PATCH 06/19] adding Travis CI support --- .travis.yml | 9 ++++++++- README.md | 27 +++++++++++++++++---------- requirements.txt | 2 +- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0b376c6..d6f2e50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,9 +16,16 @@ python: - "pypy2.7" - "pypy3.5" install: + - sudo apt-get update + - pip install --upgrade pip setuptools + - pip install --upgrade pip virtualenv + - pip install tox-travis + #- pip install -U --process-dependency-links -e .[develop] + - pip install --upgrade pytest +script: - pip install . - pip install -r requirements.txt -script: sh run_travis.sh + - sh run_travis.sh after_success: #- export CODECOV_TOKEN=":uuid-repo-token" - bash < (curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index 0c82564..a1a1f6b 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,30 @@ -Webfaction-Python-API [Travis CI](https://travis-ci.org/bieli/Webfaction-Python-API.svg?branch=master) +Webfaction-Python-API --------------------- -Webfaction Python 2 and 3 API is supported. +[![Build Status](https://travis-ci.org/bieli/Webfaction-Python-API.svg?branch=master)](http://travis-ci.org/bieli/google_rank_checker) +Webfaction API client with command line tool for calling API methods. +Python 2 and 3 API is supported. -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/ TODO ----- - [X] support for Pytohn 3 - [X] implement support for list usage disks and bandwidth +- [_] adding Travis CI build in multiple Python versions - [_] unit tests - [_] verify compatibility for Python 2 and Python 3 -- + + + +Original README description +-------------- +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/requirements.txt b/requirements.txt index 1863d7a..31bf026 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ httplib pyflakes coverage nose - +virtualenv From 96ad5ba1203daf7aa155bfe1975f79af2c17eeb8 Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 17:39:45 +0200 Subject: [PATCH 07/19] adding Travis CI support --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d6f2e50..fe27388 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ sudo: false -dist: xenial +dist: trusty language: python addons: apt: From cbaa3ae5e35a59a80bb30d8f9a91d175db99ab60 Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 17:46:49 +0200 Subject: [PATCH 08/19] adding Travis CI support --- .travis.yml | 2 ++ webfaction/webflib.py | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index fe27388..9c4273c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,8 @@ install: - sudo apt-get update - pip install --upgrade pip setuptools - pip install --upgrade pip virtualenv + - pip install coverage -U + - pip install nose -U - pip install tox-travis #- pip install -U --process-dependency-links -e .[develop] - pip install --upgrade pytest diff --git a/webfaction/webflib.py b/webfaction/webflib.py index da538f3..a1a15c2 100644 --- a/webfaction/webflib.py +++ b/webfaction/webflib.py @@ -13,7 +13,10 @@ import logging import os.path -from configobj import ConfigObj +try: + from configobj import ConfigObj +except: + pass if sys.version_info[0] > 2: from xmlrpc import client as xmlrpclib @@ -25,7 +28,6 @@ class WebFactionDBUser: def __init__(self, username, password, db_type): - super(WebFactionDBUser, self).__init__() self.username = username self.password = password self.db_type = db_type From c726b0806df035dddc7192010492f27360d2a522 Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 17:50:12 +0200 Subject: [PATCH 09/19] adding Travis CI support --- .travis.yml | 2 +- run_travis.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9c4273c..67e59dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ addons: virtualenv: system_site_packages: true python: - - "2.6" - "2.7" - "3.4" - "3.5" @@ -20,6 +19,7 @@ install: - 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 -U --process-dependency-links -e .[develop] diff --git a/run_travis.sh b/run_travis.sh index e925d1c..857a5d3 100644 --- a/run_travis.sh +++ b/run_travis.sh @@ -2,4 +2,4 @@ python -m unittest discover ./tests/ "*test.py" nosetests tests -v --with-coverage -python3 -m pyflakes +python -m pyflakes From 1188b3734fe0a58518f77be33a112b633c9c9829 Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 17:51:58 +0200 Subject: [PATCH 10/19] adding Travis CI support --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 31bf026..eed614d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ configobj>=5.0.2 #xmlrpc -httplib +#httplib pyflakes coverage nose From e15be897520b54aa5592c1c105e66a3e83f9a228 Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 17:52:35 +0200 Subject: [PATCH 11/19] adding Travis CI support --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 67e59dd..5820f94 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,6 @@ python: - "2.7" - "3.4" - "3.5" - - "3.6" # PyPy versions - "pypy2.7" - "pypy3.5" From d7aa1e81098ccdb2252155b028a76971c42b307d Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 17:57:59 +0200 Subject: [PATCH 12/19] adding Travis CI support --- .travis.yml | 1 - run_travis.sh | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5820f94..701a3e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,6 @@ virtualenv: python: - "2.7" - "3.4" - - "3.5" # PyPy versions - "pypy2.7" - "pypy3.5" diff --git a/run_travis.sh b/run_travis.sh index 857a5d3..8a07ba2 100644 --- a/run_travis.sh +++ b/run_travis.sh @@ -2,4 +2,4 @@ python -m unittest discover ./tests/ "*test.py" nosetests tests -v --with-coverage -python -m pyflakes +python -m pyflakes . From 6e0c46758c3fa057a2ebff8744da52e73aa0d5e0 Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 18:04:43 +0200 Subject: [PATCH 13/19] adding Travis CI support --- .travis.yml | 7 +++++-- README.md | 2 +- run_travis.sh | 1 + webfaction/webflib.py | 7 ++++--- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 701a3e2..3a66bb8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ install: - pip install pyflakes -U - pip install nose -U - pip install tox-travis + - pip install codecov -U #- pip install -U --process-dependency-links -e .[develop] - pip install --upgrade pytest script: @@ -27,7 +28,9 @@ script: - pip install -r requirements.txt - sh run_travis.sh after_success: - #- export CODECOV_TOKEN=":uuid-repo-token" - - bash < (curl -s https://codecov.io/bash) + - codecov +#after_success: +# - export CODECOV_TOKEN=":7da40838-6b2f-4442-9b5e-df8bfbe2b5be" +# - bash < (curl -s https://codecov.io/bash) notifications: - email: false diff --git a/README.md b/README.md index a1a1f6b..acb0534 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ TODO ----- - [X] support for Pytohn 3 - [X] implement support for list usage disks and bandwidth -- [_] adding Travis CI build in multiple Python versions +- [X] adding Travis CI build in multiple Python versions - [_] unit tests - [_] verify compatibility for Python 2 and Python 3 diff --git a/run_travis.sh b/run_travis.sh index 8a07ba2..67b1e5b 100644 --- a/run_travis.sh +++ b/run_travis.sh @@ -3,3 +3,4 @@ python -m unittest discover ./tests/ "*test.py" nosetests tests -v --with-coverage python -m pyflakes . +#coverage run ./tests/*test.py diff --git a/webfaction/webflib.py b/webfaction/webflib.py index a1a15c2..f3c064b 100644 --- a/webfaction/webflib.py +++ b/webfaction/webflib.py @@ -209,10 +209,12 @@ def create_db_user(self, username, password, db_type): 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, @@ -225,8 +227,7 @@ def delete_db_user(self, db_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( From db128b419ced4bbd42cbaee0d71493ee77455d5c Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 18:07:20 +0200 Subject: [PATCH 14/19] adding Travis CI support --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index acb0534..5101ff9 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ Webfaction-Python-API [![Build Status](https://travis-ci.org/bieli/Webfaction-Python-API.svg?branch=master)](http://travis-ci.org/bieli/google_rank_checker) +[![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. From 6e6e72d276a537d8dd3fa14318b1ff3a320c58a9 Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 18:07:49 +0200 Subject: [PATCH 15/19] adding Travis CI support --- .travis.yml | 5 ----- run_travis.sh | 1 - 2 files changed, 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3a66bb8..57d0425 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,6 @@ virtualenv: python: - "2.7" - "3.4" - # PyPy versions - "pypy2.7" - "pypy3.5" install: @@ -21,7 +20,6 @@ install: - pip install nose -U - pip install tox-travis - pip install codecov -U - #- pip install -U --process-dependency-links -e .[develop] - pip install --upgrade pytest script: - pip install . @@ -29,8 +27,5 @@ script: - sh run_travis.sh after_success: - codecov -#after_success: -# - export CODECOV_TOKEN=":7da40838-6b2f-4442-9b5e-df8bfbe2b5be" -# - bash < (curl -s https://codecov.io/bash) notifications: - email: false diff --git a/run_travis.sh b/run_travis.sh index 67b1e5b..8a07ba2 100644 --- a/run_travis.sh +++ b/run_travis.sh @@ -3,4 +3,3 @@ python -m unittest discover ./tests/ "*test.py" nosetests tests -v --with-coverage python -m pyflakes . -#coverage run ./tests/*test.py From 0c79cca0a52a570ec60dd983fce51bf47e9cc55a Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 19:47:08 +0200 Subject: [PATCH 16/19] updating tests nad CI --- .gitignore | 7 ++++ .travis.yml | 2 - README.md | 95 +++++++++++++++++++++++++++++++++++++++---- tests/webflib_test.py | 35 +++++++++++++++- webfaction/webflib.py | 11 +++-- 5 files changed, 134 insertions(+), 16 deletions(-) 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 index 57d0425..891f8fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,6 @@ virtualenv: python: - "2.7" - "3.4" - - "pypy2.7" - - "pypy3.5" install: - sudo apt-get update - pip install --upgrade pip setuptools diff --git a/README.md b/README.md index 5101ff9..fb9ba61 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Webfaction-Python-API --------------------- -[![Build Status](https://travis-ci.org/bieli/Webfaction-Python-API.svg?branch=master)](http://travis-ci.org/bieli/google_rank_checker) +[![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) @@ -10,19 +10,100 @@ 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' > ~/.webfrc1 +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 -- [_] unit tests -- [_] verify compatibility for Python 2 and Python 3 +- (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 +Original README description (from forked repository) -------------- This is a port of https://pypi.python.org/pypi/webf/ -- the repo exists because diff --git a/tests/webflib_test.py b/tests/webflib_test.py index f4eed9d..a59e382 100644 --- a/tests/webflib_test.py +++ b/tests/webflib_test.py @@ -1,6 +1,9 @@ import unittest +from unittest.mock import patch, MagicMock, call -from webfaction.webflib import WebFactionDBUser +from webfaction.webflib import WebFactionDBUser, WebFactionXmlRpc, API_URL +from xmlrpc import client as xmlrpclib +from http import client as httplib class WebflibTest(unittest.TestCase): @@ -14,3 +17,33 @@ def test_should_store_creadentials_in_dedicated_object(self): 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/webflib.py b/webfaction/webflib.py index f3c064b..360237d 100644 --- a/webfaction/webflib.py +++ b/webfaction/webflib.py @@ -43,13 +43,14 @@ class WebFactionXmlRpc: 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 @@ -81,10 +82,8 @@ def login(self): 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""" From 70a07185b4a1d3cf95cdcd85673336a99bd62cee Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 19:48:24 +0200 Subject: [PATCH 17/19] updating tests nad CI --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fb9ba61..e321ad0 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ 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' > ~/.webfrc1 +cat << 'EOF' > ~/.webfrc username="username" password="strong_password" From 096aca6b301987041306325a5708d0cb840d5dd3 Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Sat, 12 May 2018 19:51:17 +0200 Subject: [PATCH 18/19] updating tests nad CI --- tests/webflib_test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/webflib_test.py b/tests/webflib_test.py index a59e382..388358c 100644 --- a/tests/webflib_test.py +++ b/tests/webflib_test.py @@ -2,8 +2,6 @@ from unittest.mock import patch, MagicMock, call from webfaction.webflib import WebFactionDBUser, WebFactionXmlRpc, API_URL -from xmlrpc import client as xmlrpclib -from http import client as httplib class WebflibTest(unittest.TestCase): From 79c556bbb0e8669565b6de925c1d733bcb94fb0e Mon Sep 17 00:00:00 2001 From: Marcin Bielak Date: Mon, 14 May 2018 20:04:00 +0200 Subject: [PATCH 19/19] CR fixes --- webfaction/webflib.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/webfaction/webflib.py b/webfaction/webflib.py index 360237d..7f5b4e6 100644 --- a/webfaction/webflib.py +++ b/webfaction/webflib.py @@ -15,7 +15,7 @@ try: from configobj import ConfigObj -except: +except ImportError: pass if sys.version_info[0] > 2: @@ -128,6 +128,10 @@ def list_apps(self): 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 @@ -138,7 +142,7 @@ def delete_db(self, name, db_type): @param db_type: mysql or postgres @type db_type: string """ - # TODO: Validate db_type + self._validate_db_type(db_type) try: result = self.server.delete_db( self.session_id, @@ -166,7 +170,7 @@ def create_db(self, name, db_type, password): @returns: Nothing @rtype: None on success or 1 on failure """ - # TODO: Validate db_type + self._validate_db_type(db_type) # TODO: Use interactive method to get password? try: result = self.server.create_db(