From 3d3e5ca5e8a1109b39189d9ff8a9253b031fc21e Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Wed, 31 May 2017 23:56:09 +0900 Subject: [PATCH 01/18] [mod] upgrade obsolete dependencies --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 88f4242..b2cae31 100644 --- a/setup.py +++ b/setup.py @@ -29,11 +29,11 @@ license='Apache License 2.0', packages = ['r53update'], install_requires = [ - 'argparse==1.3.0', + 'argparse2==0.5.0a1', 'boto==2.36.0', 'awscli==1.7.15', - 'dnspython==1.12.0', - 'netifaces==0.10.4' + 'dnspython==1.14.0', + 'netifaces==0.10.5' ], classifiers = [ 'Development Status :: 4 - Beta', From 08bc3a12c4073ae6f348d6502f570d879ed22d85 Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Wed, 31 May 2017 23:12:39 +0900 Subject: [PATCH 02/18] [mod] refactor the code so that it's compatible with both ptyhon27 and python3x --- r53update/r53update.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/r53update/r53update.py b/r53update/r53update.py index eb4127d..15e648f 100755 --- a/r53update/r53update.py +++ b/r53update/r53update.py @@ -16,6 +16,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +from __future__ import print_function from boto.route53.connection import Route53Connection from boto.route53.record import ResourceRecordSets from botocore.session import Session @@ -106,8 +107,8 @@ def __call__(self): try: self._init() self._run() - except Exception, e: - print >>sys.stderr, "%s" % e + except Exception as e: + print("%s" % e, file=sys.stderr) sys.exit(1) ## @@ -179,7 +180,7 @@ def __init__(self, app, hostname, resolvername): def resolveGlobalIP(self, ns=False): resolver = dns.resolver.Resolver() resolver.nameservers = self._app._opts.dns if ns else self.resolveGlobalIP(True) - return map(lambda x: x.to_text(), resolver.query(self._resolvername if ns else self._hostname, 'A')) + return [x.to_text() for x in resolver.query(self._resolvername if ns else self._hostname, 'A')] class NETIFACES_GlobalIP_DetectionMethod(GlobalIP_DetectionMethod): def __init__(self, app): @@ -191,7 +192,7 @@ def resolveGlobalIP(self): except Exception as e: raise Exception("%s: no inet address found" % self._app._opts.iface) - return map(lambda x: x['addr'], inet) + return [x['addr'] for x in inet] def _pre_init(self): super(R53UpdateApp, self)._pre_init() @@ -270,7 +271,7 @@ def __get_records_from_host(self, fqdn): try: response = resolver.query(fqdn, 'A') - results = map(lambda x: x.to_text(), response) + results = [x.to_text() for x in response] except dns.resolver.NXDOMAIN: pass except dns.resolver.Timeout: @@ -326,16 +327,16 @@ def _run(self): self.logger.debug('route53 zone info is up to date') def show_version(self): - print >>sys.stderr, "Copyrights (c)2014 Takuya Sawada All rights reserved." - print >>sys.stderr, "Route53Update Dynamic DNS Updater v%s" % get_distribution("r53update").version + print("Copyrights (c)2014 Takuya Sawada All rights reserved.", file=sys.stderr) + print("Route53Update Dynamic DNS Updater v%s" % get_distribution("r53update").version, file=sys.stderr) def main(): try: R53UpdateApp(sys.argv)() - except Exception, e: - print >>sys.stderr, "%s" % e + except Exception as e: + print("%s" % e, file=sys.stderr) sys.exit(1) if __name__ == '__main__': From 9415c031931be9d668d2c28ed3e285922ff2345e Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Thu, 1 Jun 2017 00:12:42 +0900 Subject: [PATCH 03/18] [mod] remove no longer needed import --- r53update/r53update.py | 1 - 1 file changed, 1 deletion(-) diff --git a/r53update/r53update.py b/r53update/r53update.py index 15e648f..4168e2c 100755 --- a/r53update/r53update.py +++ b/r53update/r53update.py @@ -30,7 +30,6 @@ import logging import logging.handlers import netifaces -import exceptions try: import argcomplete From 09d1eef9dbc65b544bf251366fc7e47edb466ff1 Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Wed, 31 May 2017 23:15:41 +0900 Subject: [PATCH 04/18] [mod] replace implicit relative import with explicit one --- r53update/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r53update/__init__.py b/r53update/__init__.py index ed7ca21..03fcae0 100644 --- a/r53update/__init__.py +++ b/r53update/__init__.py @@ -1,3 +1,3 @@ __all__ = ['main'] -from r53update import main +from .r53update import main From 0493883006a51e5c0d75e945c24217a34b7f6cb9 Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Wed, 31 May 2017 23:44:06 +0900 Subject: [PATCH 05/18] [mod] replace obsolete `urllib2` with compatible `six.moves.urllib` library --- r53update/r53update.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/r53update/r53update.py b/r53update/r53update.py index 4168e2c..9154b5d 100755 --- a/r53update/r53update.py +++ b/r53update/r53update.py @@ -21,10 +21,10 @@ from boto.route53.record import ResourceRecordSets from botocore.session import Session from pkg_resources import get_distribution +from six.moves.urllib import request import sys import argparse -import urllib2 import dns.resolver import dns.exception import logging @@ -168,7 +168,7 @@ def __init__(self, app, url): self._url = url def resolveGlobalIP(self): - return urllib2.urlopen(self._url).read().rstrip() + return request.urlopen(self._url).read().rstrip() class DNS_GlobalIP_DetectionMethod(GlobalIP_DetectionMethod): def __init__(self, app, hostname, resolvername): From b0aeb2a4535762461de4da3eeecf791e9315521a Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Thu, 1 Jun 2017 00:21:49 +0900 Subject: [PATCH 06/18] [mod] add python3 to the classifiers list of setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index b2cae31..9f0eba9 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ 'Operating System :: POSIX', 'Programming Language :: Python', 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', 'Topic :: System :: Networking', 'Topic :: Utilities' ], From 0afdb5f2c6ae633ba446e8834824c355d772aea9 Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Thu, 1 Jun 2017 12:14:31 +0900 Subject: [PATCH 07/18] [add] add test code test_ctx.py --- r53update/test/__init__.py | 0 r53update/test/test_ctx.py | 160 +++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 r53update/test/__init__.py create mode 100644 r53update/test/test_ctx.py diff --git a/r53update/test/__init__.py b/r53update/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/r53update/test/test_ctx.py b/r53update/test/test_ctx.py new file mode 100644 index 0000000..def05d5 --- /dev/null +++ b/r53update/test/test_ctx.py @@ -0,0 +1,160 @@ +#!/bin/env python +# -*- coding: utf-8 -*- +# +# R53Update Dynamic DNS Updater +# (C)2014 Takuya Sawada All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from ..r53update import R53UpdateApp +from boto.route53.connection import Route53Connection +from botocore import exceptions + +import unittest +import string +import random +import mock +import os +import io + +class TestAppContext(unittest.TestCase): + def setUp(self): + # keep original function for reference from mock function + self.__open__ = open + self.__isfile__ = os.path.isfile + + try: + import __builtin__ + self.builtins = '__builtin__' + except: + self.builtins = 'builtins' + + # key-pair test data + self.profile = { + 'env': (self.randomstr(20), self.randomstr(40)), + 'default': (self.randomstr(20), self.randomstr(40)), + 'test': (self.randomstr(20), self.randomstr(40)) + } + + def randomstr(self, length): + return ''.join([random.choice(string.digits + string.ascii_uppercase) for i in range(12)]) + + ## + # boto core checks if config file exists before load it. + # in order to use mock file instead of actual config file, + # we must use this mock function which always return True + # if specified path is the config file. + # + # Ref) https://github.com/boto/botocore/blob/develop/botocore/configloader.py + # + def mock_isfile(self, path): + if path == os.path.expanduser('~/.aws/config'): + return True + else: + return self.__isfile__(path) + + def mock_file(self, path, *args, **kwargs): + if path == os.path.expanduser('~/.aws/config'): + return io.StringIO( + u"[default]\n" + u"aws_access_key_id = %s\n" + u"aws_secret_access_key = %s\n" + u"region = ap-northeast-1\n" + u"output = json\n" + u"\n" + u"[profile test]\n" + u"aws_access_key_id = %s\n" + u"aws_secret_access_key = %s\n" + u"region = ap-northeast-2\n" + u"output = json\n" + + % (self.profile['default'] + self.profile['test']) + ) + else: + self.__open__(path, *args) + + # check credential with environment variable + def test_env(self): + AWS_ACCESS_KEY, AWS_SECRET_KEY = self.profile['env'] + + ENV = { + 'AWS_ACCESS_KEY_ID': AWS_ACCESS_KEY, + 'AWS_SECRET_ACCESS_KEY': AWS_SECRET_KEY + } + + with mock.patch.dict('os.environ', ENV): + ctx = R53UpdateApp.Context() + + creds = ctx.session.get_credentials() + self.assertNotEqual(creds, None) + + self.assertEqual(creds.access_key, AWS_ACCESS_KEY) + self.assertEqual(creds.secret_key, AWS_SECRET_KEY) + + conn = ctx.getR53Connection() + self.assertNotEqual(conn, None) + self.assertIsInstance(conn, Route53Connection) + + # check credential with profile [default] (implicit) + def test_profile_default_implicit(self): + with (mock.patch('os.environ.get', return_value=None)): + with mock.patch('os.path.isfile', side_effect=self.mock_isfile): + with mock.patch('%s.open' % self.builtins, side_effect=self.mock_file): + AWS_ACCESS_KEY, AWS_SECRET_KEY = self.profile['default'] + ctx = R53UpdateApp.Context() + + creds = ctx.session.get_credentials() + self.assertNotEqual(creds, None) + + self.assertEqual(creds.access_key, AWS_ACCESS_KEY) + self.assertEqual(creds.secret_key, AWS_SECRET_KEY) + + self.assertIsInstance(ctx.getR53Connection(), Route53Connection) + + # check credential with profile [default] (explicit) + def test_profile_default_explicit(self): + with (mock.patch('os.environ.get', return_value=None)): + with mock.patch('os.path.isfile', side_effect=self.mock_isfile): + with mock.patch('%s.open' % self.builtins, side_effect=self.mock_file): + AWS_ACCESS_KEY, AWS_SECRET_KEY = self.profile['default'] + ctx = R53UpdateApp.Context('default') + + creds = ctx.session.get_credentials() + self.assertEqual(creds.access_key, AWS_ACCESS_KEY) + self.assertEqual(creds.secret_key, AWS_SECRET_KEY) + + self.assertIsInstance(ctx.getR53Connection(), Route53Connection) + + # check credential with profile [test] + def test_profile_test(self): + with (mock.patch('os.environ.get', return_value=None)): + with mock.patch('os.path.isfile', side_effect=self.mock_isfile): + with mock.patch('%s.open' % self.builtins, side_effect=self.mock_file): + AWS_ACCESS_KEY, AWS_SECRET_KEY = self.profile['test'] + ctx = R53UpdateApp.Context('test') + + creds = ctx.session.get_credentials() + self.assertEqual(creds.access_key, AWS_ACCESS_KEY) + self.assertEqual(creds.secret_key, AWS_SECRET_KEY) + + self.assertIsInstance(ctx.getR53Connection(), Route53Connection) + + # check not exist profile cause `ProfileNotFound` exception + def test_profile_not_found(self): + with (mock.patch('os.environ.get', return_value=None)): + with mock.patch('os.path.isfile', side_effect=self.mock_isfile): + with mock.patch('%s.open' % self.builtins, side_effect=self.mock_file): + with self.assertRaises(exceptions.ProfileNotFound): + ctx = R53UpdateApp.Context('notexist') + ctx.getR53Connection() + From 7722c38e55891713e339888d42c91a2a26393de2 Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Fri, 2 Jun 2017 00:36:58 +0900 Subject: [PATCH 08/18] [add] add test code test_method.py --- r53update/r53update.py | 2 +- r53update/test/test_method.py | 68 +++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 r53update/test/test_method.py diff --git a/r53update/r53update.py b/r53update/r53update.py index 15febd1..890316c 100755 --- a/r53update/r53update.py +++ b/r53update/r53update.py @@ -183,7 +183,7 @@ def __init__(self, app, hostname, resolvername): def resolveGlobalIP(self, ns=False): resolver = dns.resolver.Resolver() resolver.nameservers = self._app._opts.dns if ns else self.resolveGlobalIP(True) - return [x.to_text() for x in resolver.query(self._resolvername if ns else self._hostname, 'A')] + return [str(x) for x in resolver.query(self._resolvername if ns else self._hostname, 'A')] class NETIFACES_GlobalIP_DetectionMethod(GlobalIP_DetectionMethod): def __init__(self, app): diff --git a/r53update/test/test_method.py b/r53update/test/test_method.py new file mode 100644 index 0000000..97d6488 --- /dev/null +++ b/r53update/test/test_method.py @@ -0,0 +1,68 @@ +#!/bin/env python +# -*- coding: utf-8 -*- +# +# R53Update Dynamic DNS Updater +# (C)2014 Takuya Sawada All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from ..r53update import R53UpdateApp +import dns.resolver +import netifaces +import unittest +import random +import mock +import io + +class TestMethod(unittest.TestCase): + def randomIP(self): + return u'%d.%d.%d.%d' % tuple(int(random.random()*255) for _ in range(4)) + + def test_HTTP_Method(self): + IPADDR = self.randomIP() + SERVER = u'http://ifconfig.me/' + + with mock.patch('six.moves.urllib.request.urlopen', return_value=io.StringIO(IPADDR)) as urlopen: + method = R53UpdateApp.HTTP_GlobalIP_DetectionMethod(None, SERVER) + self.assertEqual(method.resolveGlobalIP(), IPADDR) + urlopen.assert_called_once_with(SERVER) + + def test_DNS_Method(self): + RETVAL = [ self.randomIP() for _ in range(4) ] + + app = mock.MagicMock() + app._opts.dns = ['8.8.8.8', '8.8.4.4'] + + with mock.patch.object(dns.resolver.Resolver, 'query', return_value=RETVAL) as query: + method = R53UpdateApp.DNS_GlobalIP_DetectionMethod(app, 'myip.opendns.com', 'resolver1.opendns.com') + self.assertListEqual(method.resolveGlobalIP(), RETVAL) + query.has_calls(['resolver1.opendns.com', 'myip.opendns.com']) + + def test_NETIFACES_Method(self): + RETVAL = { + netifaces.AF_INET: [ + { + 'addr': self.randomIP(), + 'netmask': self.randomIP(), + 'broadcast': self.randomIP() + } for _ in range(4) + ] + } + + app = mock.MagicMock() + app._opts.iface = 'eth0' + + with mock.patch('netifaces.ifaddresses', return_value=RETVAL) as ifaddresses: + method = R53UpdateApp.NETIFACES_GlobalIP_DetectionMethod(app) + self.assertListEqual(method.resolveGlobalIP(), [x['addr'] for x in RETVAL[netifaces.AF_INET]]) + ifaddresses.assert_called_once_with(app._opts.iface) From 4dc0c9ac1e05598638e69f870d4aacb1db45ab24 Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Thu, 1 Jun 2017 15:37:09 +0900 Subject: [PATCH 09/18] [add] add tox.ini to auto test --- tox.ini | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tox.ini diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..9a5661c --- /dev/null +++ b/tox.ini @@ -0,0 +1,10 @@ +[tox] +envlist = py27,py33,py34,py35,py36 +recreate = True + +[testenv] +deps = + nose + mock +commands = + nosetests From 4c1eac249d2c43e2c1062aef27ecb05e8d54b7c4 Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Fri, 2 Jun 2017 00:53:01 +0900 Subject: [PATCH 10/18] [add] add .travis.yml for continuous integration --- .travis.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c97184a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,20 @@ +language: python +sudo: false + +matrix: + include: + - python: '2.7' + env: TOXENV=py27 + - python: '3.3' + env: TOXENV=py33 + - python: '3.4' + env: TOXENV=py34 + - python: '3.5' + env: TOXENV=py35 + - python: '3.6' + env: TOXENV=py36 + +install: + - pip install tox +script: + - tox -e ${TOXENV} From fba788cb4d6fefb36bb8510d346f0c1501c480ae Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Fri, 2 Jun 2017 01:35:05 +0900 Subject: [PATCH 11/18] [add] add travis-ci badge and fix badge url link --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e3910ca..09d60f2 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ # R53Update command line utility +[![Github release](https://img.shields.io/github/release/tuntunkun/r53update.svg)](https://github.com/tuntunkun/r53update/releases) +[![Python version](https://img.shields.io/badge/python-2.7%2C%203.3%2C%203.4%2C%203.5%2C%203.6-green.svg)](#) +[![Build Status](https://travis-ci.org/tuntunkun/r53update.svg)](https://travis-ci.org/tuntunkun/r53update) +[![Hex.pm](https://img.shields.io/hexpm/l/plug.svg)](https://raw.githubusercontent.com/tuntunkun/r53update/master/LICENSE) R53Update is a command line utility for Amazon Route 53 which is one of the AWS (Amazon Web Services). This tools is useful to anyone who wants to operate server with dynamic IP. You can operate not only the server which is hosted on Amazon EC2 but also on-premise servers. -[![GitHub version](https://badge.fury.io/gh/tuntunkun%2Fr53update.svg)]() [![Hex.pm](https://img.shields.io/hexpm/l/plug.svg)]() - ## Requirements From af803eb37b23dae5f174332d8f25d5662dbc602a Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Fri, 2 Jun 2017 16:12:52 +0900 Subject: [PATCH 12/18] [mod] migrade aws sdk from 'boto' to 'boto3' --- r53update/r53update.py | 72 ++++++++++++++++++++++++++++-------------- setup.py | 3 +- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/r53update/r53update.py b/r53update/r53update.py index 890316c..8d56aae 100755 --- a/r53update/r53update.py +++ b/r53update/r53update.py @@ -17,9 +17,7 @@ # limitations under the License. # from __future__ import print_function -from boto.route53.connection import Route53Connection -from boto.route53.record import ResourceRecordSets -from botocore.session import Session +from boto3.session import Session from pkg_resources import get_distribution from six.moves.urllib import request @@ -117,18 +115,14 @@ class R53UpdateApp(App): # Context class Context(object): def __init__(self, profile=None): - self.session = Session() - self.session.profile = profile + self.session = Session(profile_name=profile) - def getR53Connection(self): + def getR53Client(self): credential = self.session.get_credentials() if not credential: raise RuntimeError("failed to get aws credential") - return Route53Connection( - credential.access_key, - credential.secret_key - ) + return self.session.client('route53') ## # Argument Completer @@ -262,6 +256,9 @@ def _post_init(self, opts): raise Exception("interface name '%s' not found" % opts.iface) self._opts.method = 'localhost' + if not opts.zone.endswith('.'): + opts.zone += '.' + def __get_global_ip(self): self.logger.debug('resolving global ip adreess with \'%s\'', self._opts.method) gips = self._gipmethods[self._opts.method].resolveGlobalIP() @@ -289,22 +286,41 @@ def __get_records_from_host(self, fqdn): def __update_r53_record(self, zone_name, host_name, gips): fqdn = '%s.%s' % (host_name, zone_name) - conn = self.ctx.getR53Connection() - zone = conn.get_zone(zone_name) - - if zone is None: - raise Exception("zone '%s' not found" % zone_name) - self.logger.debug('R53 zoneid: %s' % zone.id) + r53= self.ctx.getR53Client() - changes = ResourceRecordSets(conn, zone.id, '') - change = changes.add_change('UPSERT', fqdn, 'A', self._opts.ttl) + zones = r53.list_hosted_zones().get('HostedZones', []) + zone_id = None - for gip in gips: - change.add_value(gip) - - changes.commit() + for zone in zones: + if zone['Name'] == zone_name: + zone_id = zone['Id'] + break + + if zone_id is None: + raise Exception("zone '%s' not found" % zone_name) + self.logger.debug('R53 zoneid: %s' % zone_id) + + r53.change_resource_record_sets( + HostedZoneId = zone_id, + ChangeBatch = { + 'Comment': 'auto update with r53update version v%s' % self.version, + 'Changes': [{ + 'Action': 'UPSERT', + 'ResourceRecordSet': { + 'Name': fqdn, + 'Type': 'A', + 'TTL': self._opts.ttl, + 'ResourceRecords': [ + { + 'Value': ip + } for ip in gips + ] + } + }] + } + ) - self.logger.info('update A records of \'%s\' with \'%s\'' % (fqdn, gip)) + self.logger.info('update A records of "%s" with %s' % (fqdn, gips)) def _run(self): fqdn = '%s.%s' % (self._opts.host, self._opts.zone) @@ -329,9 +345,17 @@ def _run(self): else: self.logger.debug('route53 zone info is up to date') + @property + def version(self): + pass + + @version.getter + def version(self): + return get_distribution('r53update').version + def show_version(self): print("Copyrights (c)2014 Takuya Sawada All rights reserved.", file=sys.stderr) - print("Route53Update Dynamic DNS Updater v%s" % get_distribution("r53update").version, file=sys.stderr) + print("Route53Update Dynamic DNS Updater v%s" % self.version, file=sys.stderr) diff --git a/setup.py b/setup.py index b43e673..b123a3a 100644 --- a/setup.py +++ b/setup.py @@ -30,8 +30,7 @@ packages = ['r53update'], install_requires = [ 'argparse2==0.5.0a1', - 'boto==2.36.0', - 'awscli==1.7.15', + 'boto3==1.4.4', 'dnspython==1.14.0', 'netifaces==0.10.5' ], From e480ece14382075efbd59458ef04dfcf2387d88e Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Fri, 2 Jun 2017 16:31:11 +0900 Subject: [PATCH 13/18] [mod] fix test code --- r53update/test/test_ctx.py | 62 +++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/r53update/test/test_ctx.py b/r53update/test/test_ctx.py index def05d5..20a3309 100644 --- a/r53update/test/test_ctx.py +++ b/r53update/test/test_ctx.py @@ -17,7 +17,6 @@ # limitations under the License. # from ..r53update import R53UpdateApp -from boto.route53.connection import Route53Connection from botocore import exceptions import unittest @@ -81,7 +80,7 @@ def mock_file(self, path, *args, **kwargs): % (self.profile['default'] + self.profile['test']) ) else: - self.__open__(path, *args) + return self.__open__(path, *args, **kwargs) # check credential with environment variable def test_env(self): @@ -101,53 +100,48 @@ def test_env(self): self.assertEqual(creds.access_key, AWS_ACCESS_KEY) self.assertEqual(creds.secret_key, AWS_SECRET_KEY) - conn = ctx.getR53Connection() - self.assertNotEqual(conn, None) - self.assertIsInstance(conn, Route53Connection) + self.assertNotEqual(ctx.getR53Client(), None) # check credential with profile [default] (implicit) def test_profile_default_implicit(self): - with (mock.patch('os.environ.get', return_value=None)): - with mock.patch('os.path.isfile', side_effect=self.mock_isfile): - with mock.patch('%s.open' % self.builtins, side_effect=self.mock_file): - AWS_ACCESS_KEY, AWS_SECRET_KEY = self.profile['default'] - ctx = R53UpdateApp.Context() + with mock.patch('os.path.isfile', side_effect=self.mock_isfile): + with mock.patch('%s.open' % self.builtins, side_effect=self.mock_file): + AWS_ACCESS_KEY, AWS_SECRET_KEY = self.profile['default'] + ctx = R53UpdateApp.Context() - creds = ctx.session.get_credentials() - self.assertNotEqual(creds, None) + creds = ctx.session.get_credentials() + self.assertNotEqual(creds, None) - self.assertEqual(creds.access_key, AWS_ACCESS_KEY) - self.assertEqual(creds.secret_key, AWS_SECRET_KEY) + self.assertEqual(creds.access_key, AWS_ACCESS_KEY) + self.assertEqual(creds.secret_key, AWS_SECRET_KEY) - self.assertIsInstance(ctx.getR53Connection(), Route53Connection) + self.assertNotEqual(ctx.getR53Client(), None) # check credential with profile [default] (explicit) def test_profile_default_explicit(self): - with (mock.patch('os.environ.get', return_value=None)): - with mock.patch('os.path.isfile', side_effect=self.mock_isfile): - with mock.patch('%s.open' % self.builtins, side_effect=self.mock_file): - AWS_ACCESS_KEY, AWS_SECRET_KEY = self.profile['default'] - ctx = R53UpdateApp.Context('default') + with mock.patch('os.path.isfile', side_effect=self.mock_isfile): + with mock.patch('%s.open' % self.builtins, side_effect=self.mock_file): + AWS_ACCESS_KEY, AWS_SECRET_KEY = self.profile['default'] + ctx = R53UpdateApp.Context('default') - creds = ctx.session.get_credentials() - self.assertEqual(creds.access_key, AWS_ACCESS_KEY) - self.assertEqual(creds.secret_key, AWS_SECRET_KEY) + creds = ctx.session.get_credentials() + self.assertEqual(creds.access_key, AWS_ACCESS_KEY) + self.assertEqual(creds.secret_key, AWS_SECRET_KEY) - self.assertIsInstance(ctx.getR53Connection(), Route53Connection) + self.assertNotEqual(ctx.getR53Client(), None) # check credential with profile [test] def test_profile_test(self): - with (mock.patch('os.environ.get', return_value=None)): - with mock.patch('os.path.isfile', side_effect=self.mock_isfile): - with mock.patch('%s.open' % self.builtins, side_effect=self.mock_file): - AWS_ACCESS_KEY, AWS_SECRET_KEY = self.profile['test'] - ctx = R53UpdateApp.Context('test') + with mock.patch('os.path.isfile', side_effect=self.mock_isfile): + with mock.patch('%s.open' % self.builtins, side_effect=self.mock_file): + AWS_ACCESS_KEY, AWS_SECRET_KEY = self.profile['test'] + ctx = R53UpdateApp.Context('test') - creds = ctx.session.get_credentials() - self.assertEqual(creds.access_key, AWS_ACCESS_KEY) - self.assertEqual(creds.secret_key, AWS_SECRET_KEY) + creds = ctx.session.get_credentials() + self.assertEqual(creds.access_key, AWS_ACCESS_KEY) + self.assertEqual(creds.secret_key, AWS_SECRET_KEY) - self.assertIsInstance(ctx.getR53Connection(), Route53Connection) + self.assertNotEqual(ctx.getR53Client(), None) # check not exist profile cause `ProfileNotFound` exception def test_profile_not_found(self): @@ -156,5 +150,5 @@ def test_profile_not_found(self): with mock.patch('%s.open' % self.builtins, side_effect=self.mock_file): with self.assertRaises(exceptions.ProfileNotFound): ctx = R53UpdateApp.Context('notexist') - ctx.getR53Connection() + ctx.getR53Client() From ed9c0ccc6f3a194d9a208aef55814ab88c6fd09d Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Fri, 2 Jun 2017 20:13:20 +0900 Subject: [PATCH 14/18] [mod] update obsolete dependencies --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b123a3a..6fd7289 100644 --- a/setup.py +++ b/setup.py @@ -31,8 +31,8 @@ install_requires = [ 'argparse2==0.5.0a1', 'boto3==1.4.4', - 'dnspython==1.14.0', - 'netifaces==0.10.5' + 'dnspython==1.15.0', + 'netifaces==0.10.6' ], classifiers = [ 'Development Status :: 4 - Beta', From e9fa723e1e9b605862c9f05d6d3122c6dbe2bdbc Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Fri, 2 Jun 2017 20:22:36 +0900 Subject: [PATCH 15/18] [add] add require.io badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 09d60f2..f030dae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # R53Update command line utility [![Github release](https://img.shields.io/github/release/tuntunkun/r53update.svg)](https://github.com/tuntunkun/r53update/releases) [![Python version](https://img.shields.io/badge/python-2.7%2C%203.3%2C%203.4%2C%203.5%2C%203.6-green.svg)](#) +[![Requirements Status](https://requires.io/github/tuntunkun/r53update/requirements.svg)](https://requires.io/github/tuntunkun/r53update/requirements) [![Build Status](https://travis-ci.org/tuntunkun/r53update.svg)](https://travis-ci.org/tuntunkun/r53update) [![Hex.pm](https://img.shields.io/hexpm/l/plug.svg)](https://raw.githubusercontent.com/tuntunkun/r53update/master/LICENSE) From 9019fb5afc2ead27d56147c6b842eaa9863c2b1b Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Fri, 2 Jun 2017 20:23:15 +0900 Subject: [PATCH 16/18] [mod] change branch of travis-ci badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f030dae..e52f3e1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Github release](https://img.shields.io/github/release/tuntunkun/r53update.svg)](https://github.com/tuntunkun/r53update/releases) [![Python version](https://img.shields.io/badge/python-2.7%2C%203.3%2C%203.4%2C%203.5%2C%203.6-green.svg)](#) [![Requirements Status](https://requires.io/github/tuntunkun/r53update/requirements.svg)](https://requires.io/github/tuntunkun/r53update/requirements) -[![Build Status](https://travis-ci.org/tuntunkun/r53update.svg)](https://travis-ci.org/tuntunkun/r53update) +[![Build Status](https://travis-ci.org/tuntunkun/r53update.svg?branch=develop)](https://travis-ci.org/tuntunkun/r53update) [![Hex.pm](https://img.shields.io/hexpm/l/plug.svg)](https://raw.githubusercontent.com/tuntunkun/r53update/master/LICENSE) R53Update is a command line utility for Amazon Route 53 which is one of the AWS (Amazon Web Services). This tools is useful to anyone who wants to operate server with dynamic IP. You can operate not only the server which is hosted on Amazon EC2 but also on-premise servers. From 94a35cb818b482647edda10b6475184ef3661b7f Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Fri, 2 Jun 2017 20:32:27 +0900 Subject: [PATCH 17/18] [add] add .gitignore file --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..930244a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.tox/ +local/ + +*.egg-info/ +*.pyc From 4cbe9bc1529b5ff7939683861379d3c7851ca9f7 Mon Sep 17 00:00:00 2001 From: Takuya Sawada Date: Fri, 2 Jun 2017 20:35:07 +0900 Subject: [PATCH 18/18] [mod] change version number to '0.6.0' --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6fd7289..ec3f9c7 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ setup( name = 'r53update', - version='0.5.2', + version='0.6.0', description='R53Update Dynamic DNS Updater', author='Takuya Sawada', author_email='takuya@tuntunkun.com',