From cd5113d59820ec6bca7a5fc1975c1077f72114e4 Mon Sep 17 00:00:00 2001 From: Jay Date: Mon, 15 Dec 2014 03:29:39 +0000 Subject: [PATCH 1/5] Support for custom url via a new generic provider --- noipy/dnsupdater.py | 16 ++++++++++++++-- noipy/main.py | 26 ++++++++++++++++++++++++-- test/test_noipy.py | 19 +++++++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/noipy/dnsupdater.py b/noipy/dnsupdater.py index f87835a..b6bc31f 100755 --- a/noipy/dnsupdater.py +++ b/noipy/dnsupdater.py @@ -18,9 +18,10 @@ 'noip': 'NoipDnsUpdater', 'dyn': 'DynDnsUpdater', 'duck': 'DuckDnsUpdater', + 'generic': 'GenericDnsUpdater', } -DEFAULT_PLUGIN = 'noip' +DEFAULT_PLUGIN = 'generic' class DnsUpdaterPlugin(object): @@ -29,12 +30,13 @@ class DnsUpdaterPlugin(object): auth_type = "" - def __init__(self, auth, hostname): + def __init__(self, auth, hostname, options={}): """Init plugin with auth information, hostname and IP address. """ self._auth = auth self._hostname = hostname + self._options = options self.last_status_code = '' @property @@ -167,3 +169,13 @@ def hostname(self): def _get_base_url(self): return "https://www.duckdns.org/update?domains={hostname}" \ "&token={token}&ip={ip}" + +class GenericDnsUpdater(DnsUpdaterPlugin): + """ Generic DDNS provider plugin - accepts a custom specification for the DDNS base url """ + + auth_type = "P" + + def _get_base_url(self): + return "{url}?hostname={{hostname}}" \ + "&myip={{ip}}&wildcard=NOCHG&mx=NOCHG&backmx=NOCHG".format(url=self._options['url']) + diff --git a/noipy/main.py b/noipy/main.py index 5242f6b..d836378 100644 --- a/noipy/main.py +++ b/noipy/main.py @@ -36,6 +36,14 @@ EXECUTION_RESULT_OK = 0 EXECUTION_RESULT_NOK = 1 +URL_RE = re.compile( + r'^https?://' # http:// or https:// + r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... + r'localhost|' # localhost... + r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4 + r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6 + r'(?::\d+)?' # optional port + r'(?:/?|[/?]\S+)$', re.IGNORECASE) def get_ip(): """Return the machine external IP. @@ -58,7 +66,7 @@ def execute_update(args): provider_class = getattr(dnsupdater, dnsupdater.AVAILABLE_PLUGINS.get(args.provider)) - + updater_options = {} process_message = None exec_result = EXECUTION_RESULT_NOK update_ddns = False @@ -113,8 +121,21 @@ def execute_update(args): "provided via command line or stored with --store " \ "option.\nExecute noipy --help for more details." + if update_ddns and args.provider == 'generic': + if args.url: + if not URL_RE.match(args.url): + process_message = "Malformed url" + exec_result = EXECUTION_RESULT_NOK + update_ddns = False + else: + updater_options['url'] = args.url + else: + process_message = "Must use --url if --provider is 'generic' (default)" + exec_result = EXECUTION_RESULT_NOK + update_ddns = False + if update_ddns: - updater = provider_class(auth, args.hostname) + updater = provider_class(auth, args.hostname, updater_options) ip_address = args.ip if args.ip else get_ip() print("Updating hostname '%s' with IP address %s [provider: '%s']..." % (args.hostname, ip_address, args.provider)) @@ -135,6 +156,7 @@ def create_parser(): % dnsupdater.DEFAULT_PLUGIN, choices=dnsupdater.AVAILABLE_PLUGINS.keys(), default=dnsupdater.DEFAULT_PLUGIN) + parser.add_argument('--url',help="custom ddns server address") parser.add_argument('--store', help="store DDNS authentication information and " "update the hostname if it is provided", diff --git a/test/test_noipy.py b/test/test_noipy.py index ec7aff3..6a0a443 100644 --- a/test/test_noipy.py +++ b/test/test_noipy.py @@ -90,6 +90,24 @@ def test_duckdns_plugin(self): self.assertTrue(status_message.startswith("ERROR:"), "Status message should be an 'ERROR'") + def test_generic_plugin(self): + cmd_args = ['--provider', 'generic'] + args = self.parser.parse_args(cmd_args) + result, status_message = main.execute_update(args) + self.assertTrue(result == main.EXECUTION_RESULT_NOK, + "An error should be flagged when --provider is 'generic' and --url is not specified") + + cmd_args = ['-u', 'username', '-p', 'password', + '--url', 'https://dynupdate.no-ip.com/nic/update', + '--provider', 'generic', + '-n', 'noipy.no-ip.org', self.test_ip] + args = self.parser.parse_args(cmd_args) + result, status_message = main.execute_update(args) + self.assertTrue(result == main.EXECUTION_RESULT_OK, + "Update with 'No-IP' using generic provider failed.") + self.assertTrue(status_message.startswith("ERROR:"), + "Status message should be an 'ERROR'") + class AuthInfoTest(unittest.TestCase): @@ -171,6 +189,7 @@ def test_get_ip(self): def test_not_implemented_plugin(self): auth = authinfo.ApiAuth('username', 'password') hostname = "hostname" + url = "https://my.test.com/ddns" plugin = dnsupdater.DnsUpdaterPlugin(auth, hostname) try: plugin.update_dns("10.1.1.1") From cf91124a901a50e8668855da20781a9d474d5371 Mon Sep 17 00:00:00 2001 From: Jay Date: Mon, 15 Dec 2014 04:07:17 +0000 Subject: [PATCH 2/5] Removed an unnecessary change --- test/test_noipy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_noipy.py b/test/test_noipy.py index 6a0a443..a18923a 100644 --- a/test/test_noipy.py +++ b/test/test_noipy.py @@ -189,7 +189,7 @@ def test_get_ip(self): def test_not_implemented_plugin(self): auth = authinfo.ApiAuth('username', 'password') hostname = "hostname" - url = "https://my.test.com/ddns" + # url = "https://my.test.com/ddns" plugin = dnsupdater.DnsUpdaterPlugin(auth, hostname) try: plugin.update_dns("10.1.1.1") From 3bbab530c7c3d0a0af76c11f20d2ae630c30e35d Mon Sep 17 00:00:00 2001 From: Jay Date: Mon, 15 Dec 2014 04:14:56 +0000 Subject: [PATCH 3/5] Removed an unnecessary change --- test/test_noipy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_noipy.py b/test/test_noipy.py index a18923a..8087c67 100644 --- a/test/test_noipy.py +++ b/test/test_noipy.py @@ -189,7 +189,6 @@ def test_get_ip(self): def test_not_implemented_plugin(self): auth = authinfo.ApiAuth('username', 'password') hostname = "hostname" - # url = "https://my.test.com/ddns" plugin = dnsupdater.DnsUpdaterPlugin(auth, hostname) try: plugin.update_dns("10.1.1.1") From adb8bafb03953146b4ad1db68b81222d6b3469d2 Mon Sep 17 00:00:00 2001 From: Pablo O Vieira Date: Tue, 16 Dec 2014 10:24:10 -0200 Subject: [PATCH 4/5] PEP8'd code --- noipy/__init__.py | 2 +- noipy/dnsupdater.py | 9 ++++++--- noipy/main.py | 8 +++++--- test/test_noipy.py | 3 ++- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/noipy/__init__.py b/noipy/__init__.py index 2e36f85..5c9da53 100644 --- a/noipy/__init__.py +++ b/noipy/__init__.py @@ -10,7 +10,7 @@ """ __title__ = "noipy" -__version_info__ = ('1', '2', '3') +__version_info__ = ('1', '3', '0') __version__ = ".".join(__version_info__) __author__ = "Pablo O Vieira" __email__ = "email@povieira.com" diff --git a/noipy/dnsupdater.py b/noipy/dnsupdater.py index b6bc31f..6937c57 100755 --- a/noipy/dnsupdater.py +++ b/noipy/dnsupdater.py @@ -170,12 +170,15 @@ def _get_base_url(self): return "https://www.duckdns.org/update?domains={hostname}" \ "&token={token}&ip={ip}" + class GenericDnsUpdater(DnsUpdaterPlugin): - """ Generic DDNS provider plugin - accepts a custom specification for the DDNS base url """ + """ Generic DDNS provider plugin - accepts a custom specification for the + DDNS base url + """ auth_type = "P" def _get_base_url(self): - return "{url}?hostname={{hostname}}" \ - "&myip={{ip}}&wildcard=NOCHG&mx=NOCHG&backmx=NOCHG".format(url=self._options['url']) + return "{url}?hostname={{hostname}}&myip={{ip}}&wildcard=NOCHG" \ + "&mx=NOCHG&backmx=NOCHG".format(url=self._options['url']) diff --git a/noipy/main.py b/noipy/main.py index d836378..637b3b4 100644 --- a/noipy/main.py +++ b/noipy/main.py @@ -38,7 +38,8 @@ URL_RE = re.compile( r'^https?://' # http:// or https:// - r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... + r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+' + r'(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... r'localhost|' # localhost... r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4 r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6 @@ -130,7 +131,8 @@ def execute_update(args): else: updater_options['url'] = args.url else: - process_message = "Must use --url if --provider is 'generic' (default)" + process_message = "Must use --url if --provider is 'generic' " \ + "(default)" exec_result = EXECUTION_RESULT_NOK update_ddns = False @@ -156,7 +158,7 @@ def create_parser(): % dnsupdater.DEFAULT_PLUGIN, choices=dnsupdater.AVAILABLE_PLUGINS.keys(), default=dnsupdater.DEFAULT_PLUGIN) - parser.add_argument('--url',help="custom ddns server address") + parser.add_argument('--url',help="custom DDNS server address") parser.add_argument('--store', help="store DDNS authentication information and " "update the hostname if it is provided", diff --git a/test/test_noipy.py b/test/test_noipy.py index 8087c67..eba0c49 100644 --- a/test/test_noipy.py +++ b/test/test_noipy.py @@ -95,7 +95,8 @@ def test_generic_plugin(self): args = self.parser.parse_args(cmd_args) result, status_message = main.execute_update(args) self.assertTrue(result == main.EXECUTION_RESULT_NOK, - "An error should be flagged when --provider is 'generic' and --url is not specified") + "An error should be flagged when --provider is " + "'generic' and --url is not specified") cmd_args = ['-u', 'username', '-p', 'password', '--url', 'https://dynupdate.no-ip.com/nic/update', From 5e5475820f9a5045003aa58cec01c7a28d2f4bab Mon Sep 17 00:00:00 2001 From: Pablo O Vieira Date: Tue, 16 Dec 2014 10:31:14 -0200 Subject: [PATCH 5/5] --url feature update --- CHANGELOG.rst | 5 +++++ README.rst | 15 ++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 889a40e..ce87f06 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,11 @@ Changelog ========= +1.3.0 (2014-12-16) +------------------ + +- Support for custom DDNS URL via `--url` parameter + 1.2.3 (2014-10-10) ------------------ diff --git a/README.rst b/README.rst index fe62cd6..6ed396f 100644 --- a/README.rst +++ b/README.rst @@ -46,7 +46,7 @@ Basic usage of **noipy** command line tool: .. code-block:: bash $ noipy -u -p -n - --provider {noip|dyn|duck} + --provider {generic|noip|dyn|duck} For `DuckDNS provider `_, the command line would look like this: @@ -61,9 +61,15 @@ previously stored login information with ``--store`` option. .. code-block:: bash - $ noipy --hostname --provider {noip|dyn| duck} + $ noipy --hostname --provider {generic|noip|dyn| duck} + + +You can also specify a custom DDNS URL (thanks to @jayennis22): +.. code-block:: bash + + $ noipy --hostname [--provider generic] + --url -If ``--provider`` option is not informed, **noip** will be used as provider. It is also possible to inform an IP address other than the machine's current: @@ -72,6 +78,9 @@ It is also possible to inform an IP address other than the machine's current: $ noipy --hostname 127.0.0.1 +If ``--provider`` option is not informed, **generic** will be used as provider. + + For details: .. code-block:: bash