mirror of
https://github.com/github/octodns.git
synced 2024-05-11 05:55:00 +00:00
Merge branch 'gcore-provider' of github.com:G-Core/octodns into gcore-provider
This commit is contained in:
15
tests/config/split/unit.tests.tst/urlfwd.yaml
Normal file
15
tests/config/split/unit.tests.tst/urlfwd.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
urlfwd:
|
||||
ttl: 300
|
||||
type: URLFWD
|
||||
values:
|
||||
- code: 302
|
||||
masking: 2
|
||||
path: '/'
|
||||
query: 0
|
||||
target: 'http://www.unit.tests'
|
||||
- code: 301
|
||||
masking: 2
|
||||
path: '/target'
|
||||
query: 0
|
||||
target: 'http://target.unit.tests'
|
||||
@@ -169,6 +169,20 @@ txt:
|
||||
- Bah bah black sheep
|
||||
- have you any wool.
|
||||
- 'v=DKIM1\;k=rsa\;s=email\;h=sha256\;p=A/kinda+of/long/string+with+numb3rs'
|
||||
urlfwd:
|
||||
ttl: 300
|
||||
type: URLFWD
|
||||
values:
|
||||
- code: 302
|
||||
masking: 2
|
||||
path: '/'
|
||||
query: 0
|
||||
target: 'http://www.unit.tests'
|
||||
- code: 301
|
||||
masking: 2
|
||||
path: '/target'
|
||||
query: 0
|
||||
target: 'http://target.unit.tests'
|
||||
www:
|
||||
ttl: 300
|
||||
type: A
|
||||
|
||||
103
tests/fixtures/cloudflare-pagerules.json
vendored
Normal file
103
tests/fixtures/cloudflare-pagerules.json
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"result": [
|
||||
{
|
||||
"id": "2b1ec1793185213139f22059a165376e",
|
||||
"targets": [
|
||||
{
|
||||
"target": "url",
|
||||
"constraint": {
|
||||
"operator": "matches",
|
||||
"value": "urlfwd0.unit.tests/"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "always_use_https"
|
||||
}
|
||||
],
|
||||
"priority": 4,
|
||||
"status": "active",
|
||||
"created_on": "2021-06-29T17:14:28.000000Z",
|
||||
"modified_on": "2021-06-29T17:15:33.000000Z"
|
||||
},
|
||||
{
|
||||
"id": "2b1ec1793185213139f22059a165376f",
|
||||
"targets": [
|
||||
{
|
||||
"target": "url",
|
||||
"constraint": {
|
||||
"operator": "matches",
|
||||
"value": "urlfwd0.unit.tests/*"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "forwarding_url",
|
||||
"value": {
|
||||
"url": "https://www.unit.tests/",
|
||||
"status_code": 301
|
||||
}
|
||||
}
|
||||
],
|
||||
"priority": 3,
|
||||
"status": "active",
|
||||
"created_on": "2021-06-29T17:07:12.000000Z",
|
||||
"modified_on": "2021-06-29T17:15:12.000000Z"
|
||||
},
|
||||
{
|
||||
"id": "2b1ec1793185213139f22059a165377e",
|
||||
"targets": [
|
||||
{
|
||||
"target": "url",
|
||||
"constraint": {
|
||||
"operator": "matches",
|
||||
"value": "urlfwd1.unit.tests/*"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "forwarding_url",
|
||||
"value": {
|
||||
"url": "https://www.unit.tests/",
|
||||
"status_code": 302
|
||||
}
|
||||
}
|
||||
],
|
||||
"priority": 2,
|
||||
"status": "active",
|
||||
"created_on": "2021-06-28T22:42:27.000000Z",
|
||||
"modified_on": "2021-06-28T22:43:13.000000Z"
|
||||
},
|
||||
{
|
||||
"id": "2a9140b17ffb0e6aed826049eec970b8",
|
||||
"targets": [
|
||||
{
|
||||
"target": "url",
|
||||
"constraint": {
|
||||
"operator": "matches",
|
||||
"value": "urlfwd2.unit.tests/*"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "forwarding_url",
|
||||
"value": {
|
||||
"url": "https://www.unit.tests/",
|
||||
"status_code": 301
|
||||
}
|
||||
}
|
||||
],
|
||||
"priority": 1,
|
||||
"status": "active",
|
||||
"created_on": "2021-06-25T20:10:50.000000Z",
|
||||
"modified_on": "2021-06-28T22:38:10.000000Z"
|
||||
}
|
||||
],
|
||||
"success": true,
|
||||
"errors": [],
|
||||
"messages": []
|
||||
}
|
||||
@@ -121,12 +121,12 @@ class TestManager(TestCase):
|
||||
environ['YAML_TMP_DIR'] = tmpdir.dirname
|
||||
tc = Manager(get_config_filename('simple.yaml')) \
|
||||
.sync(dry_run=False)
|
||||
self.assertEquals(25, tc)
|
||||
self.assertEquals(26, tc)
|
||||
|
||||
# try with just one of the zones
|
||||
tc = Manager(get_config_filename('simple.yaml')) \
|
||||
.sync(dry_run=False, eligible_zones=['unit.tests.'])
|
||||
self.assertEquals(19, tc)
|
||||
self.assertEquals(20, tc)
|
||||
|
||||
# the subzone, with 2 targets
|
||||
tc = Manager(get_config_filename('simple.yaml')) \
|
||||
@@ -141,18 +141,18 @@ class TestManager(TestCase):
|
||||
# Again with force
|
||||
tc = Manager(get_config_filename('simple.yaml')) \
|
||||
.sync(dry_run=False, force=True)
|
||||
self.assertEquals(25, tc)
|
||||
self.assertEquals(26, tc)
|
||||
|
||||
# Again with max_workers = 1
|
||||
tc = Manager(get_config_filename('simple.yaml'), max_workers=1) \
|
||||
.sync(dry_run=False, force=True)
|
||||
self.assertEquals(25, tc)
|
||||
self.assertEquals(26, tc)
|
||||
|
||||
# Include meta
|
||||
tc = Manager(get_config_filename('simple.yaml'), max_workers=1,
|
||||
include_meta=True) \
|
||||
.sync(dry_run=False, force=True)
|
||||
self.assertEquals(29, tc)
|
||||
self.assertEquals(30, tc)
|
||||
|
||||
def test_eligible_sources(self):
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
@@ -218,13 +218,13 @@ class TestManager(TestCase):
|
||||
fh.write('---\n{}')
|
||||
|
||||
changes = manager.compare(['in'], ['dump'], 'unit.tests.')
|
||||
self.assertEquals(19, len(changes))
|
||||
self.assertEquals(20, len(changes))
|
||||
|
||||
# Compound sources with varying support
|
||||
changes = manager.compare(['in', 'nosshfp'],
|
||||
['dump'],
|
||||
'unit.tests.')
|
||||
self.assertEquals(18, len(changes))
|
||||
self.assertEquals(19, len(changes))
|
||||
|
||||
with self.assertRaises(ManagerException) as ctx:
|
||||
manager.compare(['nope'], ['dump'], 'unit.tests.')
|
||||
|
||||
@@ -166,9 +166,15 @@ class TestCloudflareProvider(TestCase):
|
||||
json={'result': [], 'result_info': {'count': 0,
|
||||
'per_page': 0}})
|
||||
|
||||
base = '{}/234234243423aaabb334342aaa343435'.format(base)
|
||||
|
||||
# pagerules/URLFWD
|
||||
with open('tests/fixtures/cloudflare-pagerules.json') as fh:
|
||||
mock.get('{}/pagerules?status=active'.format(base),
|
||||
status_code=200, text=fh.read())
|
||||
|
||||
# records
|
||||
base = '{}/234234243423aaabb334342aaa343435/dns_records' \
|
||||
.format(base)
|
||||
base = '{}/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,
|
||||
@@ -184,16 +190,16 @@ class TestCloudflareProvider(TestCase):
|
||||
|
||||
zone = Zone('unit.tests.', [])
|
||||
provider.populate(zone)
|
||||
self.assertEquals(16, len(zone.records))
|
||||
self.assertEquals(19, len(zone.records))
|
||||
|
||||
changes = self.expected.changes(zone, provider)
|
||||
|
||||
self.assertEquals(0, len(changes))
|
||||
self.assertEquals(4, len(changes))
|
||||
|
||||
# re-populating the same zone/records comes out of cache, no calls
|
||||
again = Zone('unit.tests.', [])
|
||||
provider.populate(again)
|
||||
self.assertEquals(16, len(again.records))
|
||||
self.assertEquals(19, len(again.records))
|
||||
|
||||
def test_apply(self):
|
||||
provider = CloudflareProvider('test', 'email', 'token', retry_period=0)
|
||||
@@ -207,12 +213,12 @@ class TestCloudflareProvider(TestCase):
|
||||
'id': 42,
|
||||
}
|
||||
}, # zone create
|
||||
] + [None] * 25 # individual record creates
|
||||
] + [None] * 27 # individual record creates
|
||||
|
||||
# non-existent zone, create everything
|
||||
plan = provider.plan(self.expected)
|
||||
self.assertEquals(16, len(plan.changes))
|
||||
self.assertEquals(16, provider.apply(plan))
|
||||
self.assertEquals(17, len(plan.changes))
|
||||
self.assertEquals(17, provider.apply(plan))
|
||||
self.assertFalse(plan.exists)
|
||||
|
||||
provider._request.assert_has_calls([
|
||||
@@ -236,9 +242,31 @@ class TestCloudflareProvider(TestCase):
|
||||
'name': 'txt.unit.tests',
|
||||
'ttl': 600
|
||||
}),
|
||||
# create at least one pagerules
|
||||
call('POST', '/zones/42/pagerules', data={
|
||||
'targets': [
|
||||
{
|
||||
'target': 'url',
|
||||
'constraint': {
|
||||
'operator': 'matches',
|
||||
'value': 'urlfwd.unit.tests/'
|
||||
}
|
||||
}
|
||||
],
|
||||
'actions': [
|
||||
{
|
||||
'id': 'forwarding_url',
|
||||
'value': {
|
||||
'url': 'http://www.unit.tests',
|
||||
'status_code': 302
|
||||
}
|
||||
}
|
||||
],
|
||||
'status': 'active'
|
||||
}),
|
||||
], True)
|
||||
# expected number of total calls
|
||||
self.assertEquals(27, provider._request.call_count)
|
||||
self.assertEquals(29, provider._request.call_count)
|
||||
|
||||
provider._request.reset_mock()
|
||||
|
||||
@@ -311,6 +339,56 @@ class TestCloudflareProvider(TestCase):
|
||||
"auto_added": False
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2a9140b17ffb0e6aed826049eec970b7",
|
||||
"targets": [
|
||||
{
|
||||
"target": "url",
|
||||
"constraint": {
|
||||
"operator": "matches",
|
||||
"value": "urlfwd.unit.tests/"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "forwarding_url",
|
||||
"value": {
|
||||
"url": "https://www.unit.tests",
|
||||
"status_code": 302
|
||||
}
|
||||
}
|
||||
],
|
||||
"priority": 1,
|
||||
"status": "active",
|
||||
"created_on": "2021-06-25T20:10:50.000000Z",
|
||||
"modified_on": "2021-06-28T22:38:10.000000Z"
|
||||
},
|
||||
{
|
||||
"id": "2a9141b18ffb0e6aed826050eec970b8",
|
||||
"targets": [
|
||||
{
|
||||
"target": "url",
|
||||
"constraint": {
|
||||
"operator": "matches",
|
||||
"value": "urlfwdother.unit.tests/target"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "forwarding_url",
|
||||
"value": {
|
||||
"url": "https://target.unit.tests",
|
||||
"status_code": 301
|
||||
}
|
||||
}
|
||||
],
|
||||
"priority": 2,
|
||||
"status": "active",
|
||||
"created_on": "2021-06-25T20:10:50.000000Z",
|
||||
"modified_on": "2021-06-28T22:38:10.000000Z"
|
||||
},
|
||||
])
|
||||
|
||||
# we don't care about the POST/create return values
|
||||
@@ -319,7 +397,7 @@ class TestCloudflareProvider(TestCase):
|
||||
# Test out the create rate-limit handling, then 9 successes
|
||||
provider._request.side_effect = [
|
||||
CloudflareRateLimitError('{}'),
|
||||
] + ([None] * 3)
|
||||
] + ([None] * 5)
|
||||
|
||||
wanted = Zone('unit.tests.', [])
|
||||
wanted.add_record(Record.new(wanted, 'nc', {
|
||||
@@ -332,14 +410,27 @@ class TestCloudflareProvider(TestCase):
|
||||
'type': 'A',
|
||||
'value': '3.2.3.4'
|
||||
}))
|
||||
wanted.add_record(Record.new(wanted, 'urlfwd', {
|
||||
'ttl': 300,
|
||||
'type': 'URLFWD',
|
||||
'value': {
|
||||
'path': '/*', # path change
|
||||
'target': 'https://www.unit.tests/', # target change
|
||||
'code': 301, # status_code change
|
||||
'masking': '2',
|
||||
'query': 0,
|
||||
}
|
||||
}))
|
||||
|
||||
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.assertEquals(4, len(plan.changes))
|
||||
self.assertEquals(4, provider.apply(plan))
|
||||
self.assertTrue(plan.exists)
|
||||
# creates a the new value and then deletes all the old
|
||||
provider._request.assert_has_calls([
|
||||
call('DELETE', '/zones/42/'
|
||||
'pagerules/2a9141b18ffb0e6aed826050eec970b8'),
|
||||
call('DELETE', '/zones/ff12ab34cd5611334422ab3322997650/'
|
||||
'dns_records/fc12ab34cd5611334422ab3322997653'),
|
||||
call('DELETE', '/zones/ff12ab34cd5611334422ab3322997650/'
|
||||
@@ -351,7 +442,29 @@ class TestCloudflareProvider(TestCase):
|
||||
'name': 'ttl.unit.tests',
|
||||
'proxied': False,
|
||||
'ttl': 300
|
||||
})
|
||||
}),
|
||||
call('PUT', '/zones/42/pagerules/'
|
||||
'2a9140b17ffb0e6aed826049eec970b7', data={
|
||||
'targets': [
|
||||
{
|
||||
'target': 'url',
|
||||
'constraint': {
|
||||
'operator': 'matches',
|
||||
'value': 'urlfwd.unit.tests/*'
|
||||
}
|
||||
}
|
||||
],
|
||||
'actions': [
|
||||
{
|
||||
'id': 'forwarding_url',
|
||||
'value': {
|
||||
'url': 'https://www.unit.tests/',
|
||||
'status_code': 301
|
||||
}
|
||||
}
|
||||
],
|
||||
'status': 'active',
|
||||
}),
|
||||
])
|
||||
|
||||
def test_update_add_swap(self):
|
||||
@@ -500,6 +613,56 @@ class TestCloudflareProvider(TestCase):
|
||||
"auto_added": False
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2a9140b17ffb0e6aed826049eec974b7",
|
||||
"targets": [
|
||||
{
|
||||
"target": "url",
|
||||
"constraint": {
|
||||
"operator": "matches",
|
||||
"value": "urlfwd1.unit.tests/"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "forwarding_url",
|
||||
"value": {
|
||||
"url": "https://www.unit.tests",
|
||||
"status_code": 302
|
||||
}
|
||||
}
|
||||
],
|
||||
"priority": 1,
|
||||
"status": "active",
|
||||
"created_on": "2021-06-25T20:10:50.000000Z",
|
||||
"modified_on": "2021-06-28T22:38:10.000000Z"
|
||||
},
|
||||
{
|
||||
"id": "2a9141b18ffb0e6aed826054eec970b8",
|
||||
"targets": [
|
||||
{
|
||||
"target": "url",
|
||||
"constraint": {
|
||||
"operator": "matches",
|
||||
"value": "urlfwd1.unit.tests/target"
|
||||
}
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"id": "forwarding_url",
|
||||
"value": {
|
||||
"url": "https://target.unit.tests",
|
||||
"status_code": 301
|
||||
}
|
||||
}
|
||||
],
|
||||
"priority": 2,
|
||||
"status": "active",
|
||||
"created_on": "2021-06-25T20:10:50.000000Z",
|
||||
"modified_on": "2021-06-28T22:38:10.000000Z"
|
||||
},
|
||||
])
|
||||
|
||||
provider._request = Mock()
|
||||
@@ -513,6 +676,8 @@ class TestCloudflareProvider(TestCase):
|
||||
}, # zone create
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
]
|
||||
|
||||
# Add something and delete something
|
||||
@@ -523,14 +688,46 @@ class TestCloudflareProvider(TestCase):
|
||||
# This matches the zone data above, one to delete, one to leave
|
||||
'values': ['ns1.foo.bar.', 'ns2.foo.bar.'],
|
||||
})
|
||||
exstingurlfwd = Record.new(zone, 'urlfwd1', {
|
||||
'ttl': 300,
|
||||
'type': 'URLFWD',
|
||||
'values': [
|
||||
{
|
||||
'path': '/',
|
||||
'target': 'https://www.unit.tests',
|
||||
'code': 302,
|
||||
'masking': '2',
|
||||
'query': 0,
|
||||
},
|
||||
{
|
||||
'path': '/target',
|
||||
'target': 'https://target.unit.tests',
|
||||
'code': 301,
|
||||
'masking': '2',
|
||||
'query': 0,
|
||||
}
|
||||
]
|
||||
})
|
||||
new = Record.new(zone, '', {
|
||||
'ttl': 300,
|
||||
'type': 'NS',
|
||||
# This leaves one and deletes one
|
||||
'value': 'ns2.foo.bar.',
|
||||
})
|
||||
newurlfwd = Record.new(zone, 'urlfwd1', {
|
||||
'ttl': 300,
|
||||
'type': 'URLFWD',
|
||||
'value': {
|
||||
'path': '/',
|
||||
'target': 'https://www.unit.tests',
|
||||
'code': 302,
|
||||
'masking': '2',
|
||||
'query': 0,
|
||||
}
|
||||
})
|
||||
change = Update(existing, new)
|
||||
plan = Plan(zone, zone, [change], True)
|
||||
changeurlfwd = Update(exstingurlfwd, newurlfwd)
|
||||
plan = Plan(zone, zone, [change, changeurlfwd], True)
|
||||
provider._apply(plan)
|
||||
|
||||
# Get zones, create zone, create a record, delete a record
|
||||
@@ -548,7 +745,31 @@ class TestCloudflareProvider(TestCase):
|
||||
'ttl': 300
|
||||
}),
|
||||
call('DELETE', '/zones/42/dns_records/'
|
||||
'fc12ab34cd5611334422ab3322997653')
|
||||
'fc12ab34cd5611334422ab3322997653'),
|
||||
call('PUT', '/zones/42/pagerules/'
|
||||
'2a9140b17ffb0e6aed826049eec974b7', data={
|
||||
'targets': [
|
||||
{
|
||||
'target': 'url',
|
||||
'constraint': {
|
||||
'operator': 'matches',
|
||||
'value': 'urlfwd1.unit.tests/'
|
||||
}
|
||||
}
|
||||
],
|
||||
'actions': [
|
||||
{
|
||||
'id': 'forwarding_url',
|
||||
'value': {
|
||||
'url': 'https://www.unit.tests',
|
||||
'status_code': 302
|
||||
}
|
||||
}
|
||||
],
|
||||
'status': 'active'
|
||||
}),
|
||||
call('DELETE', '/zones/42/pagerules/'
|
||||
'2a9141b18ffb0e6aed826054eec970b8'),
|
||||
])
|
||||
|
||||
def test_ptr(self):
|
||||
@@ -1410,3 +1631,11 @@ class TestCloudflareProvider(TestCase):
|
||||
with self.assertRaises(CloudflareRateLimitError) as ctx:
|
||||
provider.zone_records(zone)
|
||||
self.assertEquals('last', text_type(ctx.exception))
|
||||
|
||||
def test_ttl_mapping(self):
|
||||
provider = CloudflareProvider('test', 'email', 'token')
|
||||
|
||||
self.assertEquals(120, provider._ttl_data(120))
|
||||
self.assertEquals(120, provider._ttl_data(120))
|
||||
self.assertEquals(3600, provider._ttl_data(3600))
|
||||
self.assertEquals(300, provider._ttl_data(1))
|
||||
|
||||
@@ -132,7 +132,7 @@ class TestConstellixProvider(TestCase):
|
||||
plan = provider.plan(self.expected)
|
||||
|
||||
# No root NS, no ignored, no excluded, no unsupported
|
||||
n = len(self.expected.records) - 7
|
||||
n = len(self.expected.records) - 8
|
||||
self.assertEquals(n, len(plan.changes))
|
||||
self.assertEquals(n, provider.apply(plan))
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ class TestDigitalOceanProvider(TestCase):
|
||||
plan = provider.plan(self.expected)
|
||||
|
||||
# No root NS, no ignored, no excluded, no unsupported
|
||||
n = len(self.expected.records) - 9
|
||||
n = len(self.expected.records) - 10
|
||||
self.assertEquals(n, len(plan.changes))
|
||||
self.assertEquals(n, provider.apply(plan))
|
||||
self.assertFalse(plan.exists)
|
||||
|
||||
@@ -137,7 +137,7 @@ class TestDnsimpleProvider(TestCase):
|
||||
plan = provider.plan(self.expected)
|
||||
|
||||
# No root NS, no ignored, no excluded
|
||||
n = len(self.expected.records) - 7
|
||||
n = len(self.expected.records) - 8
|
||||
self.assertEquals(n, len(plan.changes))
|
||||
self.assertEquals(n, provider.apply(plan))
|
||||
self.assertFalse(plan.exists)
|
||||
|
||||
@@ -134,7 +134,7 @@ class TestDnsMadeEasyProvider(TestCase):
|
||||
plan = provider.plan(self.expected)
|
||||
|
||||
# No root NS, no ignored, no excluded, no unsupported
|
||||
n = len(self.expected.records) - 9
|
||||
n = len(self.expected.records) - 10
|
||||
self.assertEquals(n, len(plan.changes))
|
||||
self.assertEquals(n, provider.apply(plan))
|
||||
|
||||
|
||||
@@ -374,7 +374,7 @@ class TestEasyDNSProvider(TestCase):
|
||||
plan = provider.plan(self.expected)
|
||||
|
||||
# No root NS, no ignored, no excluded, no unsupported
|
||||
n = len(self.expected.records) - 8
|
||||
n = len(self.expected.records) - 9
|
||||
self.assertEquals(n, len(plan.changes))
|
||||
self.assertEquals(n, provider.apply(plan))
|
||||
self.assertFalse(plan.exists)
|
||||
|
||||
@@ -193,7 +193,7 @@ class TestGandiProvider(TestCase):
|
||||
plan = provider.plan(self.expected)
|
||||
|
||||
# No root NS, no ignored, no excluded, no LOC
|
||||
n = len(self.expected.records) - 5
|
||||
n = len(self.expected.records) - 6
|
||||
self.assertEquals(n, len(plan.changes))
|
||||
self.assertEquals(n, provider.apply(plan))
|
||||
self.assertFalse(plan.exists)
|
||||
|
||||
@@ -108,7 +108,7 @@ class TestHetznerProvider(TestCase):
|
||||
plan = provider.plan(self.expected)
|
||||
|
||||
# No root NS, no ignored, no excluded, no unsupported
|
||||
n = len(self.expected.records) - 9
|
||||
n = len(self.expected.records) - 10
|
||||
self.assertEquals(n, len(plan.changes))
|
||||
self.assertEquals(n, provider.apply(plan))
|
||||
self.assertFalse(plan.exists)
|
||||
|
||||
@@ -109,6 +109,17 @@ class TestNs1Provider(TestCase):
|
||||
'value': 'ca.unit.tests',
|
||||
},
|
||||
}))
|
||||
expected.add(Record.new(zone, 'urlfwd', {
|
||||
'ttl': 41,
|
||||
'type': 'URLFWD',
|
||||
'value': {
|
||||
'path': '/',
|
||||
'target': 'http://foo.unit.tests',
|
||||
'code': 301,
|
||||
'masking': 2,
|
||||
'query': 0,
|
||||
},
|
||||
}))
|
||||
|
||||
ns1_records = [{
|
||||
'type': 'A',
|
||||
@@ -164,6 +175,11 @@ class TestNs1Provider(TestCase):
|
||||
'ttl': 40,
|
||||
'short_answers': ['0 issue ca.unit.tests'],
|
||||
'domain': 'unit.tests.',
|
||||
}, {
|
||||
'type': 'URLFWD',
|
||||
'ttl': 41,
|
||||
'short_answers': ['/ http://foo.unit.tests 301 2 0'],
|
||||
'domain': 'urlfwd.unit.tests.',
|
||||
}]
|
||||
|
||||
@patch('ns1.rest.records.Records.retrieve')
|
||||
@@ -345,7 +361,7 @@ class TestNs1Provider(TestCase):
|
||||
# Test out the create rate-limit handling, then 9 successes
|
||||
record_create_mock.side_effect = [
|
||||
RateLimitException('boo', period=0),
|
||||
] + ([None] * 9)
|
||||
] + ([None] * 10)
|
||||
|
||||
got_n = provider.apply(plan)
|
||||
self.assertEquals(expected_n, got_n)
|
||||
|
||||
@@ -185,7 +185,7 @@ class TestPowerDnsProvider(TestCase):
|
||||
expected = Zone('unit.tests.', [])
|
||||
source = YamlProvider('test', join(dirname(__file__), 'config'))
|
||||
source.populate(expected)
|
||||
expected_n = len(expected.records) - 3
|
||||
expected_n = len(expected.records) - 4
|
||||
self.assertEquals(19, expected_n)
|
||||
|
||||
# No diffs == no changes
|
||||
@@ -291,7 +291,7 @@ class TestPowerDnsProvider(TestCase):
|
||||
expected = Zone('unit.tests.', [])
|
||||
source = YamlProvider('test', join(dirname(__file__), 'config'))
|
||||
source.populate(expected)
|
||||
self.assertEquals(22, len(expected.records))
|
||||
self.assertEquals(23, len(expected.records))
|
||||
|
||||
# A small change to a single record
|
||||
with requests_mock() as mock:
|
||||
|
||||
@@ -35,7 +35,7 @@ class TestYamlProvider(TestCase):
|
||||
|
||||
# without it we see everything
|
||||
source.populate(zone)
|
||||
self.assertEquals(22, len(zone.records))
|
||||
self.assertEquals(23, len(zone.records))
|
||||
|
||||
source.populate(dynamic_zone)
|
||||
self.assertEquals(6, len(dynamic_zone.records))
|
||||
@@ -58,12 +58,12 @@ class TestYamlProvider(TestCase):
|
||||
|
||||
# We add everything
|
||||
plan = target.plan(zone)
|
||||
self.assertEquals(19, len([c for c in plan.changes
|
||||
self.assertEquals(20, len([c for c in plan.changes
|
||||
if isinstance(c, Create)]))
|
||||
self.assertFalse(isfile(yaml_file))
|
||||
|
||||
# Now actually do it
|
||||
self.assertEquals(19, target.apply(plan))
|
||||
self.assertEquals(20, target.apply(plan))
|
||||
self.assertTrue(isfile(yaml_file))
|
||||
|
||||
# Dynamic plan
|
||||
@@ -87,7 +87,7 @@ class TestYamlProvider(TestCase):
|
||||
|
||||
# A 2nd sync should still create everything
|
||||
plan = target.plan(zone)
|
||||
self.assertEquals(19, len([c for c in plan.changes
|
||||
self.assertEquals(20, len([c for c in plan.changes
|
||||
if isinstance(c, Create)]))
|
||||
|
||||
with open(yaml_file) as fh:
|
||||
@@ -107,6 +107,7 @@ class TestYamlProvider(TestCase):
|
||||
self.assertTrue('values' in data.pop('sub'))
|
||||
self.assertTrue('values' in data.pop('txt'))
|
||||
self.assertTrue('values' in data.pop('loc'))
|
||||
self.assertTrue('values' in data.pop('urlfwd'))
|
||||
# these are stored as singular 'value'
|
||||
self.assertTrue('value' in data.pop('_imap._tcp'))
|
||||
self.assertTrue('value' in data.pop('_pop3._tcp'))
|
||||
@@ -248,7 +249,7 @@ class TestSplitYamlProvider(TestCase):
|
||||
|
||||
# without it we see everything
|
||||
source.populate(zone)
|
||||
self.assertEquals(19, len(zone.records))
|
||||
self.assertEquals(20, len(zone.records))
|
||||
|
||||
source.populate(dynamic_zone)
|
||||
self.assertEquals(5, len(dynamic_zone.records))
|
||||
@@ -263,12 +264,12 @@ class TestSplitYamlProvider(TestCase):
|
||||
|
||||
# We add everything
|
||||
plan = target.plan(zone)
|
||||
self.assertEquals(16, len([c for c in plan.changes
|
||||
self.assertEquals(17, len([c for c in plan.changes
|
||||
if isinstance(c, Create)]))
|
||||
self.assertFalse(isdir(zone_dir))
|
||||
|
||||
# Now actually do it
|
||||
self.assertEquals(16, target.apply(plan))
|
||||
self.assertEquals(17, target.apply(plan))
|
||||
|
||||
# Dynamic plan
|
||||
plan = target.plan(dynamic_zone)
|
||||
@@ -291,7 +292,7 @@ class TestSplitYamlProvider(TestCase):
|
||||
|
||||
# A 2nd sync should still create everything
|
||||
plan = target.plan(zone)
|
||||
self.assertEquals(16, len([c for c in plan.changes
|
||||
self.assertEquals(17, len([c for c in plan.changes
|
||||
if isinstance(c, Create)]))
|
||||
|
||||
yaml_file = join(zone_dir, '$unit.tests.yaml')
|
||||
@@ -306,7 +307,8 @@ class TestSplitYamlProvider(TestCase):
|
||||
|
||||
# These records are stored as plural "values." Check each file to
|
||||
# ensure correctness.
|
||||
for record_name in ('_srv._tcp', 'mx', 'naptr', 'sub', 'txt'):
|
||||
for record_name in ('_srv._tcp', 'mx', 'naptr', 'sub', 'txt',
|
||||
'urlfwd'):
|
||||
yaml_file = join(zone_dir, '{}.yaml'.format(record_name))
|
||||
self.assertTrue(isfile(yaml_file))
|
||||
with open(yaml_file) as fh:
|
||||
|
||||
@@ -12,8 +12,8 @@ from octodns.record import ARecord, AaaaRecord, AliasRecord, CaaRecord, \
|
||||
CaaValue, CnameRecord, DnameRecord, Create, Delete, GeoValue, LocRecord, \
|
||||
LocValue, MxRecord, MxValue, NaptrRecord, NaptrValue, NsRecord, \
|
||||
PtrRecord, Record, SshfpRecord, SshfpValue, SpfRecord, SrvRecord, \
|
||||
SrvValue, TxtRecord, Update, ValidationError, _Dynamic, _DynamicPool, \
|
||||
_DynamicRule
|
||||
SrvValue, TxtRecord, Update, UrlfwdRecord, UrlfwdValue, ValidationError, \
|
||||
_Dynamic, _DynamicPool, _DynamicRule
|
||||
from octodns.zone import Zone
|
||||
|
||||
from helpers import DynamicProvider, GeoProvider, SimpleProvider
|
||||
@@ -884,6 +884,112 @@ class TestRecord(TestCase):
|
||||
b_value = 'b other'
|
||||
self.assertMultipleValues(TxtRecord, a_values, b_value)
|
||||
|
||||
def test_urlfwd(self):
|
||||
a_values = [{
|
||||
'path': '/',
|
||||
'target': 'http://foo',
|
||||
'code': 301,
|
||||
'masking': 2,
|
||||
'query': 0,
|
||||
}, {
|
||||
'path': '/target',
|
||||
'target': 'http://target',
|
||||
'code': 302,
|
||||
'masking': 2,
|
||||
'query': 0,
|
||||
}]
|
||||
a_data = {'ttl': 30, 'values': a_values}
|
||||
a = UrlfwdRecord(self.zone, 'a', a_data)
|
||||
self.assertEquals('a', a.name)
|
||||
self.assertEquals('a.unit.tests.', a.fqdn)
|
||||
self.assertEquals(30, a.ttl)
|
||||
self.assertEquals(a_values[0]['path'], a.values[0].path)
|
||||
self.assertEquals(a_values[0]['target'], a.values[0].target)
|
||||
self.assertEquals(a_values[0]['code'], a.values[0].code)
|
||||
self.assertEquals(a_values[0]['masking'], a.values[0].masking)
|
||||
self.assertEquals(a_values[0]['query'], a.values[0].query)
|
||||
self.assertEquals(a_values[1]['path'], a.values[1].path)
|
||||
self.assertEquals(a_values[1]['target'], a.values[1].target)
|
||||
self.assertEquals(a_values[1]['code'], a.values[1].code)
|
||||
self.assertEquals(a_values[1]['masking'], a.values[1].masking)
|
||||
self.assertEquals(a_values[1]['query'], a.values[1].query)
|
||||
self.assertEquals(a_data, a.data)
|
||||
|
||||
b_value = {
|
||||
'path': '/',
|
||||
'target': 'http://location',
|
||||
'code': 301,
|
||||
'masking': 2,
|
||||
'query': 0,
|
||||
}
|
||||
b_data = {'ttl': 30, 'value': b_value}
|
||||
b = UrlfwdRecord(self.zone, 'b', b_data)
|
||||
self.assertEquals(b_value['path'], b.values[0].path)
|
||||
self.assertEquals(b_value['target'], b.values[0].target)
|
||||
self.assertEquals(b_value['code'], b.values[0].code)
|
||||
self.assertEquals(b_value['masking'], b.values[0].masking)
|
||||
self.assertEquals(b_value['query'], b.values[0].query)
|
||||
self.assertEquals(b_data, b.data)
|
||||
|
||||
target = SimpleProvider()
|
||||
# No changes with self
|
||||
self.assertFalse(a.changes(a, target))
|
||||
# Diff in path causes change
|
||||
other = UrlfwdRecord(self.zone, 'a', {'ttl': 30, 'values': a_values})
|
||||
other.values[0].path = '/change'
|
||||
change = a.changes(other, target)
|
||||
self.assertEqual(change.existing, a)
|
||||
self.assertEqual(change.new, other)
|
||||
# Diff in target causes change
|
||||
other = UrlfwdRecord(self.zone, 'a', {'ttl': 30, 'values': a_values})
|
||||
other.values[0].target = 'http://target'
|
||||
change = a.changes(other, target)
|
||||
self.assertEqual(change.existing, a)
|
||||
self.assertEqual(change.new, other)
|
||||
# Diff in code causes change
|
||||
other = UrlfwdRecord(self.zone, 'a', {'ttl': 30, 'values': a_values})
|
||||
other.values[0].code = 302
|
||||
change = a.changes(other, target)
|
||||
self.assertEqual(change.existing, a)
|
||||
self.assertEqual(change.new, other)
|
||||
# Diff in masking causes change
|
||||
other = UrlfwdRecord(self.zone, 'a', {'ttl': 30, 'values': a_values})
|
||||
other.values[0].masking = 0
|
||||
change = a.changes(other, target)
|
||||
self.assertEqual(change.existing, a)
|
||||
self.assertEqual(change.new, other)
|
||||
# Diff in query causes change
|
||||
other = UrlfwdRecord(self.zone, 'a', {'ttl': 30, 'values': a_values})
|
||||
other.values[0].query = 1
|
||||
change = a.changes(other, target)
|
||||
self.assertEqual(change.existing, a)
|
||||
self.assertEqual(change.new, other)
|
||||
|
||||
# hash
|
||||
v = UrlfwdValue({
|
||||
'path': '/',
|
||||
'target': 'http://place',
|
||||
'code': 301,
|
||||
'masking': 2,
|
||||
'query': 0,
|
||||
})
|
||||
o = UrlfwdValue({
|
||||
'path': '/location',
|
||||
'target': 'http://redirect',
|
||||
'code': 302,
|
||||
'masking': 2,
|
||||
'query': 0,
|
||||
})
|
||||
values = set()
|
||||
values.add(v)
|
||||
self.assertTrue(v in values)
|
||||
self.assertFalse(o in values)
|
||||
values.add(o)
|
||||
self.assertTrue(o in values)
|
||||
|
||||
# __repr__ doesn't blow up
|
||||
a.__repr__()
|
||||
|
||||
def test_record_new(self):
|
||||
txt = Record.new(self.zone, 'txt', {
|
||||
'ttl': 44,
|
||||
@@ -3019,6 +3125,203 @@ class TestRecordValidation(TestCase):
|
||||
# should be chunked values, with quoting
|
||||
self.assertEquals(single.chunked_values, chunked.chunked_values)
|
||||
|
||||
def test_URLFWD(self):
|
||||
# doesn't blow up
|
||||
Record.new(self.zone, '', {
|
||||
'type': 'URLFWD',
|
||||
'ttl': 600,
|
||||
'value': {
|
||||
'path': '/',
|
||||
'target': 'http://foo',
|
||||
'code': 301,
|
||||
'masking': 2,
|
||||
'query': 0,
|
||||
}
|
||||
})
|
||||
Record.new(self.zone, '', {
|
||||
'type': 'URLFWD',
|
||||
'ttl': 600,
|
||||
'values': [{
|
||||
'path': '/',
|
||||
'target': 'http://foo',
|
||||
'code': 301,
|
||||
'masking': 2,
|
||||
'query': 0,
|
||||
}, {
|
||||
'path': '/target',
|
||||
'target': 'http://target',
|
||||
'code': 302,
|
||||
'masking': 2,
|
||||
'query': 0,
|
||||
}]
|
||||
})
|
||||
|
||||
# missing path
|
||||
with self.assertRaises(ValidationError) as ctx:
|
||||
Record.new(self.zone, '', {
|
||||
'type': 'URLFWD',
|
||||
'ttl': 600,
|
||||
'value': {
|
||||
'target': 'http://foo',
|
||||
'code': 301,
|
||||
'masking': 2,
|
||||
'query': 0,
|
||||
}
|
||||
})
|
||||
self.assertEquals(['missing path'], ctx.exception.reasons)
|
||||
|
||||
# missing target
|
||||
with self.assertRaises(ValidationError) as ctx:
|
||||
Record.new(self.zone, '', {
|
||||
'type': 'URLFWD',
|
||||
'ttl': 600,
|
||||
'value': {
|
||||
'path': '/',
|
||||
'code': 301,
|
||||
'masking': 2,
|
||||
'query': 0,
|
||||
}
|
||||
})
|
||||
self.assertEquals(['missing target'], ctx.exception.reasons)
|
||||
|
||||
# missing code
|
||||
with self.assertRaises(ValidationError) as ctx:
|
||||
Record.new(self.zone, '', {
|
||||
'type': 'URLFWD',
|
||||
'ttl': 600,
|
||||
'value': {
|
||||
'path': '/',
|
||||
'target': 'http://foo',
|
||||
'masking': 2,
|
||||
'query': 0,
|
||||
}
|
||||
})
|
||||
self.assertEquals(['missing code'], ctx.exception.reasons)
|
||||
|
||||
# invalid code
|
||||
with self.assertRaises(ValidationError) as ctx:
|
||||
Record.new(self.zone, '', {
|
||||
'type': 'URLFWD',
|
||||
'ttl': 600,
|
||||
'value': {
|
||||
'path': '/',
|
||||
'target': 'http://foo',
|
||||
'code': 'nope',
|
||||
'masking': 2,
|
||||
'query': 0,
|
||||
}
|
||||
})
|
||||
self.assertEquals(['invalid return code "nope"'],
|
||||
ctx.exception.reasons)
|
||||
|
||||
# unrecognized code
|
||||
with self.assertRaises(ValidationError) as ctx:
|
||||
Record.new(self.zone, '', {
|
||||
'type': 'URLFWD',
|
||||
'ttl': 600,
|
||||
'value': {
|
||||
'path': '/',
|
||||
'target': 'http://foo',
|
||||
'code': 3,
|
||||
'masking': 2,
|
||||
'query': 0,
|
||||
}
|
||||
})
|
||||
self.assertEquals(['unrecognized return code "3"'],
|
||||
ctx.exception.reasons)
|
||||
|
||||
# missing masking
|
||||
with self.assertRaises(ValidationError) as ctx:
|
||||
Record.new(self.zone, '', {
|
||||
'type': 'URLFWD',
|
||||
'ttl': 600,
|
||||
'value': {
|
||||
'path': '/',
|
||||
'target': 'http://foo',
|
||||
'code': 301,
|
||||
'query': 0,
|
||||
}
|
||||
})
|
||||
self.assertEquals(['missing masking'], ctx.exception.reasons)
|
||||
|
||||
# invalid masking
|
||||
with self.assertRaises(ValidationError) as ctx:
|
||||
Record.new(self.zone, '', {
|
||||
'type': 'URLFWD',
|
||||
'ttl': 600,
|
||||
'value': {
|
||||
'path': '/',
|
||||
'target': 'http://foo',
|
||||
'code': 301,
|
||||
'masking': 'nope',
|
||||
'query': 0,
|
||||
}
|
||||
})
|
||||
self.assertEquals(['invalid masking setting "nope"'],
|
||||
ctx.exception.reasons)
|
||||
|
||||
# unrecognized masking
|
||||
with self.assertRaises(ValidationError) as ctx:
|
||||
Record.new(self.zone, '', {
|
||||
'type': 'URLFWD',
|
||||
'ttl': 600,
|
||||
'value': {
|
||||
'path': '/',
|
||||
'target': 'http://foo',
|
||||
'code': 301,
|
||||
'masking': 3,
|
||||
'query': 0,
|
||||
}
|
||||
})
|
||||
self.assertEquals(['unrecognized masking setting "3"'],
|
||||
ctx.exception.reasons)
|
||||
|
||||
# missing query
|
||||
with self.assertRaises(ValidationError) as ctx:
|
||||
Record.new(self.zone, '', {
|
||||
'type': 'URLFWD',
|
||||
'ttl': 600,
|
||||
'value': {
|
||||
'path': '/',
|
||||
'target': 'http://foo',
|
||||
'code': 301,
|
||||
'masking': 2,
|
||||
}
|
||||
})
|
||||
self.assertEquals(['missing query'], ctx.exception.reasons)
|
||||
|
||||
# invalid query
|
||||
with self.assertRaises(ValidationError) as ctx:
|
||||
Record.new(self.zone, '', {
|
||||
'type': 'URLFWD',
|
||||
'ttl': 600,
|
||||
'value': {
|
||||
'path': '/',
|
||||
'target': 'http://foo',
|
||||
'code': 301,
|
||||
'masking': 2,
|
||||
'query': 'nope',
|
||||
}
|
||||
})
|
||||
self.assertEquals(['invalid query setting "nope"'],
|
||||
ctx.exception.reasons)
|
||||
|
||||
# unrecognized query
|
||||
with self.assertRaises(ValidationError) as ctx:
|
||||
Record.new(self.zone, '', {
|
||||
'type': 'URLFWD',
|
||||
'ttl': 600,
|
||||
'value': {
|
||||
'path': '/',
|
||||
'target': 'http://foo',
|
||||
'code': 301,
|
||||
'masking': 2,
|
||||
'query': 3,
|
||||
}
|
||||
})
|
||||
self.assertEquals(['unrecognized query setting "3"'],
|
||||
ctx.exception.reasons)
|
||||
|
||||
|
||||
class TestDynamicRecords(TestCase):
|
||||
zone = Zone('unit.tests.', [])
|
||||
|
||||
Reference in New Issue
Block a user