From e18dfe1c19550b6a8128c2a9eac072f3b0649024 Mon Sep 17 00:00:00 2001 From: Mark Mercado Date: Fri, 13 Sep 2019 08:09:21 -0400 Subject: [PATCH 01/10] switch from X-Auth-{Email,Key} to Authorization for Cloudflare --- .gitignore | 1 + CONTRIBUTING.md | 1 + octodns/provider/cloudflare.py | 12 +++--- tests/test_octodns_provider_cloudflare.py | 46 +++++++++++------------ 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index 1efa084..6af64e8 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ nosetests.xml octodns.egg-info/ output/ tmp/ +/.vscode/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0fc2f2c..accabd5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,6 +43,7 @@ Here are a few things you can do that will increase the likelihood of your pull ``` pip install -r requirements.txt pip install -r requirements-dev.txt +python setup.py install ``` ## License note diff --git a/octodns/provider/cloudflare.py b/octodns/provider/cloudflare.py index 881c2fd..9652322 100644 --- a/octodns/provider/cloudflare.py +++ b/octodns/provider/cloudflare.py @@ -37,8 +37,6 @@ class CloudflareProvider(BaseProvider): cloudflare: class: octodns.provider.cloudflare.CloudflareProvider - # Your Cloudflare account email address (required) - email: dns-manager@example.com # The api key (required) token: foo # Import CDN enabled records as CNAME to {}.cdn.cloudflare.net. Records @@ -66,16 +64,16 @@ class CloudflareProvider(BaseProvider): MIN_TTL = 120 TIMEOUT = 15 - def __init__(self, id, email, token, cdn=False, *args, **kwargs): + def __init__(self, id, token, cdn=False, *args, **kwargs): self.log = getLogger('CloudflareProvider[{}]'.format(id)) - self.log.debug('__init__: id=%s, email=%s, token=***, cdn=%s', id, - email, cdn) + self.log.debug('__init__: id=%s, token=***, cdn=%s', id, cdn) super(CloudflareProvider, self).__init__(id, *args, **kwargs) sess = Session() + # https://api.cloudflare.com/#getting-started-requests + # https://tools.ietf.org/html/rfc6750#section-2.1 sess.headers.update({ - 'X-Auth-Email': email, - 'X-Auth-Key': token, + 'Authorization': 'Bearer ' + token, }) self.cdn = cdn self._sess = sess diff --git a/tests/test_octodns_provider_cloudflare.py b/tests/test_octodns_provider_cloudflare.py index 5c6d503..2727fc8 100644 --- a/tests/test_octodns_provider_cloudflare.py +++ b/tests/test_octodns_provider_cloudflare.py @@ -51,7 +51,7 @@ class TestCloudflareProvider(TestCase): empty = {'result': [], 'result_info': {'count': 0, 'per_page': 0}} def test_populate(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') # Bad requests with requests_mock() as mock: @@ -71,7 +71,7 @@ class TestCloudflareProvider(TestCase): with requests_mock() as mock: mock.get(ANY, status_code=403, text='{"success":false,"errors":[{"code":9103,' - '"message":"Unknown X-Auth-Key or X-Auth-Email"}],' + '"message":"Unknown Authorization header"}],' '"messages":[],"result":null}') with self.assertRaises(Exception) as ctx: @@ -79,7 +79,7 @@ class TestCloudflareProvider(TestCase): provider.populate(zone) self.assertEquals('CloudflareAuthenticationError', type(ctx.exception).__name__) - self.assertEquals('Unknown X-Auth-Key or X-Auth-Email', + self.assertEquals('Unknown Authorization header', ctx.exception.message) # Bad auth, unknown resp @@ -160,7 +160,7 @@ class TestCloudflareProvider(TestCase): self.assertEquals(12, len(again.records)) def test_apply(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') provider._request = Mock() @@ -315,7 +315,7 @@ class TestCloudflareProvider(TestCase): ]) def test_update_add_swap(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') provider.zone_records = Mock(return_value=[ { @@ -422,7 +422,7 @@ class TestCloudflareProvider(TestCase): def test_update_delete(self): # We need another run so that we can delete, we can't both add and # delete in one go b/c of swaps - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') provider.zone_records = Mock(return_value=[ { @@ -510,7 +510,7 @@ class TestCloudflareProvider(TestCase): ]) def test_srv(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') zone = Zone('unit.tests.', []) # SRV record not under a sub-domain @@ -568,7 +568,7 @@ class TestCloudflareProvider(TestCase): }, list(srv_record_with_sub_contents)[0]) def test_alias(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') # A CNAME for us to transform to ALIAS provider.zone_records = Mock(return_value=[ @@ -611,7 +611,7 @@ class TestCloudflareProvider(TestCase): }, list(contents)[0]) def test_gen_key(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') for expected, data in ( ('foo.bar.com.', { @@ -644,7 +644,7 @@ class TestCloudflareProvider(TestCase): self.assertEqual(expected, provider._gen_key(data)) def test_cdn(self): - provider = CloudflareProvider('test', 'email', 'token', True) + provider = CloudflareProvider('test', 'token', True) # A CNAME for us to transform to ALIAS provider.zone_records = Mock(return_value=[ @@ -783,7 +783,7 @@ class TestCloudflareProvider(TestCase): self.assertEquals(1, len(plan.changes)) def test_cdn_alias(self): - provider = CloudflareProvider('test', 'email', 'token', True) + provider = CloudflareProvider('test', 'token', True) # A CNAME for us to transform to ALIAS provider.zone_records = Mock(return_value=[ @@ -828,7 +828,7 @@ class TestCloudflareProvider(TestCase): self.assertEquals(False, hasattr(plan, 'changes')) def test_unproxiabletype_recordfor_returnsrecordwithnocloudflare(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') name = "unit.tests" _type = "NS" zone_records = [ @@ -859,7 +859,7 @@ class TestCloudflareProvider(TestCase): self.assertFalse('cloudflare' in record._octodns) def test_proxiabletype_recordfor_retrecordwithcloudflareunproxied(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') name = "multi.unit.tests" _type = "AAAA" zone_records = [ @@ -890,7 +890,7 @@ class TestCloudflareProvider(TestCase): self.assertFalse(record._octodns['cloudflare']['proxied']) def test_proxiabletype_recordfor_returnsrecordwithcloudflareproxied(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') name = "multi.unit.tests" _type = "AAAA" zone_records = [ @@ -921,7 +921,7 @@ class TestCloudflareProvider(TestCase): self.assertTrue(record._octodns['cloudflare']['proxied']) def test_proxiedrecordandnewttl_includechange_returnsfalse(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') zone = Zone('unit.tests.', []) existing = set_record_proxied_flag( Record.new(zone, 'a', { @@ -942,7 +942,7 @@ class TestCloudflareProvider(TestCase): self.assertFalse(include_change) def test_unproxiabletype_gendata_returnsnoproxied(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') zone = Zone('unit.tests.', []) record = Record.new(zone, 'a', { 'ttl': 3600, @@ -955,7 +955,7 @@ class TestCloudflareProvider(TestCase): self.assertFalse('proxied' in data) def test_proxiabletype_gendata_returnsunproxied(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') zone = Zone('unit.tests.', []) record = set_record_proxied_flag( Record.new(zone, 'a', { @@ -970,7 +970,7 @@ class TestCloudflareProvider(TestCase): self.assertFalse(data['proxied']) def test_proxiabletype_gendata_returnsproxied(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') zone = Zone('unit.tests.', []) record = set_record_proxied_flag( Record.new(zone, 'a', { @@ -985,7 +985,7 @@ class TestCloudflareProvider(TestCase): self.assertTrue(data['proxied']) def test_createrecord_extrachanges_returnsemptylist(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') provider.zone_records = Mock(return_value=[]) existing = Zone('unit.tests.', []) provider.populate(existing) @@ -1017,7 +1017,7 @@ class TestCloudflareProvider(TestCase): self.assertFalse(extra_changes) def test_updaterecord_extrachanges_returnsemptylist(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') provider.zone_records = Mock(return_value=[ { "id": "fc12ab34cd5611334422ab3322997642", @@ -1067,7 +1067,7 @@ class TestCloudflareProvider(TestCase): self.assertFalse(extra_changes) def test_deleterecord_extrachanges_returnsemptylist(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') provider.zone_records = Mock(return_value=[ { "id": "fc12ab34cd5611334422ab3322997642", @@ -1099,7 +1099,7 @@ class TestCloudflareProvider(TestCase): self.assertFalse(extra_changes) def test_proxify_extrachanges_returnsupdatelist(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') provider.zone_records = Mock(return_value=[ { "id": "fc12ab34cd5611334422ab3322997642", @@ -1155,7 +1155,7 @@ class TestCloudflareProvider(TestCase): ) def test_unproxify_extrachanges_returnsupdatelist(self): - provider = CloudflareProvider('test', 'email', 'token') + provider = CloudflareProvider('test', 'token') provider.zone_records = Mock(return_value=[ { "id": "fc12ab34cd5611334422ab3322997642", From f200d91e49b7686a0d271bf735c6fdcbf236410d Mon Sep 17 00:00:00 2001 From: Mark Mercado Date: Wed, 25 Sep 2019 16:56:01 -0400 Subject: [PATCH 02/10] don't impose editor things --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6af64e8..1efa084 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,3 @@ nosetests.xml octodns.egg-info/ output/ tmp/ -/.vscode/ From 16121be4de0e9209d93aa85ab2779da763aae4bd Mon Sep 17 00:00:00 2001 From: Mark Mercado Date: Wed, 25 Sep 2019 20:53:45 -0400 Subject: [PATCH 03/10] support email/token and just token for cloudflare --- octodns/provider/cloudflare.py | 19 +++++++--- tests/test_octodns_provider_cloudflare.py | 46 +++++++++++------------ 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/octodns/provider/cloudflare.py b/octodns/provider/cloudflare.py index 9652322..96f91fc 100644 --- a/octodns/provider/cloudflare.py +++ b/octodns/provider/cloudflare.py @@ -38,6 +38,8 @@ class CloudflareProvider(BaseProvider): cloudflare: class: octodns.provider.cloudflare.CloudflareProvider # The api key (required) + # Your Cloudflare account email address (required) + email: dns-manager@example.com (optional if using token) token: foo # Import CDN enabled records as CNAME to {}.cdn.cloudflare.net. Records # ending at .cdn.cloudflare.net. will be ignored when this provider is @@ -64,17 +66,24 @@ class CloudflareProvider(BaseProvider): MIN_TTL = 120 TIMEOUT = 15 - def __init__(self, id, token, cdn=False, *args, **kwargs): + def __init__(self, id, token, email=None, cdn=False, *args, **kwargs): self.log = getLogger('CloudflareProvider[{}]'.format(id)) - self.log.debug('__init__: id=%s, token=***, cdn=%s', id, cdn) + self.log.debug('__init__: id=%s, email=%s, token=***, cdn=%s', id, + email, cdn) super(CloudflareProvider, self).__init__(id, *args, **kwargs) sess = Session() # https://api.cloudflare.com/#getting-started-requests # https://tools.ietf.org/html/rfc6750#section-2.1 - sess.headers.update({ - 'Authorization': 'Bearer ' + token, - }) + if not email: + sess.headers.update({ + 'Authorization': 'Bearer %s'.format(token), + }) + else: + sess.headers.update({ + 'X-Auth-Email': email, + 'X-Auth-Key': token, + }) self.cdn = cdn self._sess = sess diff --git a/tests/test_octodns_provider_cloudflare.py b/tests/test_octodns_provider_cloudflare.py index 2727fc8..5c6d503 100644 --- a/tests/test_octodns_provider_cloudflare.py +++ b/tests/test_octodns_provider_cloudflare.py @@ -51,7 +51,7 @@ class TestCloudflareProvider(TestCase): empty = {'result': [], 'result_info': {'count': 0, 'per_page': 0}} def test_populate(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') # Bad requests with requests_mock() as mock: @@ -71,7 +71,7 @@ class TestCloudflareProvider(TestCase): with requests_mock() as mock: mock.get(ANY, status_code=403, text='{"success":false,"errors":[{"code":9103,' - '"message":"Unknown Authorization header"}],' + '"message":"Unknown X-Auth-Key or X-Auth-Email"}],' '"messages":[],"result":null}') with self.assertRaises(Exception) as ctx: @@ -79,7 +79,7 @@ class TestCloudflareProvider(TestCase): provider.populate(zone) self.assertEquals('CloudflareAuthenticationError', type(ctx.exception).__name__) - self.assertEquals('Unknown Authorization header', + self.assertEquals('Unknown X-Auth-Key or X-Auth-Email', ctx.exception.message) # Bad auth, unknown resp @@ -160,7 +160,7 @@ class TestCloudflareProvider(TestCase): self.assertEquals(12, len(again.records)) def test_apply(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') provider._request = Mock() @@ -315,7 +315,7 @@ class TestCloudflareProvider(TestCase): ]) def test_update_add_swap(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') provider.zone_records = Mock(return_value=[ { @@ -422,7 +422,7 @@ class TestCloudflareProvider(TestCase): def test_update_delete(self): # We need another run so that we can delete, we can't both add and # delete in one go b/c of swaps - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') provider.zone_records = Mock(return_value=[ { @@ -510,7 +510,7 @@ class TestCloudflareProvider(TestCase): ]) def test_srv(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') zone = Zone('unit.tests.', []) # SRV record not under a sub-domain @@ -568,7 +568,7 @@ class TestCloudflareProvider(TestCase): }, list(srv_record_with_sub_contents)[0]) def test_alias(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') # A CNAME for us to transform to ALIAS provider.zone_records = Mock(return_value=[ @@ -611,7 +611,7 @@ class TestCloudflareProvider(TestCase): }, list(contents)[0]) def test_gen_key(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') for expected, data in ( ('foo.bar.com.', { @@ -644,7 +644,7 @@ class TestCloudflareProvider(TestCase): self.assertEqual(expected, provider._gen_key(data)) def test_cdn(self): - provider = CloudflareProvider('test', 'token', True) + provider = CloudflareProvider('test', 'email', 'token', True) # A CNAME for us to transform to ALIAS provider.zone_records = Mock(return_value=[ @@ -783,7 +783,7 @@ class TestCloudflareProvider(TestCase): self.assertEquals(1, len(plan.changes)) def test_cdn_alias(self): - provider = CloudflareProvider('test', 'token', True) + provider = CloudflareProvider('test', 'email', 'token', True) # A CNAME for us to transform to ALIAS provider.zone_records = Mock(return_value=[ @@ -828,7 +828,7 @@ class TestCloudflareProvider(TestCase): self.assertEquals(False, hasattr(plan, 'changes')) def test_unproxiabletype_recordfor_returnsrecordwithnocloudflare(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') name = "unit.tests" _type = "NS" zone_records = [ @@ -859,7 +859,7 @@ class TestCloudflareProvider(TestCase): self.assertFalse('cloudflare' in record._octodns) def test_proxiabletype_recordfor_retrecordwithcloudflareunproxied(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') name = "multi.unit.tests" _type = "AAAA" zone_records = [ @@ -890,7 +890,7 @@ class TestCloudflareProvider(TestCase): self.assertFalse(record._octodns['cloudflare']['proxied']) def test_proxiabletype_recordfor_returnsrecordwithcloudflareproxied(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') name = "multi.unit.tests" _type = "AAAA" zone_records = [ @@ -921,7 +921,7 @@ class TestCloudflareProvider(TestCase): self.assertTrue(record._octodns['cloudflare']['proxied']) def test_proxiedrecordandnewttl_includechange_returnsfalse(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') zone = Zone('unit.tests.', []) existing = set_record_proxied_flag( Record.new(zone, 'a', { @@ -942,7 +942,7 @@ class TestCloudflareProvider(TestCase): self.assertFalse(include_change) def test_unproxiabletype_gendata_returnsnoproxied(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') zone = Zone('unit.tests.', []) record = Record.new(zone, 'a', { 'ttl': 3600, @@ -955,7 +955,7 @@ class TestCloudflareProvider(TestCase): self.assertFalse('proxied' in data) def test_proxiabletype_gendata_returnsunproxied(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') zone = Zone('unit.tests.', []) record = set_record_proxied_flag( Record.new(zone, 'a', { @@ -970,7 +970,7 @@ class TestCloudflareProvider(TestCase): self.assertFalse(data['proxied']) def test_proxiabletype_gendata_returnsproxied(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') zone = Zone('unit.tests.', []) record = set_record_proxied_flag( Record.new(zone, 'a', { @@ -985,7 +985,7 @@ class TestCloudflareProvider(TestCase): self.assertTrue(data['proxied']) def test_createrecord_extrachanges_returnsemptylist(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') provider.zone_records = Mock(return_value=[]) existing = Zone('unit.tests.', []) provider.populate(existing) @@ -1017,7 +1017,7 @@ class TestCloudflareProvider(TestCase): self.assertFalse(extra_changes) def test_updaterecord_extrachanges_returnsemptylist(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') provider.zone_records = Mock(return_value=[ { "id": "fc12ab34cd5611334422ab3322997642", @@ -1067,7 +1067,7 @@ class TestCloudflareProvider(TestCase): self.assertFalse(extra_changes) def test_deleterecord_extrachanges_returnsemptylist(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') provider.zone_records = Mock(return_value=[ { "id": "fc12ab34cd5611334422ab3322997642", @@ -1099,7 +1099,7 @@ class TestCloudflareProvider(TestCase): self.assertFalse(extra_changes) def test_proxify_extrachanges_returnsupdatelist(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') provider.zone_records = Mock(return_value=[ { "id": "fc12ab34cd5611334422ab3322997642", @@ -1155,7 +1155,7 @@ class TestCloudflareProvider(TestCase): ) def test_unproxify_extrachanges_returnsupdatelist(self): - provider = CloudflareProvider('test', 'token') + provider = CloudflareProvider('test', 'email', 'token') provider.zone_records = Mock(return_value=[ { "id": "fc12ab34cd5611334422ab3322997642", From e23579686537136b1c2fdd7f52dba430e278712b Mon Sep 17 00:00:00 2001 From: Mark Mercado Date: Sat, 28 Sep 2019 08:57:54 -0400 Subject: [PATCH 04/10] support and test old and new authn methods --- octodns/provider/cloudflare.py | 14 +- ...st_octodns_provider_cloudflare_no_email.py | 1211 +++++++++++++++++ 2 files changed, 1218 insertions(+), 7 deletions(-) create mode 100644 tests/test_octodns_provider_cloudflare_no_email.py diff --git a/octodns/provider/cloudflare.py b/octodns/provider/cloudflare.py index 96f91fc..491f84d 100644 --- a/octodns/provider/cloudflare.py +++ b/octodns/provider/cloudflare.py @@ -66,23 +66,23 @@ class CloudflareProvider(BaseProvider): MIN_TTL = 120 TIMEOUT = 15 - def __init__(self, id, token, email=None, cdn=False, *args, **kwargs): + def __init__(self, id, email=None, token=None, cdn=False, *args, **kwargs): self.log = getLogger('CloudflareProvider[{}]'.format(id)) self.log.debug('__init__: id=%s, email=%s, token=***, cdn=%s', id, email, cdn) super(CloudflareProvider, self).__init__(id, *args, **kwargs) sess = Session() - # https://api.cloudflare.com/#getting-started-requests - # https://tools.ietf.org/html/rfc6750#section-2.1 - if not email: + if email and token: sess.headers.update({ - 'Authorization': 'Bearer %s'.format(token), + 'X-Auth-Email': email, + 'X-Auth-Key': token, }) else: + # https://api.cloudflare.com/#getting-started-requests + # https://tools.ietf.org/html/rfc6750#section-2.1 sess.headers.update({ - 'X-Auth-Email': email, - 'X-Auth-Key': token, + 'Authorization': 'Bearer %s'.format(token), }) self.cdn = cdn self._sess = sess diff --git a/tests/test_octodns_provider_cloudflare_no_email.py b/tests/test_octodns_provider_cloudflare_no_email.py new file mode 100644 index 0000000..8e25fe2 --- /dev/null +++ b/tests/test_octodns_provider_cloudflare_no_email.py @@ -0,0 +1,1211 @@ +# +# +# + +from __future__ import absolute_import, division, print_function, \ + unicode_literals + +from mock import Mock, call +from os.path import dirname, join +from requests import HTTPError +from requests_mock import ANY, mock as requests_mock +from unittest import TestCase + +from octodns.record import Record, Update +from octodns.provider.base import Plan +from octodns.provider.cloudflare import CloudflareProvider +from octodns.provider.yaml import YamlProvider +from octodns.zone import Zone + + +def set_record_proxied_flag(record, proxied): + try: + record._octodns['cloudflare']['proxied'] = proxied + except KeyError: + record._octodns['cloudflare'] = { + 'proxied': proxied + } + + return record + + +class TestCloudflareProvider(TestCase): + expected = Zone('unit.tests.', []) + source = YamlProvider('test', join(dirname(__file__), 'config')) + source.populate(expected) + + # Our test suite differs a bit, add our NS and remove the simple one + expected.add_record(Record.new(expected, 'under', { + 'ttl': 3600, + 'type': 'NS', + 'values': [ + 'ns1.unit.tests.', + 'ns2.unit.tests.', + ] + })) + for record in list(expected.records): + if record.name == 'sub' and record._type == 'NS': + expected._remove_record(record) + break + + empty = {'result': [], 'result_info': {'count': 0, 'per_page': 0}} + + def test_populate(self): + provider = CloudflareProvider('test', 'token') + + # Bad requests + with requests_mock() as mock: + mock.get(ANY, status_code=400, + text='{"success":false,"errors":[{"code":1101,' + '"message":"request was invalid"}],' + '"messages":[],"result":null}') + + with self.assertRaises(Exception) as ctx: + zone = Zone('unit.tests.', []) + provider.populate(zone) + + self.assertEquals('CloudflareError', type(ctx.exception).__name__) + self.assertEquals('request was invalid', ctx.exception.message) + + # Bad auth + with requests_mock() as mock: + mock.get(ANY, status_code=403, + text='{"success":false,"errors":[{"code":9103,' + '"message":"Unknown X-Auth-Key or X-Auth-Email"}],' + '"messages":[],"result":null}') + + with self.assertRaises(Exception) as ctx: + zone = Zone('unit.tests.', []) + provider.populate(zone) + self.assertEquals('CloudflareAuthenticationError', + type(ctx.exception).__name__) + self.assertEquals('Unknown X-Auth-Key or X-Auth-Email', + ctx.exception.message) + + # Bad auth, unknown resp + with requests_mock() as mock: + mock.get(ANY, status_code=403, text='{}') + + with self.assertRaises(Exception) as ctx: + zone = Zone('unit.tests.', []) + provider.populate(zone) + self.assertEquals('CloudflareAuthenticationError', + type(ctx.exception).__name__) + self.assertEquals('Cloudflare error', ctx.exception.message) + + # General error + with requests_mock() as mock: + mock.get(ANY, status_code=502, text='Things caught fire') + + with self.assertRaises(HTTPError) as ctx: + zone = Zone('unit.tests.', []) + provider.populate(zone) + self.assertEquals(502, ctx.exception.response.status_code) + + # Non-existent zone doesn't populate anything + with requests_mock() as mock: + mock.get(ANY, status_code=200, json=self.empty) + + zone = Zone('unit.tests.', []) + provider.populate(zone) + self.assertEquals(set(), zone.records) + + # re-populating the same non-existent zone uses cache and makes no + # calls + again = Zone('unit.tests.', []) + provider.populate(again) + self.assertEquals(set(), again.records) + + # bust zone cache + provider._zones = None + + # existing zone with data + with requests_mock() as mock: + base = 'https://api.cloudflare.com/client/v4/zones' + + # zones + with open('tests/fixtures/cloudflare-zones-page-1.json') as fh: + mock.get('{}?page=1'.format(base), status_code=200, + text=fh.read()) + with open('tests/fixtures/cloudflare-zones-page-2.json') as fh: + mock.get('{}?page=2'.format(base), status_code=200, + text=fh.read()) + mock.get('{}?page=3'.format(base), status_code=200, + json={'result': [], 'result_info': {'count': 0, + 'per_page': 0}}) + + # records + base = '{}/234234243423aaabb334342aaa343435/dns_records' \ + .format(base) + with open('tests/fixtures/cloudflare-dns_records-' + 'page-1.json') as fh: + mock.get('{}?page=1'.format(base), status_code=200, + text=fh.read()) + with open('tests/fixtures/cloudflare-dns_records-' + 'page-2.json') as fh: + mock.get('{}?page=2'.format(base), status_code=200, + text=fh.read()) + + zone = Zone('unit.tests.', []) + provider.populate(zone) + self.assertEquals(12, len(zone.records)) + + changes = self.expected.changes(zone, provider) + + self.assertEquals(0, len(changes)) + + # re-populating the same zone/records comes out of cache, no calls + again = Zone('unit.tests.', []) + provider.populate(again) + self.assertEquals(12, len(again.records)) + + def test_apply(self): + provider = CloudflareProvider('test', 'token') + + provider._request = Mock() + + provider._request.side_effect = [ + self.empty, # no zones + { + 'result': { + 'id': 42, + } + }, # zone create + ] + [None] * 20 # individual record creates + + # non-existent zone, create everything + plan = provider.plan(self.expected) + self.assertEquals(12, len(plan.changes)) + self.assertEquals(12, provider.apply(plan)) + self.assertFalse(plan.exists) + + provider._request.assert_has_calls([ + # created the domain + call('POST', '/zones', data={ + 'jump_start': False, + 'name': 'unit.tests' + }), + # created at least one of the record with expected data + call('POST', '/zones/42/dns_records', data={ + 'content': 'ns1.unit.tests.', + 'type': 'NS', + 'name': 'under.unit.tests', + 'ttl': 3600 + }), + # make sure semicolons are not escaped when sending data + call('POST', '/zones/42/dns_records', data={ + 'content': 'v=DKIM1;k=rsa;s=email;h=sha256;' + 'p=A/kinda+of/long/string+with+numb3rs', + 'type': 'TXT', + 'name': 'txt.unit.tests', + 'ttl': 600 + }), + ], True) + # expected number of total calls + self.assertEquals(22, provider._request.call_count) + + provider._request.reset_mock() + + provider.zone_records = Mock(return_value=[ + { + "id": "fc12ab34cd5611334422ab3322997653", + "type": "A", + "name": "www.unit.tests", + "content": "1.2.3.4", + "proxiable": True, + "proxied": False, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + }, + { + "id": "fc12ab34cd5611334422ab3322997654", + "type": "A", + "name": "www.unit.tests", + "content": "2.2.3.4", + "proxiable": True, + "proxied": False, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:44.030044Z", + "created_on": "2017-03-11T18:01:44.030044Z", + "meta": { + "auto_added": False + } + }, + { + "id": "fc12ab34cd5611334422ab3322997655", + "type": "A", + "name": "nc.unit.tests", + "content": "3.2.3.4", + "proxiable": True, + "proxied": False, + "ttl": 120, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:44.030044Z", + "created_on": "2017-03-11T18:01:44.030044Z", + "meta": { + "auto_added": False + } + }, + { + "id": "fc12ab34cd5611334422ab3322997655", + "type": "A", + "name": "ttl.unit.tests", + "content": "4.2.3.4", + "proxiable": True, + "proxied": False, + "ttl": 600, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:44.030044Z", + "created_on": "2017-03-11T18:01:44.030044Z", + "meta": { + "auto_added": False + } + }, + ]) + + # we don't care about the POST/create return values + provider._request.return_value = {} + provider._request.side_effect = None + + wanted = Zone('unit.tests.', []) + wanted.add_record(Record.new(wanted, 'nc', { + 'ttl': 60, # TTL is below their min + 'type': 'A', + 'value': '3.2.3.4' + })) + wanted.add_record(Record.new(wanted, 'ttl', { + 'ttl': 300, # TTL change + 'type': 'A', + 'value': '3.2.3.4' + })) + + plan = provider.plan(wanted) + # only see the delete & ttl update, below min-ttl is filtered out + self.assertEquals(2, len(plan.changes)) + self.assertEquals(2, provider.apply(plan)) + self.assertTrue(plan.exists) + # creates a the new value and then deletes all the old + provider._request.assert_has_calls([ + call('PUT', '/zones/42/dns_records/' + 'fc12ab34cd5611334422ab3322997655', data={ + 'content': '3.2.3.4', + 'type': 'A', + 'name': 'ttl.unit.tests', + 'proxied': False, + 'ttl': 300 + }), + call('DELETE', '/zones/ff12ab34cd5611334422ab3322997650/' + 'dns_records/fc12ab34cd5611334422ab3322997653'), + call('DELETE', '/zones/ff12ab34cd5611334422ab3322997650/' + 'dns_records/fc12ab34cd5611334422ab3322997654') + ]) + + def test_update_add_swap(self): + provider = CloudflareProvider('test', 'token') + + provider.zone_records = Mock(return_value=[ + { + "id": "fc12ab34cd5611334422ab3322997653", + "type": "A", + "name": "a.unit.tests", + "content": "1.1.1.1", + "proxiable": True, + "proxied": False, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + }, + { + "id": "fc12ab34cd5611334422ab3322997654", + "type": "A", + "name": "a.unit.tests", + "content": "2.2.2.2", + "proxiable": True, + "proxied": False, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + }, + ]) + + provider._request = Mock() + provider._request.side_effect = [ + self.empty, # no zones + { + 'result': { + 'id': 42, + } + }, # zone create + None, + None, + None, + None, + ] + + # Add something and delete something + zone = Zone('unit.tests.', []) + existing = Record.new(zone, 'a', { + 'ttl': 300, + 'type': 'A', + # This matches the zone data above, one to swap, one to leave + 'values': ['1.1.1.1', '2.2.2.2'], + }) + new = Record.new(zone, 'a', { + 'ttl': 300, + 'type': 'A', + # This leaves one, swaps ones, and adds one + 'values': ['2.2.2.2', '3.3.3.3', '4.4.4.4'], + }) + change = Update(existing, new) + plan = Plan(zone, zone, [change], True) + provider._apply(plan) + + # get the list of zones, create a zone, add some records, update + # something, and delete something + provider._request.assert_has_calls([ + call('GET', '/zones', params={'page': 1}), + call('POST', '/zones', data={ + 'jump_start': False, + 'name': 'unit.tests' + }), + call('POST', '/zones/42/dns_records', data={ + 'content': '4.4.4.4', + 'type': 'A', + 'name': 'a.unit.tests', + 'proxied': False, + 'ttl': 300 + }), + call('PUT', '/zones/42/dns_records/' + 'fc12ab34cd5611334422ab3322997654', data={ + 'content': '2.2.2.2', + 'type': 'A', + 'name': 'a.unit.tests', + 'proxied': False, + 'ttl': 300 + }), + call('PUT', '/zones/42/dns_records/' + 'fc12ab34cd5611334422ab3322997653', data={ + 'content': '3.3.3.3', + 'type': 'A', + 'name': 'a.unit.tests', + 'proxied': False, + 'ttl': 300 + }), + ]) + + def test_update_delete(self): + # We need another run so that we can delete, we can't both add and + # delete in one go b/c of swaps + provider = CloudflareProvider('test', 'token') + + provider.zone_records = Mock(return_value=[ + { + "id": "fc12ab34cd5611334422ab3322997653", + "type": "NS", + "name": "unit.tests", + "content": "ns1.foo.bar", + "proxiable": True, + "proxied": False, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + }, + { + "id": "fc12ab34cd5611334422ab3322997654", + "type": "NS", + "name": "unit.tests", + "content": "ns2.foo.bar", + "proxiable": True, + "proxied": False, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + }, + ]) + + provider._request = Mock() + provider._request.side_effect = [ + self.empty, # no zones + { + 'result': { + 'id': 42, + } + }, # zone create + None, + None, + ] + + # Add something and delete something + zone = Zone('unit.tests.', []) + existing = Record.new(zone, '', { + 'ttl': 300, + 'type': 'NS', + # This matches the zone data above, one to delete, one to leave + 'values': ['ns1.foo.bar.', 'ns2.foo.bar.'], + }) + new = Record.new(zone, '', { + 'ttl': 300, + 'type': 'NS', + # This leaves one and deletes one + 'value': 'ns2.foo.bar.', + }) + change = Update(existing, new) + plan = Plan(zone, zone, [change], True) + provider._apply(plan) + + # Get zones, create zone, create a record, delete a record + provider._request.assert_has_calls([ + call('GET', '/zones', params={'page': 1}), + call('POST', '/zones', data={ + 'jump_start': False, + 'name': 'unit.tests' + }), + call('PUT', '/zones/42/dns_records/' + 'fc12ab34cd5611334422ab3322997654', data={ + 'content': 'ns2.foo.bar.', + 'type': 'NS', + 'name': 'unit.tests', + 'ttl': 300 + }), + call('DELETE', '/zones/42/dns_records/' + 'fc12ab34cd5611334422ab3322997653') + ]) + + def test_srv(self): + provider = CloudflareProvider('test', 'token') + + zone = Zone('unit.tests.', []) + # SRV record not under a sub-domain + srv_record = Record.new(zone, '_example._tcp', { + 'ttl': 300, + 'type': 'SRV', + 'value': { + 'port': 1234, + 'priority': 0, + 'target': 'nc.unit.tests.', + 'weight': 5 + } + }) + # SRV record under a sub-domain + srv_record_with_sub = Record.new(zone, '_example._tcp.sub', { + 'ttl': 300, + 'type': 'SRV', + 'value': { + 'port': 1234, + 'priority': 0, + 'target': 'nc.unit.tests.', + 'weight': 5 + } + }) + + srv_record_contents = provider._gen_data(srv_record) + srv_record_with_sub_contents = provider._gen_data(srv_record_with_sub) + self.assertEquals({ + 'name': '_example._tcp.unit.tests', + 'ttl': 300, + 'type': 'SRV', + 'data': { + 'service': '_example', + 'proto': '_tcp', + 'name': 'unit.tests.', + 'priority': 0, + 'weight': 5, + 'port': 1234, + 'target': 'nc.unit.tests' + } + }, list(srv_record_contents)[0]) + self.assertEquals({ + 'name': '_example._tcp.sub.unit.tests', + 'ttl': 300, + 'type': 'SRV', + 'data': { + 'service': '_example', + 'proto': '_tcp', + 'name': 'sub', + 'priority': 0, + 'weight': 5, + 'port': 1234, + 'target': 'nc.unit.tests' + } + }, list(srv_record_with_sub_contents)[0]) + + def test_alias(self): + provider = CloudflareProvider('test', 'token') + + # A CNAME for us to transform to ALIAS + provider.zone_records = Mock(return_value=[ + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": "CNAME", + "name": "unit.tests", + "content": "www.unit.tests", + "proxiable": True, + "proxied": False, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + }, + ]) + + zone = Zone('unit.tests.', []) + provider.populate(zone) + self.assertEquals(1, len(zone.records)) + record = list(zone.records)[0] + self.assertEquals('', record.name) + self.assertEquals('unit.tests.', record.fqdn) + self.assertEquals('ALIAS', record._type) + self.assertEquals('www.unit.tests.', record.value) + + # Make sure we transform back to CNAME going the other way + contents = provider._gen_data(record) + self.assertEquals({ + 'content': 'www.unit.tests.', + 'name': 'unit.tests', + 'proxied': False, + 'ttl': 300, + 'type': 'CNAME' + }, list(contents)[0]) + + def test_gen_key(self): + provider = CloudflareProvider('test', 'token') + + for expected, data in ( + ('foo.bar.com.', { + 'content': 'foo.bar.com.', + 'type': 'CNAME', + }), + ('10 foo.bar.com.', { + 'content': 'foo.bar.com.', + 'priority': 10, + 'type': 'MX', + }), + ('0 tag some-value', { + 'data': { + 'flags': 0, + 'tag': 'tag', + 'value': 'some-value', + }, + 'type': 'CAA', + }), + ('42 100 thing-were-pointed.at 101', { + 'data': { + 'port': 42, + 'priority': 100, + 'target': 'thing-were-pointed.at', + 'weight': 101, + }, + 'type': 'SRV', + }), + ): + self.assertEqual(expected, provider._gen_key(data)) + + def test_cdn(self): + provider = CloudflareProvider('test', None, 'token', True) + + # A CNAME for us to transform to ALIAS + provider.zone_records = Mock(return_value=[ + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": "CNAME", + "name": "cname.unit.tests", + "content": "www.unit.tests", + "proxiable": True, + "proxied": True, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + }, + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": "A", + "name": "a.unit.tests", + "content": "1.1.1.1", + "proxiable": True, + "proxied": True, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + }, + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": "A", + "name": "a.unit.tests", + "content": "1.1.1.2", + "proxiable": True, + "proxied": True, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + }, + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": "A", + "name": "multi.unit.tests", + "content": "1.1.1.3", + "proxiable": True, + "proxied": True, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + }, + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": "AAAA", + "name": "multi.unit.tests", + "content": "::1", + "proxiable": True, + "proxied": True, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + }, + ]) + + zone = Zone('unit.tests.', []) + provider.populate(zone) + + # the two A records get merged into one CNAME record pointing to + # the CDN. + self.assertEquals(3, len(zone.records)) + + record = list(zone.records)[0] + self.assertEquals('multi', record.name) + self.assertEquals('multi.unit.tests.', record.fqdn) + self.assertEquals('CNAME', record._type) + self.assertEquals('multi.unit.tests.cdn.cloudflare.net.', record.value) + + record = list(zone.records)[1] + self.assertEquals('cname', record.name) + self.assertEquals('cname.unit.tests.', record.fqdn) + self.assertEquals('CNAME', record._type) + self.assertEquals('cname.unit.tests.cdn.cloudflare.net.', record.value) + + record = list(zone.records)[2] + self.assertEquals('a', record.name) + self.assertEquals('a.unit.tests.', record.fqdn) + self.assertEquals('CNAME', record._type) + self.assertEquals('a.unit.tests.cdn.cloudflare.net.', record.value) + + # CDN enabled records can't be updated, we don't know the real values + # never point a Cloudflare record to itself. + wanted = Zone('unit.tests.', []) + wanted.add_record(Record.new(wanted, 'cname', { + 'ttl': 300, + 'type': 'CNAME', + 'value': 'change.unit.tests.cdn.cloudflare.net.' + })) + wanted.add_record(Record.new(wanted, 'new', { + 'ttl': 300, + 'type': 'CNAME', + 'value': 'new.unit.tests.cdn.cloudflare.net.' + })) + wanted.add_record(Record.new(wanted, 'created', { + 'ttl': 300, + 'type': 'CNAME', + 'value': 'www.unit.tests.' + })) + + plan = provider.plan(wanted) + self.assertEquals(1, len(plan.changes)) + + def test_cdn_alias(self): + provider = CloudflareProvider('test', 'token', cdn=True) + + # A CNAME for us to transform to ALIAS + provider.zone_records = Mock(return_value=[ + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": "CNAME", + "name": "unit.tests", + "content": "www.unit.tests", + "proxiable": True, + "proxied": True, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + }, + ]) + + zone = Zone('unit.tests.', []) + provider.populate(zone) + self.assertEquals(1, len(zone.records)) + record = list(zone.records)[0] + self.assertEquals('', record.name) + self.assertEquals('unit.tests.', record.fqdn) + self.assertEquals('ALIAS', record._type) + self.assertEquals('unit.tests.cdn.cloudflare.net.', record.value) + + # CDN enabled records can't be updated, we don't know the real values + # never point a Cloudflare record to itself. + wanted = Zone('unit.tests.', []) + wanted.add_record(Record.new(wanted, '', { + 'ttl': 300, + 'type': 'ALIAS', + 'value': 'change.unit.tests.cdn.cloudflare.net.' + })) + + plan = provider.plan(wanted) + self.assertEquals(False, hasattr(plan, 'changes')) + + def test_unproxiabletype_recordfor_returnsrecordwithnocloudflare(self): + provider = CloudflareProvider('test', 'token') + name = "unit.tests" + _type = "NS" + zone_records = [ + { + "id": "fc12ab34cd5611334422ab3322997654", + "type": _type, + "name": name, + "content": "ns2.foo.bar", + "proxiable": True, + "proxied": False, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + } + ] + provider.zone_records = Mock(return_value=zone_records) + zone = Zone('unit.tests.', []) + provider.populate(zone) + + record = provider._record_for(zone, name, _type, zone_records, False) + + self.assertFalse('cloudflare' in record._octodns) + + def test_proxiabletype_recordfor_retrecordwithcloudflareunproxied(self): + provider = CloudflareProvider('test', 'token') + name = "multi.unit.tests" + _type = "AAAA" + zone_records = [ + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": _type, + "name": name, + "content": "::1", + "proxiable": True, + "proxied": False, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + } + ] + provider.zone_records = Mock(return_value=zone_records) + zone = Zone('unit.tests.', []) + provider.populate(zone) + + record = provider._record_for(zone, name, _type, zone_records, False) + + self.assertFalse(record._octodns['cloudflare']['proxied']) + + def test_proxiabletype_recordfor_returnsrecordwithcloudflareproxied(self): + provider = CloudflareProvider('test', 'token') + name = "multi.unit.tests" + _type = "AAAA" + zone_records = [ + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": _type, + "name": name, + "content": "::1", + "proxiable": True, + "proxied": True, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + } + ] + provider.zone_records = Mock(return_value=zone_records) + zone = Zone('unit.tests.', []) + provider.populate(zone) + + record = provider._record_for(zone, name, _type, zone_records, False) + + self.assertTrue(record._octodns['cloudflare']['proxied']) + + def test_proxiedrecordandnewttl_includechange_returnsfalse(self): + provider = CloudflareProvider('test', 'token') + zone = Zone('unit.tests.', []) + existing = set_record_proxied_flag( + Record.new(zone, 'a', { + 'ttl': 1, + 'type': 'A', + 'values': ['1.1.1.1', '2.2.2.2'] + }), True + ) + new = Record.new(zone, 'a', { + 'ttl': 300, + 'type': 'A', + 'values': ['1.1.1.1', '2.2.2.2'] + }) + change = Update(existing, new) + + include_change = provider._include_change(change) + + self.assertFalse(include_change) + + def test_unproxiabletype_gendata_returnsnoproxied(self): + provider = CloudflareProvider('test', 'token') + zone = Zone('unit.tests.', []) + record = Record.new(zone, 'a', { + 'ttl': 3600, + 'type': 'NS', + 'value': 'ns1.unit.tests.' + }) + + data = provider._gen_data(record).next() + + self.assertFalse('proxied' in data) + + def test_proxiabletype_gendata_returnsunproxied(self): + provider = CloudflareProvider('test', 'token') + zone = Zone('unit.tests.', []) + record = set_record_proxied_flag( + Record.new(zone, 'a', { + 'ttl': 300, + 'type': 'A', + 'value': '1.2.3.4' + }), False + ) + + data = provider._gen_data(record).next() + + self.assertFalse(data['proxied']) + + def test_proxiabletype_gendata_returnsproxied(self): + provider = CloudflareProvider('test', 'token') + zone = Zone('unit.tests.', []) + record = set_record_proxied_flag( + Record.new(zone, 'a', { + 'ttl': 300, + 'type': 'A', + 'value': '1.2.3.4' + }), True + ) + + data = provider._gen_data(record).next() + + self.assertTrue(data['proxied']) + + def test_createrecord_extrachanges_returnsemptylist(self): + provider = CloudflareProvider('test', 'token') + provider.zone_records = Mock(return_value=[]) + existing = Zone('unit.tests.', []) + provider.populate(existing) + provider.zone_records = Mock(return_value=[ + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": "CNAME", + "name": "a.unit.tests", + "content": "www.unit.tests", + "proxiable": True, + "proxied": True, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + } + ]) + desired = Zone('unit.tests.', []) + provider.populate(desired) + changes = existing.changes(desired, provider) + + extra_changes = provider._extra_changes(existing, desired, changes) + + self.assertFalse(extra_changes) + + def test_updaterecord_extrachanges_returnsemptylist(self): + provider = CloudflareProvider('test', 'token') + provider.zone_records = Mock(return_value=[ + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": "CNAME", + "name": "a.unit.tests", + "content": "www.unit.tests", + "proxiable": True, + "proxied": True, + "ttl": 120, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + } + ]) + existing = Zone('unit.tests.', []) + provider.populate(existing) + provider.zone_records = Mock(return_value=[ + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": "CNAME", + "name": "a.unit.tests", + "content": "www.unit.tests", + "proxiable": True, + "proxied": True, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + } + ]) + desired = Zone('unit.tests.', []) + provider.populate(desired) + changes = existing.changes(desired, provider) + + extra_changes = provider._extra_changes(existing, desired, changes) + + self.assertFalse(extra_changes) + + def test_deleterecord_extrachanges_returnsemptylist(self): + provider = CloudflareProvider('test', 'token') + provider.zone_records = Mock(return_value=[ + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": "CNAME", + "name": "a.unit.tests", + "content": "www.unit.tests", + "proxiable": True, + "proxied": True, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + } + ]) + existing = Zone('unit.tests.', []) + provider.populate(existing) + provider.zone_records = Mock(return_value=[]) + desired = Zone('unit.tests.', []) + provider.populate(desired) + changes = existing.changes(desired, provider) + + extra_changes = provider._extra_changes(existing, desired, changes) + + self.assertFalse(extra_changes) + + def test_proxify_extrachanges_returnsupdatelist(self): + provider = CloudflareProvider('test', 'token') + provider.zone_records = Mock(return_value=[ + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": "CNAME", + "name": "a.unit.tests", + "content": "www.unit.tests", + "proxiable": True, + "proxied": False, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + } + ]) + existing = Zone('unit.tests.', []) + provider.populate(existing) + provider.zone_records = Mock(return_value=[ + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": "CNAME", + "name": "a.unit.tests", + "content": "www.unit.tests", + "proxiable": True, + "proxied": True, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + } + ]) + desired = Zone('unit.tests.', []) + provider.populate(desired) + changes = existing.changes(desired, provider) + + extra_changes = provider._extra_changes(existing, desired, changes) + + self.assertEquals(1, len(extra_changes)) + self.assertFalse( + extra_changes[0].existing._octodns['cloudflare']['proxied'] + ) + self.assertTrue( + extra_changes[0].new._octodns['cloudflare']['proxied'] + ) + + def test_unproxify_extrachanges_returnsupdatelist(self): + provider = CloudflareProvider('test', 'token') + provider.zone_records = Mock(return_value=[ + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": "CNAME", + "name": "a.unit.tests", + "content": "www.unit.tests", + "proxiable": True, + "proxied": True, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + } + ]) + existing = Zone('unit.tests.', []) + provider.populate(existing) + provider.zone_records = Mock(return_value=[ + { + "id": "fc12ab34cd5611334422ab3322997642", + "type": "CNAME", + "name": "a.unit.tests", + "content": "www.unit.tests", + "proxiable": True, + "proxied": False, + "ttl": 300, + "locked": False, + "zone_id": "ff12ab34cd5611334422ab3322997650", + "zone_name": "unit.tests", + "modified_on": "2017-03-11T18:01:43.420689Z", + "created_on": "2017-03-11T18:01:43.420689Z", + "meta": { + "auto_added": False + } + } + ]) + desired = Zone('unit.tests.', []) + provider.populate(desired) + changes = existing.changes(desired, provider) + + extra_changes = provider._extra_changes(existing, desired, changes) + + self.assertEquals(1, len(extra_changes)) + self.assertTrue( + extra_changes[0].existing._octodns['cloudflare']['proxied'] + ) + self.assertFalse( + extra_changes[0].new._octodns['cloudflare']['proxied'] + ) From cf3dc943152ea6ed005969ec835513ca150a3de0 Mon Sep 17 00:00:00 2001 From: Mark Mercado Date: Wed, 5 Feb 2020 14:40:44 -0500 Subject: [PATCH 05/10] use the generic replacement field specifier --- octodns/provider/cloudflare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/octodns/provider/cloudflare.py b/octodns/provider/cloudflare.py index 1f0d1ea..d83a523 100644 --- a/octodns/provider/cloudflare.py +++ b/octodns/provider/cloudflare.py @@ -82,7 +82,7 @@ class CloudflareProvider(BaseProvider): # https://api.cloudflare.com/#getting-started-requests # https://tools.ietf.org/html/rfc6750#section-2.1 sess.headers.update({ - 'Authorization': 'Bearer %s'.format(token), + 'Authorization': 'Bearer {}'.format(token), }) self.cdn = cdn self._sess = sess From 59b12dd9c03f9066f728e2687576d48df514aacc Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Sat, 15 Feb 2020 16:02:20 -0800 Subject: [PATCH 06/10] Remove CloudFlare test copy, and specifically test token auth --- tests/test_octodns_provider_cloudflare.py | 11 + ...st_octodns_provider_cloudflare_no_email.py | 1211 ----------------- 2 files changed, 11 insertions(+), 1211 deletions(-) delete mode 100644 tests/test_octodns_provider_cloudflare_no_email.py diff --git a/tests/test_octodns_provider_cloudflare.py b/tests/test_octodns_provider_cloudflare.py index 3581033..5bcf25f 100644 --- a/tests/test_octodns_provider_cloudflare.py +++ b/tests/test_octodns_provider_cloudflare.py @@ -1212,3 +1212,14 @@ class TestCloudflareProvider(TestCase): self.assertFalse( extra_changes[0].new._octodns['cloudflare']['proxied'] ) + + def test_emailless_auth(self): + provider = CloudflareProvider('test', token='token 123', + email='email 234') + headers = provider._sess.headers + self.assertEquals('email 234', headers['X-Auth-Email']) + self.assertEquals('token 123', headers['X-Auth-Key']) + + provider = CloudflareProvider('test', token='token 123') + headers = provider._sess.headers + self.assertEquals('Bearer token 123', headers['Authorization']) diff --git a/tests/test_octodns_provider_cloudflare_no_email.py b/tests/test_octodns_provider_cloudflare_no_email.py deleted file mode 100644 index 8e25fe2..0000000 --- a/tests/test_octodns_provider_cloudflare_no_email.py +++ /dev/null @@ -1,1211 +0,0 @@ -# -# -# - -from __future__ import absolute_import, division, print_function, \ - unicode_literals - -from mock import Mock, call -from os.path import dirname, join -from requests import HTTPError -from requests_mock import ANY, mock as requests_mock -from unittest import TestCase - -from octodns.record import Record, Update -from octodns.provider.base import Plan -from octodns.provider.cloudflare import CloudflareProvider -from octodns.provider.yaml import YamlProvider -from octodns.zone import Zone - - -def set_record_proxied_flag(record, proxied): - try: - record._octodns['cloudflare']['proxied'] = proxied - except KeyError: - record._octodns['cloudflare'] = { - 'proxied': proxied - } - - return record - - -class TestCloudflareProvider(TestCase): - expected = Zone('unit.tests.', []) - source = YamlProvider('test', join(dirname(__file__), 'config')) - source.populate(expected) - - # Our test suite differs a bit, add our NS and remove the simple one - expected.add_record(Record.new(expected, 'under', { - 'ttl': 3600, - 'type': 'NS', - 'values': [ - 'ns1.unit.tests.', - 'ns2.unit.tests.', - ] - })) - for record in list(expected.records): - if record.name == 'sub' and record._type == 'NS': - expected._remove_record(record) - break - - empty = {'result': [], 'result_info': {'count': 0, 'per_page': 0}} - - def test_populate(self): - provider = CloudflareProvider('test', 'token') - - # Bad requests - with requests_mock() as mock: - mock.get(ANY, status_code=400, - text='{"success":false,"errors":[{"code":1101,' - '"message":"request was invalid"}],' - '"messages":[],"result":null}') - - with self.assertRaises(Exception) as ctx: - zone = Zone('unit.tests.', []) - provider.populate(zone) - - self.assertEquals('CloudflareError', type(ctx.exception).__name__) - self.assertEquals('request was invalid', ctx.exception.message) - - # Bad auth - with requests_mock() as mock: - mock.get(ANY, status_code=403, - text='{"success":false,"errors":[{"code":9103,' - '"message":"Unknown X-Auth-Key or X-Auth-Email"}],' - '"messages":[],"result":null}') - - with self.assertRaises(Exception) as ctx: - zone = Zone('unit.tests.', []) - provider.populate(zone) - self.assertEquals('CloudflareAuthenticationError', - type(ctx.exception).__name__) - self.assertEquals('Unknown X-Auth-Key or X-Auth-Email', - ctx.exception.message) - - # Bad auth, unknown resp - with requests_mock() as mock: - mock.get(ANY, status_code=403, text='{}') - - with self.assertRaises(Exception) as ctx: - zone = Zone('unit.tests.', []) - provider.populate(zone) - self.assertEquals('CloudflareAuthenticationError', - type(ctx.exception).__name__) - self.assertEquals('Cloudflare error', ctx.exception.message) - - # General error - with requests_mock() as mock: - mock.get(ANY, status_code=502, text='Things caught fire') - - with self.assertRaises(HTTPError) as ctx: - zone = Zone('unit.tests.', []) - provider.populate(zone) - self.assertEquals(502, ctx.exception.response.status_code) - - # Non-existent zone doesn't populate anything - with requests_mock() as mock: - mock.get(ANY, status_code=200, json=self.empty) - - zone = Zone('unit.tests.', []) - provider.populate(zone) - self.assertEquals(set(), zone.records) - - # re-populating the same non-existent zone uses cache and makes no - # calls - again = Zone('unit.tests.', []) - provider.populate(again) - self.assertEquals(set(), again.records) - - # bust zone cache - provider._zones = None - - # existing zone with data - with requests_mock() as mock: - base = 'https://api.cloudflare.com/client/v4/zones' - - # zones - with open('tests/fixtures/cloudflare-zones-page-1.json') as fh: - mock.get('{}?page=1'.format(base), status_code=200, - text=fh.read()) - with open('tests/fixtures/cloudflare-zones-page-2.json') as fh: - mock.get('{}?page=2'.format(base), status_code=200, - text=fh.read()) - mock.get('{}?page=3'.format(base), status_code=200, - json={'result': [], 'result_info': {'count': 0, - 'per_page': 0}}) - - # records - base = '{}/234234243423aaabb334342aaa343435/dns_records' \ - .format(base) - with open('tests/fixtures/cloudflare-dns_records-' - 'page-1.json') as fh: - mock.get('{}?page=1'.format(base), status_code=200, - text=fh.read()) - with open('tests/fixtures/cloudflare-dns_records-' - 'page-2.json') as fh: - mock.get('{}?page=2'.format(base), status_code=200, - text=fh.read()) - - zone = Zone('unit.tests.', []) - provider.populate(zone) - self.assertEquals(12, len(zone.records)) - - changes = self.expected.changes(zone, provider) - - self.assertEquals(0, len(changes)) - - # re-populating the same zone/records comes out of cache, no calls - again = Zone('unit.tests.', []) - provider.populate(again) - self.assertEquals(12, len(again.records)) - - def test_apply(self): - provider = CloudflareProvider('test', 'token') - - provider._request = Mock() - - provider._request.side_effect = [ - self.empty, # no zones - { - 'result': { - 'id': 42, - } - }, # zone create - ] + [None] * 20 # individual record creates - - # non-existent zone, create everything - plan = provider.plan(self.expected) - self.assertEquals(12, len(plan.changes)) - self.assertEquals(12, provider.apply(plan)) - self.assertFalse(plan.exists) - - provider._request.assert_has_calls([ - # created the domain - call('POST', '/zones', data={ - 'jump_start': False, - 'name': 'unit.tests' - }), - # created at least one of the record with expected data - call('POST', '/zones/42/dns_records', data={ - 'content': 'ns1.unit.tests.', - 'type': 'NS', - 'name': 'under.unit.tests', - 'ttl': 3600 - }), - # make sure semicolons are not escaped when sending data - call('POST', '/zones/42/dns_records', data={ - 'content': 'v=DKIM1;k=rsa;s=email;h=sha256;' - 'p=A/kinda+of/long/string+with+numb3rs', - 'type': 'TXT', - 'name': 'txt.unit.tests', - 'ttl': 600 - }), - ], True) - # expected number of total calls - self.assertEquals(22, provider._request.call_count) - - provider._request.reset_mock() - - provider.zone_records = Mock(return_value=[ - { - "id": "fc12ab34cd5611334422ab3322997653", - "type": "A", - "name": "www.unit.tests", - "content": "1.2.3.4", - "proxiable": True, - "proxied": False, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - }, - { - "id": "fc12ab34cd5611334422ab3322997654", - "type": "A", - "name": "www.unit.tests", - "content": "2.2.3.4", - "proxiable": True, - "proxied": False, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:44.030044Z", - "created_on": "2017-03-11T18:01:44.030044Z", - "meta": { - "auto_added": False - } - }, - { - "id": "fc12ab34cd5611334422ab3322997655", - "type": "A", - "name": "nc.unit.tests", - "content": "3.2.3.4", - "proxiable": True, - "proxied": False, - "ttl": 120, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:44.030044Z", - "created_on": "2017-03-11T18:01:44.030044Z", - "meta": { - "auto_added": False - } - }, - { - "id": "fc12ab34cd5611334422ab3322997655", - "type": "A", - "name": "ttl.unit.tests", - "content": "4.2.3.4", - "proxiable": True, - "proxied": False, - "ttl": 600, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:44.030044Z", - "created_on": "2017-03-11T18:01:44.030044Z", - "meta": { - "auto_added": False - } - }, - ]) - - # we don't care about the POST/create return values - provider._request.return_value = {} - provider._request.side_effect = None - - wanted = Zone('unit.tests.', []) - wanted.add_record(Record.new(wanted, 'nc', { - 'ttl': 60, # TTL is below their min - 'type': 'A', - 'value': '3.2.3.4' - })) - wanted.add_record(Record.new(wanted, 'ttl', { - 'ttl': 300, # TTL change - 'type': 'A', - 'value': '3.2.3.4' - })) - - plan = provider.plan(wanted) - # only see the delete & ttl update, below min-ttl is filtered out - self.assertEquals(2, len(plan.changes)) - self.assertEquals(2, provider.apply(plan)) - self.assertTrue(plan.exists) - # creates a the new value and then deletes all the old - provider._request.assert_has_calls([ - call('PUT', '/zones/42/dns_records/' - 'fc12ab34cd5611334422ab3322997655', data={ - 'content': '3.2.3.4', - 'type': 'A', - 'name': 'ttl.unit.tests', - 'proxied': False, - 'ttl': 300 - }), - call('DELETE', '/zones/ff12ab34cd5611334422ab3322997650/' - 'dns_records/fc12ab34cd5611334422ab3322997653'), - call('DELETE', '/zones/ff12ab34cd5611334422ab3322997650/' - 'dns_records/fc12ab34cd5611334422ab3322997654') - ]) - - def test_update_add_swap(self): - provider = CloudflareProvider('test', 'token') - - provider.zone_records = Mock(return_value=[ - { - "id": "fc12ab34cd5611334422ab3322997653", - "type": "A", - "name": "a.unit.tests", - "content": "1.1.1.1", - "proxiable": True, - "proxied": False, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - }, - { - "id": "fc12ab34cd5611334422ab3322997654", - "type": "A", - "name": "a.unit.tests", - "content": "2.2.2.2", - "proxiable": True, - "proxied": False, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - }, - ]) - - provider._request = Mock() - provider._request.side_effect = [ - self.empty, # no zones - { - 'result': { - 'id': 42, - } - }, # zone create - None, - None, - None, - None, - ] - - # Add something and delete something - zone = Zone('unit.tests.', []) - existing = Record.new(zone, 'a', { - 'ttl': 300, - 'type': 'A', - # This matches the zone data above, one to swap, one to leave - 'values': ['1.1.1.1', '2.2.2.2'], - }) - new = Record.new(zone, 'a', { - 'ttl': 300, - 'type': 'A', - # This leaves one, swaps ones, and adds one - 'values': ['2.2.2.2', '3.3.3.3', '4.4.4.4'], - }) - change = Update(existing, new) - plan = Plan(zone, zone, [change], True) - provider._apply(plan) - - # get the list of zones, create a zone, add some records, update - # something, and delete something - provider._request.assert_has_calls([ - call('GET', '/zones', params={'page': 1}), - call('POST', '/zones', data={ - 'jump_start': False, - 'name': 'unit.tests' - }), - call('POST', '/zones/42/dns_records', data={ - 'content': '4.4.4.4', - 'type': 'A', - 'name': 'a.unit.tests', - 'proxied': False, - 'ttl': 300 - }), - call('PUT', '/zones/42/dns_records/' - 'fc12ab34cd5611334422ab3322997654', data={ - 'content': '2.2.2.2', - 'type': 'A', - 'name': 'a.unit.tests', - 'proxied': False, - 'ttl': 300 - }), - call('PUT', '/zones/42/dns_records/' - 'fc12ab34cd5611334422ab3322997653', data={ - 'content': '3.3.3.3', - 'type': 'A', - 'name': 'a.unit.tests', - 'proxied': False, - 'ttl': 300 - }), - ]) - - def test_update_delete(self): - # We need another run so that we can delete, we can't both add and - # delete in one go b/c of swaps - provider = CloudflareProvider('test', 'token') - - provider.zone_records = Mock(return_value=[ - { - "id": "fc12ab34cd5611334422ab3322997653", - "type": "NS", - "name": "unit.tests", - "content": "ns1.foo.bar", - "proxiable": True, - "proxied": False, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - }, - { - "id": "fc12ab34cd5611334422ab3322997654", - "type": "NS", - "name": "unit.tests", - "content": "ns2.foo.bar", - "proxiable": True, - "proxied": False, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - }, - ]) - - provider._request = Mock() - provider._request.side_effect = [ - self.empty, # no zones - { - 'result': { - 'id': 42, - } - }, # zone create - None, - None, - ] - - # Add something and delete something - zone = Zone('unit.tests.', []) - existing = Record.new(zone, '', { - 'ttl': 300, - 'type': 'NS', - # This matches the zone data above, one to delete, one to leave - 'values': ['ns1.foo.bar.', 'ns2.foo.bar.'], - }) - new = Record.new(zone, '', { - 'ttl': 300, - 'type': 'NS', - # This leaves one and deletes one - 'value': 'ns2.foo.bar.', - }) - change = Update(existing, new) - plan = Plan(zone, zone, [change], True) - provider._apply(plan) - - # Get zones, create zone, create a record, delete a record - provider._request.assert_has_calls([ - call('GET', '/zones', params={'page': 1}), - call('POST', '/zones', data={ - 'jump_start': False, - 'name': 'unit.tests' - }), - call('PUT', '/zones/42/dns_records/' - 'fc12ab34cd5611334422ab3322997654', data={ - 'content': 'ns2.foo.bar.', - 'type': 'NS', - 'name': 'unit.tests', - 'ttl': 300 - }), - call('DELETE', '/zones/42/dns_records/' - 'fc12ab34cd5611334422ab3322997653') - ]) - - def test_srv(self): - provider = CloudflareProvider('test', 'token') - - zone = Zone('unit.tests.', []) - # SRV record not under a sub-domain - srv_record = Record.new(zone, '_example._tcp', { - 'ttl': 300, - 'type': 'SRV', - 'value': { - 'port': 1234, - 'priority': 0, - 'target': 'nc.unit.tests.', - 'weight': 5 - } - }) - # SRV record under a sub-domain - srv_record_with_sub = Record.new(zone, '_example._tcp.sub', { - 'ttl': 300, - 'type': 'SRV', - 'value': { - 'port': 1234, - 'priority': 0, - 'target': 'nc.unit.tests.', - 'weight': 5 - } - }) - - srv_record_contents = provider._gen_data(srv_record) - srv_record_with_sub_contents = provider._gen_data(srv_record_with_sub) - self.assertEquals({ - 'name': '_example._tcp.unit.tests', - 'ttl': 300, - 'type': 'SRV', - 'data': { - 'service': '_example', - 'proto': '_tcp', - 'name': 'unit.tests.', - 'priority': 0, - 'weight': 5, - 'port': 1234, - 'target': 'nc.unit.tests' - } - }, list(srv_record_contents)[0]) - self.assertEquals({ - 'name': '_example._tcp.sub.unit.tests', - 'ttl': 300, - 'type': 'SRV', - 'data': { - 'service': '_example', - 'proto': '_tcp', - 'name': 'sub', - 'priority': 0, - 'weight': 5, - 'port': 1234, - 'target': 'nc.unit.tests' - } - }, list(srv_record_with_sub_contents)[0]) - - def test_alias(self): - provider = CloudflareProvider('test', 'token') - - # A CNAME for us to transform to ALIAS - provider.zone_records = Mock(return_value=[ - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": "CNAME", - "name": "unit.tests", - "content": "www.unit.tests", - "proxiable": True, - "proxied": False, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - }, - ]) - - zone = Zone('unit.tests.', []) - provider.populate(zone) - self.assertEquals(1, len(zone.records)) - record = list(zone.records)[0] - self.assertEquals('', record.name) - self.assertEquals('unit.tests.', record.fqdn) - self.assertEquals('ALIAS', record._type) - self.assertEquals('www.unit.tests.', record.value) - - # Make sure we transform back to CNAME going the other way - contents = provider._gen_data(record) - self.assertEquals({ - 'content': 'www.unit.tests.', - 'name': 'unit.tests', - 'proxied': False, - 'ttl': 300, - 'type': 'CNAME' - }, list(contents)[0]) - - def test_gen_key(self): - provider = CloudflareProvider('test', 'token') - - for expected, data in ( - ('foo.bar.com.', { - 'content': 'foo.bar.com.', - 'type': 'CNAME', - }), - ('10 foo.bar.com.', { - 'content': 'foo.bar.com.', - 'priority': 10, - 'type': 'MX', - }), - ('0 tag some-value', { - 'data': { - 'flags': 0, - 'tag': 'tag', - 'value': 'some-value', - }, - 'type': 'CAA', - }), - ('42 100 thing-were-pointed.at 101', { - 'data': { - 'port': 42, - 'priority': 100, - 'target': 'thing-were-pointed.at', - 'weight': 101, - }, - 'type': 'SRV', - }), - ): - self.assertEqual(expected, provider._gen_key(data)) - - def test_cdn(self): - provider = CloudflareProvider('test', None, 'token', True) - - # A CNAME for us to transform to ALIAS - provider.zone_records = Mock(return_value=[ - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": "CNAME", - "name": "cname.unit.tests", - "content": "www.unit.tests", - "proxiable": True, - "proxied": True, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - }, - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": "A", - "name": "a.unit.tests", - "content": "1.1.1.1", - "proxiable": True, - "proxied": True, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - }, - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": "A", - "name": "a.unit.tests", - "content": "1.1.1.2", - "proxiable": True, - "proxied": True, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - }, - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": "A", - "name": "multi.unit.tests", - "content": "1.1.1.3", - "proxiable": True, - "proxied": True, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - }, - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": "AAAA", - "name": "multi.unit.tests", - "content": "::1", - "proxiable": True, - "proxied": True, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - }, - ]) - - zone = Zone('unit.tests.', []) - provider.populate(zone) - - # the two A records get merged into one CNAME record pointing to - # the CDN. - self.assertEquals(3, len(zone.records)) - - record = list(zone.records)[0] - self.assertEquals('multi', record.name) - self.assertEquals('multi.unit.tests.', record.fqdn) - self.assertEquals('CNAME', record._type) - self.assertEquals('multi.unit.tests.cdn.cloudflare.net.', record.value) - - record = list(zone.records)[1] - self.assertEquals('cname', record.name) - self.assertEquals('cname.unit.tests.', record.fqdn) - self.assertEquals('CNAME', record._type) - self.assertEquals('cname.unit.tests.cdn.cloudflare.net.', record.value) - - record = list(zone.records)[2] - self.assertEquals('a', record.name) - self.assertEquals('a.unit.tests.', record.fqdn) - self.assertEquals('CNAME', record._type) - self.assertEquals('a.unit.tests.cdn.cloudflare.net.', record.value) - - # CDN enabled records can't be updated, we don't know the real values - # never point a Cloudflare record to itself. - wanted = Zone('unit.tests.', []) - wanted.add_record(Record.new(wanted, 'cname', { - 'ttl': 300, - 'type': 'CNAME', - 'value': 'change.unit.tests.cdn.cloudflare.net.' - })) - wanted.add_record(Record.new(wanted, 'new', { - 'ttl': 300, - 'type': 'CNAME', - 'value': 'new.unit.tests.cdn.cloudflare.net.' - })) - wanted.add_record(Record.new(wanted, 'created', { - 'ttl': 300, - 'type': 'CNAME', - 'value': 'www.unit.tests.' - })) - - plan = provider.plan(wanted) - self.assertEquals(1, len(plan.changes)) - - def test_cdn_alias(self): - provider = CloudflareProvider('test', 'token', cdn=True) - - # A CNAME for us to transform to ALIAS - provider.zone_records = Mock(return_value=[ - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": "CNAME", - "name": "unit.tests", - "content": "www.unit.tests", - "proxiable": True, - "proxied": True, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - }, - ]) - - zone = Zone('unit.tests.', []) - provider.populate(zone) - self.assertEquals(1, len(zone.records)) - record = list(zone.records)[0] - self.assertEquals('', record.name) - self.assertEquals('unit.tests.', record.fqdn) - self.assertEquals('ALIAS', record._type) - self.assertEquals('unit.tests.cdn.cloudflare.net.', record.value) - - # CDN enabled records can't be updated, we don't know the real values - # never point a Cloudflare record to itself. - wanted = Zone('unit.tests.', []) - wanted.add_record(Record.new(wanted, '', { - 'ttl': 300, - 'type': 'ALIAS', - 'value': 'change.unit.tests.cdn.cloudflare.net.' - })) - - plan = provider.plan(wanted) - self.assertEquals(False, hasattr(plan, 'changes')) - - def test_unproxiabletype_recordfor_returnsrecordwithnocloudflare(self): - provider = CloudflareProvider('test', 'token') - name = "unit.tests" - _type = "NS" - zone_records = [ - { - "id": "fc12ab34cd5611334422ab3322997654", - "type": _type, - "name": name, - "content": "ns2.foo.bar", - "proxiable": True, - "proxied": False, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - } - ] - provider.zone_records = Mock(return_value=zone_records) - zone = Zone('unit.tests.', []) - provider.populate(zone) - - record = provider._record_for(zone, name, _type, zone_records, False) - - self.assertFalse('cloudflare' in record._octodns) - - def test_proxiabletype_recordfor_retrecordwithcloudflareunproxied(self): - provider = CloudflareProvider('test', 'token') - name = "multi.unit.tests" - _type = "AAAA" - zone_records = [ - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": _type, - "name": name, - "content": "::1", - "proxiable": True, - "proxied": False, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - } - ] - provider.zone_records = Mock(return_value=zone_records) - zone = Zone('unit.tests.', []) - provider.populate(zone) - - record = provider._record_for(zone, name, _type, zone_records, False) - - self.assertFalse(record._octodns['cloudflare']['proxied']) - - def test_proxiabletype_recordfor_returnsrecordwithcloudflareproxied(self): - provider = CloudflareProvider('test', 'token') - name = "multi.unit.tests" - _type = "AAAA" - zone_records = [ - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": _type, - "name": name, - "content": "::1", - "proxiable": True, - "proxied": True, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - } - ] - provider.zone_records = Mock(return_value=zone_records) - zone = Zone('unit.tests.', []) - provider.populate(zone) - - record = provider._record_for(zone, name, _type, zone_records, False) - - self.assertTrue(record._octodns['cloudflare']['proxied']) - - def test_proxiedrecordandnewttl_includechange_returnsfalse(self): - provider = CloudflareProvider('test', 'token') - zone = Zone('unit.tests.', []) - existing = set_record_proxied_flag( - Record.new(zone, 'a', { - 'ttl': 1, - 'type': 'A', - 'values': ['1.1.1.1', '2.2.2.2'] - }), True - ) - new = Record.new(zone, 'a', { - 'ttl': 300, - 'type': 'A', - 'values': ['1.1.1.1', '2.2.2.2'] - }) - change = Update(existing, new) - - include_change = provider._include_change(change) - - self.assertFalse(include_change) - - def test_unproxiabletype_gendata_returnsnoproxied(self): - provider = CloudflareProvider('test', 'token') - zone = Zone('unit.tests.', []) - record = Record.new(zone, 'a', { - 'ttl': 3600, - 'type': 'NS', - 'value': 'ns1.unit.tests.' - }) - - data = provider._gen_data(record).next() - - self.assertFalse('proxied' in data) - - def test_proxiabletype_gendata_returnsunproxied(self): - provider = CloudflareProvider('test', 'token') - zone = Zone('unit.tests.', []) - record = set_record_proxied_flag( - Record.new(zone, 'a', { - 'ttl': 300, - 'type': 'A', - 'value': '1.2.3.4' - }), False - ) - - data = provider._gen_data(record).next() - - self.assertFalse(data['proxied']) - - def test_proxiabletype_gendata_returnsproxied(self): - provider = CloudflareProvider('test', 'token') - zone = Zone('unit.tests.', []) - record = set_record_proxied_flag( - Record.new(zone, 'a', { - 'ttl': 300, - 'type': 'A', - 'value': '1.2.3.4' - }), True - ) - - data = provider._gen_data(record).next() - - self.assertTrue(data['proxied']) - - def test_createrecord_extrachanges_returnsemptylist(self): - provider = CloudflareProvider('test', 'token') - provider.zone_records = Mock(return_value=[]) - existing = Zone('unit.tests.', []) - provider.populate(existing) - provider.zone_records = Mock(return_value=[ - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": "CNAME", - "name": "a.unit.tests", - "content": "www.unit.tests", - "proxiable": True, - "proxied": True, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - } - ]) - desired = Zone('unit.tests.', []) - provider.populate(desired) - changes = existing.changes(desired, provider) - - extra_changes = provider._extra_changes(existing, desired, changes) - - self.assertFalse(extra_changes) - - def test_updaterecord_extrachanges_returnsemptylist(self): - provider = CloudflareProvider('test', 'token') - provider.zone_records = Mock(return_value=[ - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": "CNAME", - "name": "a.unit.tests", - "content": "www.unit.tests", - "proxiable": True, - "proxied": True, - "ttl": 120, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - } - ]) - existing = Zone('unit.tests.', []) - provider.populate(existing) - provider.zone_records = Mock(return_value=[ - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": "CNAME", - "name": "a.unit.tests", - "content": "www.unit.tests", - "proxiable": True, - "proxied": True, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - } - ]) - desired = Zone('unit.tests.', []) - provider.populate(desired) - changes = existing.changes(desired, provider) - - extra_changes = provider._extra_changes(existing, desired, changes) - - self.assertFalse(extra_changes) - - def test_deleterecord_extrachanges_returnsemptylist(self): - provider = CloudflareProvider('test', 'token') - provider.zone_records = Mock(return_value=[ - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": "CNAME", - "name": "a.unit.tests", - "content": "www.unit.tests", - "proxiable": True, - "proxied": True, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - } - ]) - existing = Zone('unit.tests.', []) - provider.populate(existing) - provider.zone_records = Mock(return_value=[]) - desired = Zone('unit.tests.', []) - provider.populate(desired) - changes = existing.changes(desired, provider) - - extra_changes = provider._extra_changes(existing, desired, changes) - - self.assertFalse(extra_changes) - - def test_proxify_extrachanges_returnsupdatelist(self): - provider = CloudflareProvider('test', 'token') - provider.zone_records = Mock(return_value=[ - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": "CNAME", - "name": "a.unit.tests", - "content": "www.unit.tests", - "proxiable": True, - "proxied": False, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - } - ]) - existing = Zone('unit.tests.', []) - provider.populate(existing) - provider.zone_records = Mock(return_value=[ - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": "CNAME", - "name": "a.unit.tests", - "content": "www.unit.tests", - "proxiable": True, - "proxied": True, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - } - ]) - desired = Zone('unit.tests.', []) - provider.populate(desired) - changes = existing.changes(desired, provider) - - extra_changes = provider._extra_changes(existing, desired, changes) - - self.assertEquals(1, len(extra_changes)) - self.assertFalse( - extra_changes[0].existing._octodns['cloudflare']['proxied'] - ) - self.assertTrue( - extra_changes[0].new._octodns['cloudflare']['proxied'] - ) - - def test_unproxify_extrachanges_returnsupdatelist(self): - provider = CloudflareProvider('test', 'token') - provider.zone_records = Mock(return_value=[ - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": "CNAME", - "name": "a.unit.tests", - "content": "www.unit.tests", - "proxiable": True, - "proxied": True, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - } - ]) - existing = Zone('unit.tests.', []) - provider.populate(existing) - provider.zone_records = Mock(return_value=[ - { - "id": "fc12ab34cd5611334422ab3322997642", - "type": "CNAME", - "name": "a.unit.tests", - "content": "www.unit.tests", - "proxiable": True, - "proxied": False, - "ttl": 300, - "locked": False, - "zone_id": "ff12ab34cd5611334422ab3322997650", - "zone_name": "unit.tests", - "modified_on": "2017-03-11T18:01:43.420689Z", - "created_on": "2017-03-11T18:01:43.420689Z", - "meta": { - "auto_added": False - } - } - ]) - desired = Zone('unit.tests.', []) - provider.populate(desired) - changes = existing.changes(desired, provider) - - extra_changes = provider._extra_changes(existing, desired, changes) - - self.assertEquals(1, len(extra_changes)) - self.assertTrue( - extra_changes[0].existing._octodns['cloudflare']['proxied'] - ) - self.assertFalse( - extra_changes[0].new._octodns['cloudflare']['proxied'] - ) From e7370d01daba0152a159a70d6b6b471d81f00bf2 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Sat, 15 Feb 2020 16:10:21 -0800 Subject: [PATCH 07/10] Update development setup section of CONTRIBUTING --- CONTRIBUTING.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index accabd5..4665a95 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,11 +41,12 @@ Here are a few things you can do that will increase the likelihood of your pull ## Development setup ``` -pip install -r requirements.txt -pip install -r requirements-dev.txt -python setup.py install +./scipt/bootstrap +source env/bin/activate ``` +See the [`scripts/`](/scripts) if you'd like to run tests and coverage ([`script/coverage/`](/scripts/coverage)) and coverage ([`script/lint`](/scripts/lint)). After bootstrapping and sourcing the `env/` commands in the [`octodns/cmds/`](/octodns/cmds) directory can be run with `PYTHONPATH=. ./octodns/cmds/sync.py ...` + ## License note We can only accept contributions that are compatible with the MIT license. From 309d2fb572ebc20e487b5b038582713df140eaaf Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Sat, 15 Feb 2020 16:11:41 -0800 Subject: [PATCH 08/10] script not scripts --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4665a95..b510bd6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,7 +45,7 @@ Here are a few things you can do that will increase the likelihood of your pull source env/bin/activate ``` -See the [`scripts/`](/scripts) if you'd like to run tests and coverage ([`script/coverage/`](/scripts/coverage)) and coverage ([`script/lint`](/scripts/lint)). After bootstrapping and sourcing the `env/` commands in the [`octodns/cmds/`](/octodns/cmds) directory can be run with `PYTHONPATH=. ./octodns/cmds/sync.py ...` +See the [`script/`](/script) if you'd like to run tests and coverage ([`script/coverage`](/scripts/coverage)) and coverage ([`script/lint`](/scripts/lint)). After bootstrapping and sourcing the `env/` commands in the [`octodns/cmds/`](/octodns/cmds) directory can be run with `PYTHONPATH=. ./octodns/cmds/sync.py ...` ## License note From 9c3a8a7cf9d9e923b06e20ca8aabe1eecf59d4f8 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Sat, 15 Feb 2020 16:12:19 -0800 Subject: [PATCH 09/10] Ugh --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b510bd6..f5000d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,7 +45,7 @@ Here are a few things you can do that will increase the likelihood of your pull source env/bin/activate ``` -See the [`script/`](/script) if you'd like to run tests and coverage ([`script/coverage`](/scripts/coverage)) and coverage ([`script/lint`](/scripts/lint)). After bootstrapping and sourcing the `env/` commands in the [`octodns/cmds/`](/octodns/cmds) directory can be run with `PYTHONPATH=. ./octodns/cmds/sync.py ...` +See the [`script/`](/script) if you'd like to run tests and coverage ([`script/coverage`](/script/coverage)) and coverage ([`script/lint`](/scripts/lint)). After bootstrapping and sourcing the `env/` commands in the [`octodns/cmds/`](/octodns/cmds) directory can be run with `PYTHONPATH=. ./octodns/cmds/sync.py ...` ## License note From 908fb5aaec3fdb255f5937da8696f8b2d83cacfd Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Sat, 15 Feb 2020 16:12:58 -0800 Subject: [PATCH 10/10] Ugh --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f5000d8..c3a8f48 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,7 +45,7 @@ Here are a few things you can do that will increase the likelihood of your pull source env/bin/activate ``` -See the [`script/`](/script) if you'd like to run tests and coverage ([`script/coverage`](/script/coverage)) and coverage ([`script/lint`](/scripts/lint)). After bootstrapping and sourcing the `env/` commands in the [`octodns/cmds/`](/octodns/cmds) directory can be run with `PYTHONPATH=. ./octodns/cmds/sync.py ...` +See the [`script/`](/script) if you'd like to run tests and coverage ([`script/coverage`](/script/coverage)) and coverage ([`script/lint`](/script/lint)). After bootstrapping and sourcing the `env/` commands in the [`octodns/cmds/`](/octodns/cmds) directory can be run with `PYTHONPATH=. ./octodns/cmds/sync.py ...` ## License note