mirror of
				https://github.com/github/octodns.git
				synced 2024-05-11 05:55:00 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			470 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			470 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #
 | |
| #
 | |
| #
 | |
| 
 | |
| from __future__ import absolute_import, division, print_function, \
 | |
|     unicode_literals
 | |
| 
 | |
| from unittest import TestCase
 | |
| 
 | |
| from mock import patch, call
 | |
| from ovh import APIError, ResourceNotFoundError, InvalidCredential
 | |
| 
 | |
| from octodns.provider.ovh import OvhProvider
 | |
| from octodns.record import Record
 | |
| from octodns.zone import Zone
 | |
| 
 | |
| 
 | |
| class TestOvhProvider(TestCase):
 | |
|     api_record = []
 | |
|     valid_dkim = []
 | |
|     invalid_dkim = []
 | |
| 
 | |
|     valid_dkim_key = "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxLaG16G4SaE" \
 | |
|                      "cXVdiIxTg7gKSGbHKQLm30CHib1h9FzS9nkcyvQSyQj1rMFyqC//" \
 | |
|                      "tft3ohx3nvJl+bGCWxdtLYDSmir9PW54e5CTdxEh8MWRkBO3StF6" \
 | |
|                      "QG/tAh3aTGDmkqhIJGLb87iHvpmVKqURmEUzJPv5KPJfWLofADI+" \
 | |
|                      "q9lQIDAQAB"
 | |
| 
 | |
|     zone = Zone('unit.tests.', [])
 | |
|     expected = set()
 | |
| 
 | |
|     # A, subdomain=''
 | |
|     api_record.append({
 | |
|         'fieldType': 'A',
 | |
|         'ttl': 100,
 | |
|         'target': '1.2.3.4',
 | |
|         'subDomain': '',
 | |
|         'id': 1
 | |
|     })
 | |
|     expected.add(Record.new(zone, '', {
 | |
|         'ttl': 100,
 | |
|         'type': 'A',
 | |
|         'value': '1.2.3.4',
 | |
|     }))
 | |
| 
 | |
|     # A, subdomain='sub
 | |
|     api_record.append({
 | |
|         'fieldType': 'A',
 | |
|         'ttl': 200,
 | |
|         'target': '1.2.3.4',
 | |
|         'subDomain': 'sub',
 | |
|         'id': 2
 | |
|     })
 | |
|     expected.add(Record.new(zone, 'sub', {
 | |
|         'ttl': 200,
 | |
|         'type': 'A',
 | |
|         'value': '1.2.3.4',
 | |
|     }))
 | |
| 
 | |
|     # CNAME
 | |
|     api_record.append({
 | |
|         'fieldType': 'CNAME',
 | |
|         'ttl': 300,
 | |
|         'target': 'unit.tests.',
 | |
|         'subDomain': 'www2',
 | |
|         'id': 3
 | |
|     })
 | |
|     expected.add(Record.new(zone, 'www2', {
 | |
|         'ttl': 300,
 | |
|         'type': 'CNAME',
 | |
|         'value': 'unit.tests.',
 | |
|     }))
 | |
| 
 | |
|     # MX
 | |
|     api_record.append({
 | |
|         'fieldType': 'MX',
 | |
|         'ttl': 400,
 | |
|         'target': '10 mx1.unit.tests.',
 | |
|         'subDomain': '',
 | |
|         'id': 4
 | |
|     })
 | |
|     expected.add(Record.new(zone, '', {
 | |
|         'ttl': 400,
 | |
|         'type': 'MX',
 | |
|         'values': [{
 | |
|             'preference': 10,
 | |
|             'exchange': 'mx1.unit.tests.',
 | |
|         }]
 | |
|     }))
 | |
| 
 | |
|     # NAPTR
 | |
|     api_record.append({
 | |
|         'fieldType': 'NAPTR',
 | |
|         'ttl': 500,
 | |
|         'target': '10 100 "S" "SIP+D2U" "!^.*$!sip:info@bar.example.com!" .',
 | |
|         'subDomain': 'naptr',
 | |
|         'id': 5
 | |
|     })
 | |
|     expected.add(Record.new(zone, 'naptr', {
 | |
|         'ttl': 500,
 | |
|         'type': 'NAPTR',
 | |
|         'values': [{
 | |
|             'flags': 'S',
 | |
|             'order': 10,
 | |
|             'preference': 100,
 | |
|             'regexp': '!^.*$!sip:info@bar.example.com!',
 | |
|             'replacement': '.',
 | |
|             'service': 'SIP+D2U',
 | |
|         }]
 | |
|     }))
 | |
| 
 | |
|     # NS
 | |
|     api_record.append({
 | |
|         'fieldType': 'NS',
 | |
|         'ttl': 600,
 | |
|         'target': 'ns1.unit.tests.',
 | |
|         'subDomain': '',
 | |
|         'id': 6
 | |
|     })
 | |
|     api_record.append({
 | |
|         'fieldType': 'NS',
 | |
|         'ttl': 600,
 | |
|         'target': 'ns2.unit.tests.',
 | |
|         'subDomain': '',
 | |
|         'id': 7
 | |
|     })
 | |
|     expected.add(Record.new(zone, '', {
 | |
|         'ttl': 600,
 | |
|         'type': 'NS',
 | |
|         'values': ['ns1.unit.tests.', 'ns2.unit.tests.'],
 | |
|     }))
 | |
| 
 | |
|     # NS with sub
 | |
|     api_record.append({
 | |
|         'fieldType': 'NS',
 | |
|         'ttl': 700,
 | |
|         'target': 'ns3.unit.tests.',
 | |
|         'subDomain': 'www3',
 | |
|         'id': 8
 | |
|     })
 | |
|     api_record.append({
 | |
|         'fieldType': 'NS',
 | |
|         'ttl': 700,
 | |
|         'target': 'ns4.unit.tests.',
 | |
|         'subDomain': 'www3',
 | |
|         'id': 9
 | |
|     })
 | |
|     expected.add(Record.new(zone, 'www3', {
 | |
|         'ttl': 700,
 | |
|         'type': 'NS',
 | |
|         'values': ['ns3.unit.tests.', 'ns4.unit.tests.'],
 | |
|     }))
 | |
| 
 | |
|     api_record.append({
 | |
|         'fieldType': 'SRV',
 | |
|         'ttl': 800,
 | |
|         'target': '10 20 30 foo-1.unit.tests.',
 | |
|         'subDomain': '_srv._tcp',
 | |
|         'id': 10
 | |
|     })
 | |
|     api_record.append({
 | |
|         'fieldType': 'SRV',
 | |
|         'ttl': 800,
 | |
|         'target': '40 50 60 foo-2.unit.tests.',
 | |
|         'subDomain': '_srv._tcp',
 | |
|         'id': 11
 | |
|     })
 | |
|     expected.add(Record.new(zone, '_srv._tcp', {
 | |
|         'ttl': 800,
 | |
|         'type': 'SRV',
 | |
|         'values': [{
 | |
|             'priority': 10,
 | |
|             'weight': 20,
 | |
|             'port': 30,
 | |
|             'target': 'foo-1.unit.tests.',
 | |
|         }, {
 | |
|             'priority': 40,
 | |
|             'weight': 50,
 | |
|             'port': 60,
 | |
|             'target': 'foo-2.unit.tests.',
 | |
|         }]
 | |
|     }))
 | |
| 
 | |
|     # PTR
 | |
|     api_record.append({
 | |
|         'fieldType': 'PTR',
 | |
|         'ttl': 900,
 | |
|         'target': 'unit.tests.',
 | |
|         'subDomain': '4',
 | |
|         'id': 12
 | |
|     })
 | |
|     expected.add(Record.new(zone, '4', {
 | |
|         'ttl': 900,
 | |
|         'type': 'PTR',
 | |
|         'value': 'unit.tests.'
 | |
|     }))
 | |
| 
 | |
|     # SPF
 | |
|     api_record.append({
 | |
|         'fieldType': 'SPF',
 | |
|         'ttl': 1000,
 | |
|         'target': 'v=spf1 include:unit.texts.redirect ~all',
 | |
|         'subDomain': '',
 | |
|         'id': 13
 | |
|     })
 | |
|     expected.add(Record.new(zone, '', {
 | |
|         'ttl': 1000,
 | |
|         'type': 'SPF',
 | |
|         'value': 'v=spf1 include:unit.texts.redirect ~all'
 | |
|     }))
 | |
| 
 | |
|     # SSHFP
 | |
|     api_record.append({
 | |
|         'fieldType': 'SSHFP',
 | |
|         'ttl': 1100,
 | |
|         'target': '1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73 ',
 | |
|         'subDomain': '',
 | |
|         'id': 14
 | |
|     })
 | |
|     expected.add(Record.new(zone, '', {
 | |
|         'ttl': 1100,
 | |
|         'type': 'SSHFP',
 | |
|         'value': {
 | |
|             'algorithm': 1,
 | |
|             'fingerprint': 'bf6b6825d2977c511a475bbefb88aad54a92ac73',
 | |
|             'fingerprint_type': 1
 | |
|         }
 | |
|     }))
 | |
| 
 | |
|     # AAAA
 | |
|     api_record.append({
 | |
|         'fieldType': 'AAAA',
 | |
|         'ttl': 1200,
 | |
|         'target': '1:1ec:1::1',
 | |
|         'subDomain': '',
 | |
|         'id': 15
 | |
|     })
 | |
|     expected.add(Record.new(zone, '', {
 | |
|         'ttl': 200,
 | |
|         'type': 'AAAA',
 | |
|         'value': '1:1ec:1::1',
 | |
|     }))
 | |
| 
 | |
|     # DKIM
 | |
|     api_record.append({
 | |
|         'fieldType': 'DKIM',
 | |
|         'ttl': 1300,
 | |
|         'target': valid_dkim_key,
 | |
|         'subDomain': 'dkim',
 | |
|         'id': 16
 | |
|     })
 | |
|     expected.add(Record.new(zone, 'dkim', {
 | |
|         'ttl': 1300,
 | |
|         'type': 'TXT',
 | |
|         'value': valid_dkim_key,
 | |
|     }))
 | |
| 
 | |
|     # TXT
 | |
|     api_record.append({
 | |
|         'fieldType': 'TXT',
 | |
|         'ttl': 1400,
 | |
|         'target': 'TXT text',
 | |
|         'subDomain': 'txt',
 | |
|         'id': 17
 | |
|     })
 | |
|     expected.add(Record.new(zone, 'txt', {
 | |
|         'ttl': 1400,
 | |
|         'type': 'TXT',
 | |
|         'value': 'TXT text',
 | |
|     }))
 | |
| 
 | |
|     # LOC
 | |
|     # We do not have associated record for LOC, as it's not managed
 | |
|     api_record.append({
 | |
|         'fieldType': 'LOC',
 | |
|         'ttl': 1500,
 | |
|         'target': '1 1 1 N 1 1 1 E 1m 1m',
 | |
|         'subDomain': '',
 | |
|         'id': 18
 | |
|     })
 | |
| 
 | |
|     # CAA
 | |
|     api_record.append({
 | |
|         'fieldType': 'CAA',
 | |
|         'ttl': 1600,
 | |
|         'target': '0 issue "ca.unit.tests"',
 | |
|         'subDomain': 'caa',
 | |
|         'id': 19
 | |
|     })
 | |
|     expected.add(Record.new(zone, 'caa', {
 | |
|         'ttl': 1600,
 | |
|         'type': 'CAA',
 | |
|         'values': [{
 | |
|             'flags': 0,
 | |
|             'tag': 'issue',
 | |
|             'value': 'ca.unit.tests'
 | |
|         }]
 | |
|     }))
 | |
| 
 | |
|     valid_dkim = [valid_dkim_key,
 | |
|                   'v=DKIM1 \\; %s' % valid_dkim_key,
 | |
|                   'h=sha256 \\; %s' % valid_dkim_key,
 | |
|                   'h=sha1 \\; %s' % valid_dkim_key,
 | |
|                   's=* \\; %s' % valid_dkim_key,
 | |
|                   's=email \\; %s' % valid_dkim_key,
 | |
|                   't=y \\; %s' % valid_dkim_key,
 | |
|                   't=s \\; %s' % valid_dkim_key,
 | |
|                   'k=rsa \\; %s' % valid_dkim_key,
 | |
|                   'n=notes \\; %s' % valid_dkim_key,
 | |
|                   'g=granularity \\; %s' % valid_dkim_key,
 | |
|                   ]
 | |
|     invalid_dkim = ['p=%invalid%',  # Invalid public key
 | |
|                     'v=DKIM1',  # Missing public key
 | |
|                     'v=DKIM2 \\; %s' % valid_dkim_key,  # Invalid version
 | |
|                     'h=sha512 \\; %s' % valid_dkim_key,  # Invalid hash algo
 | |
|                     's=fake \\; %s' % valid_dkim_key,  # Invalid selector
 | |
|                     't=fake \\; %s' % valid_dkim_key,  # Invalid flag
 | |
|                     'u=invalid \\; %s' % valid_dkim_key,  # Invalid key
 | |
|                     ]
 | |
| 
 | |
|     @patch('ovh.Client')
 | |
|     def test_populate(self, client_mock):
 | |
|         provider = OvhProvider('test', 'endpoint', 'application_key',
 | |
|                                'application_secret', 'consumer_key')
 | |
| 
 | |
|         with patch.object(provider._client, 'get') as get_mock:
 | |
|             zone = Zone('unit.tests.', [])
 | |
|             get_mock.side_effect = ResourceNotFoundError('boom')
 | |
|             with self.assertRaises(APIError) as ctx:
 | |
|                 provider.populate(zone)
 | |
|             self.assertEquals(get_mock.side_effect, ctx.exception)
 | |
| 
 | |
|             get_mock.side_effect = InvalidCredential('boom')
 | |
|             with self.assertRaises(APIError) as ctx:
 | |
|                 provider.populate(zone)
 | |
|             self.assertEquals(get_mock.side_effect, ctx.exception)
 | |
| 
 | |
|             zone = Zone('unit.tests.', [])
 | |
|             get_mock.side_effect = ResourceNotFoundError('This service does '
 | |
|                                                          'not exist')
 | |
|             exists = provider.populate(zone)
 | |
|             self.assertEquals(set(), zone.records)
 | |
|             self.assertFalse(exists)
 | |
| 
 | |
|             zone = Zone('unit.tests.', [])
 | |
|             get_returns = [[record['id'] for record in self.api_record]]
 | |
|             get_returns += self.api_record
 | |
|             get_mock.side_effect = get_returns
 | |
|             exists = provider.populate(zone)
 | |
|             self.assertEquals(self.expected, zone.records)
 | |
|             self.assertTrue(exists)
 | |
| 
 | |
|     @patch('ovh.Client')
 | |
|     def test_is_valid_dkim(self, client_mock):
 | |
|         """Test _is_valid_dkim"""
 | |
|         provider = OvhProvider('test', 'endpoint', 'application_key',
 | |
|                                'application_secret', 'consumer_key')
 | |
|         for dkim in self.valid_dkim:
 | |
|             self.assertTrue(provider._is_valid_dkim(dkim))
 | |
|         for dkim in self.invalid_dkim:
 | |
|             self.assertFalse(provider._is_valid_dkim(dkim))
 | |
| 
 | |
|     @patch('ovh.Client')
 | |
|     def test_apply(self, client_mock):
 | |
|         provider = OvhProvider('test', 'endpoint', 'application_key',
 | |
|                                'application_secret', 'consumer_key')
 | |
| 
 | |
|         desired = Zone('unit.tests.', [])
 | |
| 
 | |
|         for r in self.expected:
 | |
|             desired.add_record(r)
 | |
| 
 | |
|         with patch.object(provider._client, 'post') as get_mock:
 | |
|             plan = provider.plan(desired)
 | |
|             get_mock.side_effect = APIError('boom')
 | |
|             with self.assertRaises(APIError) as ctx:
 | |
|                 provider.apply(plan)
 | |
|             self.assertEquals(get_mock.side_effect, ctx.exception)
 | |
| 
 | |
|         # Records get by API call
 | |
|         with patch.object(provider._client, 'get') as get_mock:
 | |
|             get_returns = [
 | |
|                 [1, 2, 3, 4],
 | |
|                 {'fieldType': 'A', 'ttl': 600, 'target': '5.6.7.8',
 | |
|                  'subDomain': '', 'id': 100},
 | |
|                 {'fieldType': 'A', 'ttl': 600, 'target': '5.6.7.8',
 | |
|                  'subDomain': 'fake', 'id': 101},
 | |
|                 {'fieldType': 'TXT', 'ttl': 600, 'target': 'fake txt record',
 | |
|                  'subDomain': 'txt', 'id': 102},
 | |
|                 {'fieldType': 'DKIM', 'ttl': 600,
 | |
|                  'target': 'v=DKIM1; %s' % self.valid_dkim_key,
 | |
|                  'subDomain': 'dkim', 'id': 103}
 | |
|             ]
 | |
|             get_mock.side_effect = get_returns
 | |
| 
 | |
|             plan = provider.plan(desired)
 | |
| 
 | |
|             with patch.object(provider._client, 'post') as post_mock, \
 | |
|                     patch.object(provider._client, 'delete') as delete_mock:
 | |
|                 get_mock.side_effect = [[100], [101], [102], [103]]
 | |
|                 provider.apply(plan)
 | |
|                 wanted_calls = [
 | |
|                     call('/domain/zone/unit.tests/record', fieldType='A',
 | |
|                          subDomain='', target='1.2.3.4', ttl=100),
 | |
|                     call('/domain/zone/unit.tests/record', fieldType='AAAA',
 | |
|                          subDomain='', target='1:1ec:1::1', ttl=200),
 | |
|                     call('/domain/zone/unit.tests/record', fieldType='MX',
 | |
|                          subDomain='', target='10 mx1.unit.tests.', ttl=400),
 | |
|                     call('/domain/zone/unit.tests/record', fieldType='SPF',
 | |
|                          subDomain='',
 | |
|                          target='v=spf1 include:unit.texts.redirect ~all',
 | |
|                          ttl=1000),
 | |
|                     call('/domain/zone/unit.tests/record', fieldType='SSHFP',
 | |
|                          subDomain='',
 | |
|                          target='1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73',
 | |
|                          ttl=1100),
 | |
|                     call('/domain/zone/unit.tests/record', fieldType='PTR',
 | |
|                          subDomain='4', target='unit.tests.', ttl=900),
 | |
|                     call('/domain/zone/unit.tests/record', fieldType='SRV',
 | |
|                          subDomain='_srv._tcp',
 | |
|                          target='10 20 30 foo-1.unit.tests.', ttl=800),
 | |
|                     call('/domain/zone/unit.tests/record', fieldType='SRV',
 | |
|                          subDomain='_srv._tcp',
 | |
|                          target='40 50 60 foo-2.unit.tests.', ttl=800),
 | |
|                     call('/domain/zone/unit.tests/record', fieldType='CAA',
 | |
|                          subDomain='caa', target='0 issue "ca.unit.tests"',
 | |
|                          ttl=1600),
 | |
|                     call('/domain/zone/unit.tests/record', fieldType='DKIM',
 | |
|                          subDomain='dkim',
 | |
|                          target='p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxLaG'
 | |
|                          '16G4SaEcXVdiIxTg7gKSGbHKQLm30CHib1h9FzS9nkcyvQSyQj1r'
 | |
|                          'MFyqC//tft3ohx3nvJl+bGCWxdtLYDSmir9PW54e5CTdxEh8MWRk'
 | |
|                          'BO3StF6QG/tAh3aTGDmkqhIJGLb87iHvpmVKqURmEUzJPv5KPJfW'
 | |
|                          'LofADI+q9lQIDAQAB', ttl=1300),
 | |
|                     call('/domain/zone/unit.tests/record', fieldType='NAPTR',
 | |
|                          subDomain='naptr',
 | |
|                          target='10 100 "S" "SIP+D2U" "!^.*$!sip:info@bar.exam'
 | |
|                          'ple.com!" .', ttl=500),
 | |
|                     call('/domain/zone/unit.tests/record', fieldType='A',
 | |
|                          subDomain='sub', target='1.2.3.4', ttl=200),
 | |
|                     call('/domain/zone/unit.tests/record', fieldType='TXT',
 | |
|                          subDomain='txt', target='TXT text', ttl=1400),
 | |
|                     call('/domain/zone/unit.tests/record', fieldType='CNAME',
 | |
|                          subDomain='www2', target='unit.tests.', ttl=300),
 | |
|                     call('/domain/zone/unit.tests/record', fieldType='NS',
 | |
|                          subDomain='www3', target='ns3.unit.tests.', ttl=700),
 | |
|                     call('/domain/zone/unit.tests/record', fieldType='NS',
 | |
|                          subDomain='www3', target='ns4.unit.tests.', ttl=700),
 | |
|                     call('/domain/zone/unit.tests/refresh')]
 | |
| 
 | |
|                 post_mock.assert_has_calls(wanted_calls)
 | |
| 
 | |
|                 # Get for delete calls
 | |
|                 wanted_get_calls = [
 | |
|                     call(u'/domain/zone/unit.tests/record', fieldType=u'A',
 | |
|                          subDomain=u''),
 | |
|                     call(u'/domain/zone/unit.tests/record', fieldType=u'DKIM',
 | |
|                          subDomain='dkim'),
 | |
|                     call(u'/domain/zone/unit.tests/record', fieldType=u'A',
 | |
|                          subDomain='fake'),
 | |
|                     call(u'/domain/zone/unit.tests/record', fieldType=u'TXT',
 | |
|                          subDomain='txt')]
 | |
|                 get_mock.assert_has_calls(wanted_get_calls)
 | |
|                 # 4 delete calls for update and delete
 | |
|                 delete_mock.assert_has_calls(
 | |
|                     [call(u'/domain/zone/unit.tests/record/100'),
 | |
|                      call(u'/domain/zone/unit.tests/record/101'),
 | |
|                      call(u'/domain/zone/unit.tests/record/102'),
 | |
|                      call(u'/domain/zone/unit.tests/record/103')])
 |