def process_value_to_user(self, record_type, value): """ Process a record value (string) for sending to the user. """ record = DNSRecord() record.type = record_type record.target = value self.process_to_user(record) return record.target
def process_value_from_user(self, record_type, value): """ Process a record value (string) after receiving from the user. """ record = DNSRecord() record.type = record_type record.target = value self.process_from_user(record) return record.target
def test_unknown_records(): data = { "id": 17, "type": "unknown", "name": "", "ttl": 3600, "comment": "my first record", } with pytest.raises(DNSAPIError) as exc: _create_record_from_json(data) assert exc.value.args[0] == 'Cannot parse unknown record type: unknown' record = DNSRecord() record.type = 'unknown' with pytest.raises(DNSAPIError) as exc: _record_to_json(record) assert exc.value.args[0] == 'Cannot serialize unknown record type: unknown'
def _create_record_from_encoding(source, type=None): source = dict(source) result = DNSRecord() result.id = source.pop('id') result.type = source.pop('type', type) result.prefix = source.pop('prefix', None) ttl = source.pop('ttl') result.ttl = int(ttl) if ttl is not None else None priority = source.pop('priority') target = source.pop('target') if result.type in ('PTR', 'MX'): result.target = '{0} {1}'.format(priority, target) else: result.target = target source.pop('zone', None) result.extra['comment'] = source.pop('comment') or '' result.extra.update(source) return result
def _create_record_from_json(source, type=None, has_id=True): source = dict(source) result = DNSRecord() if has_id: result.id = source.pop('id') result.type = source.pop('type', type) result.ttl = source.pop('ttl', None) name = source.pop('name', None) if name == '@': name = None result.prefix = name result.target = source.pop('value') source.pop('zone_id', None) result.extra.update(source) return result
def test_update_id_delete(): api = HetznerAPI(MagicMock(), '123') with pytest.raises(DNSAPIError) as exc: api.delete_record(1, DNSRecord()) assert exc.value.args[0] == 'Need record ID to delete record!'
def run_module(module, create_api, provider_information): option_provider = ModuleOptionProvider(module) record_converter = RecordConverter(provider_information, option_provider) record_in = normalize_dns_name(module.params.get('record')) prefix_in = module.params.get('prefix') type_in = module.params.get('type') try: # Create API api = create_api() # Get zone information if module.params.get('zone_name') is not None: zone_in = normalize_dns_name(module.params.get('zone_name')) record_in, prefix = get_prefix( normalized_zone=zone_in, normalized_record=record_in, prefix=prefix_in, provider_information=provider_information) zone = api.get_zone_with_records_by_name(zone_in, prefix=prefix, record_type=type_in) if zone is None: module.fail_json(msg='Zone not found') zone_id = zone.zone.id records = zone.records elif record_in is not None: zone = api.get_zone_with_records_by_id( module.params.get('zone_id'), record_type=type_in, prefix=provider_information.normalize_prefix(prefix_in) if prefix_in is not None else NOT_PROVIDED, ) if zone is None: module.fail_json(msg='Zone not found') zone_in = normalize_dns_name(zone.zone.name) record_in, prefix = get_prefix( normalized_zone=zone_in, normalized_record=record_in, prefix=prefix_in, provider_information=provider_information) zone_id = zone.zone.id records = zone.records else: zone_id = module.params.get('zone_id') prefix = provider_information.normalize_prefix(prefix_in) records = api.get_zone_records( zone_id, record_type=type_in, prefix=prefix, ) if records is None: module.fail_json(msg='Zone not found') zone_in = None record_in = None # Find matching records records = filter_records(records, prefix=prefix) record_converter.process_multiple_from_api(records) # Parse records value_in = module.params.get('value') value_in = record_converter.process_value_from_user(type_in, value_in) # Compare records existing_record = None exact_match = False ttl_in = module.params.get('ttl') for record in records: if record.target == value_in: existing_record = record exact_match = record.ttl == ttl_in break before = existing_record.clone() if existing_record else None after = before changed = False if module.params.get('state') == 'present': if existing_record is None: # Create record record = DNSRecord() record.prefix = prefix record.type = type_in record.ttl = ttl_in record.target = value_in api_record = record_converter.clone_to_api(record) if not module.check_mode: new_api_record = api.add_record(zone_id, api_record) record = record_converter.clone_from_api(new_api_record) after = record changed = True elif not exact_match: # Update record record = existing_record record.ttl = ttl_in api_record = record_converter.clone_to_api(record) if not module.check_mode: new_api_record = api.update_record(zone_id, api_record) record = record_converter.clone_from_api(new_api_record) after = record changed = True else: if existing_record is not None: # Delete record api_record = record_converter.clone_to_api(record) if not module.check_mode: api.delete_record(zone_id, api_record) after = None changed = True # Compose result result = dict( changed=changed, zone_id=zone_id, ) if module._diff: result['diff'] = dict( before=format_record_for_output( before, record_in, prefix, record_converter=record_converter) if before else {}, after=format_record_for_output( after, record_in, prefix, record_converter=record_converter) if after else {}, ) module.exit_json(**result) except DNSConversionError as e: module.fail_json(msg='Error while converting DNS values: {0}'.format( e.error_message), error=e.error_message, exception=traceback.format_exc()) except DNSAPIAuthenticationError as e: module.fail_json(msg='Cannot authenticate: {0}'.format(e), error=to_text(e), exception=traceback.format_exc()) except DNSAPIError as e: module.fail_json(msg='Error: {0}'.format(e), error=to_text(e), exception=traceback.format_exc())
def test_format_records_for_output(): A1 = DNSRecord() A1.type = 'A' A1.ttl = 300 A1.target = '1.2.3.4' A1.extra['foo'] = 'bar' A2 = DNSRecord() A2.type = 'A' A2.ttl = 300 A2.target = '1.2.3.5' A3 = DNSRecord() A3.type = 'A' A3.ttl = 3600 A3.target = '1.2.3.6' AAAA = DNSRecord() AAAA.type = 'AAAA' AAAA.ttl = 600 AAAA.target = '::1' AAAA2 = DNSRecord() AAAA2.type = 'AAAA' AAAA2.ttl = None AAAA2.target = '::2' assert format_records_for_output([], 'foo', '') == { 'record': 'foo', 'prefix': '', 'type': None, 'ttl': None, 'value': [], } assert format_records_for_output([A1, A2], 'foo', 'bar') == { 'record': 'foo', 'prefix': 'bar', 'type': 'A', 'ttl': 300, 'value': ['1.2.3.4', '1.2.3.5'], } assert format_records_for_output([A3, A1], 'foo', None) == { 'record': 'foo', 'prefix': '', 'type': 'A', 'ttl': 300, 'ttls': [300, 3600], 'value': ['1.2.3.6', '1.2.3.4'], } assert format_records_for_output([A3], 'foo', None) == { 'record': 'foo', 'prefix': '', 'type': 'A', 'ttl': 3600, 'value': ['1.2.3.6'], } assert format_records_for_output([AAAA], 'foo', None) == { 'record': 'foo', 'prefix': '', 'type': 'AAAA', 'ttl': 600, 'value': ['::1'], } assert format_records_for_output([A3, AAAA], 'foo', None) == { 'record': 'foo', 'prefix': '', 'type': 'A', 'ttl': 600, 'ttls': [600, 3600], 'value': ['1.2.3.6', '::1'], } assert format_records_for_output([AAAA, A3], 'foo', None) == { 'record': 'foo', 'prefix': '', 'type': 'A', 'ttl': 600, 'ttls': [600, 3600], 'value': ['::1', '1.2.3.6'], } assert format_records_for_output([AAAA2], 'foo', None) == { 'record': 'foo', 'prefix': '', 'type': 'AAAA', 'ttl': None, 'value': ['::2'], } print(format_records_for_output([AAAA2, AAAA], 'foo', None)) assert format_records_for_output([AAAA2, AAAA], 'foo', None) == { 'record': 'foo', 'prefix': '', 'type': 'AAAA', 'ttl': None, 'ttls': [None, 600], 'value': ['::2', '::1'], }
def test_record_str_repr(): A1 = DNSRecord() A1.prefix = None A1.type = 'A' A1.ttl = 300 A1.target = '1.2.3.4' assert str(A1) == 'DNSRecord(type: A, prefix: (none), target: "1.2.3.4", ttl: 5m)' assert repr(A1) == 'DNSRecord(type: A, prefix: (none), target: "1.2.3.4", ttl: 5m)' A2 = DNSRecord() A2.id = 23 A2.prefix = 'bar' A2.type = 'A' A2.ttl = 1 A2.target = '' A2.extra['foo'] = 'bar' assert str(A2) == 'DNSRecord(id: 23, type: A, prefix: "bar", target: "", ttl: 1s, extra: {\'foo\': \'bar\'})' assert repr(A2) == 'DNSRecord(id: 23, type: A, prefix: "bar", target: "", ttl: 1s, extra: {\'foo\': \'bar\'})'
def run_module(module, create_api, provider_information): option_provider = ModuleOptionProvider(module) record_converter = RecordConverter(provider_information, option_provider) record_in = normalize_dns_name(module.params.get('record')) prefix_in = module.params.get('prefix') type_in = module.params.get('type') try: # Create API api = create_api() # Get zone information if module.params.get('zone_name') is not None: zone_in = normalize_dns_name(module.params.get('zone_name')) record_in, prefix = get_prefix( normalized_zone=zone_in, normalized_record=record_in, prefix=prefix_in, provider_information=provider_information) zone = api.get_zone_with_records_by_name(zone_in, prefix=prefix, record_type=type_in) if zone is None: module.fail_json(msg='Zone not found') zone_id = zone.zone.id records = zone.records elif record_in is not None: zone = api.get_zone_with_records_by_id( module.params.get('zone_id'), record_type=type_in, prefix=provider_information.normalize_prefix(prefix_in) if prefix_in is not None else NOT_PROVIDED, ) if zone is None: module.fail_json(msg='Zone not found') zone_in = normalize_dns_name(zone.zone.name) record_in, prefix = get_prefix( normalized_zone=zone_in, normalized_record=record_in, prefix=prefix_in, provider_information=provider_information) zone_id = zone.zone.id records = zone.records else: zone_id = module.params.get('zone_id') prefix = provider_information.normalize_prefix(prefix_in) records = api.get_zone_records( zone_id, record_type=type_in, prefix=prefix, ) if records is None: module.fail_json(msg='Zone not found') zone_in = None record_in = None # Find matching records records = filter_records(records, prefix=prefix) record_converter.process_multiple_from_api(records) # Parse records values = [] value_in = module.params.get('value') or [] value_in = record_converter.process_values_from_user(type_in, value_in) values = value_in[:] # Compare records ttl_in = module.params.get('ttl') mismatch = False mismatch_records = [] keep_records = [] for record in records: if record.ttl != ttl_in: mismatch = True mismatch_records.append(record) continue val = record.target if val in values: values.remove(val) keep_records.append(record) else: mismatch = True mismatch_records.append(record) continue if values: mismatch = True before = [record.clone() for record in records] after = keep_records[:] # Determine what to do to_create = [] to_delete = [] to_change = [] on_existing = module.params.get('on_existing') no_mod = False if module.params.get('state') == 'present': if records and mismatch: # Mismatch: user wants to overwrite? if on_existing == 'replace': to_delete.extend(mismatch_records) elif on_existing == 'keep_and_fail': module.fail_json( msg= "Record already exists with different value. Set on_existing=replace to replace it" ) elif on_existing == 'keep_and_warn': module.warn( "Record already exists with different value. Set on_existing=replace to replace it" ) no_mod = True else: # on_existing == 'keep' no_mod = True if no_mod: after = before[:] else: for target in values: if to_delete: # If there's a record to delete, change it to new record record = to_delete.pop() to_change.append(record) else: # Otherwise create new record record = DNSRecord() to_create.append(record) record.prefix = prefix record.type = type_in record.ttl = ttl_in record.target = target after.append(record) if module.params.get('state') == 'absent': if mismatch: # Mismatch: user wants to overwrite? if on_existing == 'replace': no_mod = False elif on_existing == 'keep_and_fail': module.fail_json( msg= "Record already exists with different value. Set on_existing=replace to remove it" ) elif on_existing == 'keep_and_warn': module.warn( "Record already exists with different value. Set on_existing=replace to remove it" ) no_mod = True else: # on_existing == 'keep' no_mod = True if no_mod: after = before[:] else: to_delete.extend(records) after = [] # Compose result result = dict( changed=False, zone_id=zone_id, ) # Determine whether there's something to do if to_create or to_delete or to_change: # Actually do something records_to_delete = record_converter.clone_multiple_to_api( to_delete) records_to_change = record_converter.clone_multiple_to_api( to_change) records_to_create = record_converter.clone_multiple_to_api( to_create) result['changed'] = True if not module.check_mode: dummy, errors, success = bulk_apply_changes( api, zone_id=zone_id, records_to_delete=records_to_delete, records_to_change=records_to_change, records_to_create=records_to_create, provider_information=provider_information, options=option_provider, ) if errors: if len(errors) == 1: raise errors[0] module.fail_json( msg='Errors: {0}'.format('; '.join( [str(e) for e in errors])), errors=[str(e) for e in errors], ) # Include diff information if module._diff: result['diff'] = dict( before=(format_records_for_output( sorted(before, key=lambda record: record.target), record_in, prefix, record_converter=record_converter) if before else dict()), after=(format_records_for_output( sorted(after, key=lambda record: record.target), record_in, prefix, record_converter=record_converter) if after else dict()), ) module.exit_json(**result) except DNSConversionError as e: module.fail_json(msg='Error while converting DNS values: {0}'.format( e.error_message), error=e.error_message, exception=traceback.format_exc()) except DNSAPIAuthenticationError as e: module.fail_json(msg='Cannot authenticate: {0}'.format(e), error=to_text(e), exception=traceback.format_exc()) except DNSAPIError as e: module.fail_json(msg='Error: {0}'.format(e), error=to_text(e), exception=traceback.format_exc())
def test_update_id_missing(): api = HostTechJSONAPI(MagicMock(), '123') with pytest.raises(DNSAPIError) as exc: api.update_record(1, DNSRecord()) assert exc.value.args[0] == 'Need record ID to update record!'
def test_zone_with_records_str_repr(): Z1 = DNSZone('foo') Z2 = DNSZone('foo') Z2.id = 42 A1 = DNSRecord() A1.prefix = None A1.type = 'A' A1.ttl = 300 A1.target = '1.2.3.4' A2 = DNSRecord() A2.id = 23 A2.prefix = 'bar' A2.type = 'A' A2.ttl = 1 A2.target = '' A2.extra['foo'] = 23 ZZ1 = DNSZoneWithRecords(Z1, [A1]) ZZ2 = DNSZoneWithRecords(Z2, [A1, A2]) assert str( ZZ1 ) == '(DNSZone(name: foo, info: {}), [DNSRecord(type: A, prefix: (none), target: "1.2.3.4", ttl: 5m)])' assert repr( ZZ1 ) == 'DNSZoneWithRecords(DNSZone(name: foo, info: {}), [DNSRecord(type: A, prefix: (none), target: "1.2.3.4", ttl: 5m)])' assert str(ZZ2) == ( '(DNSZone(id: 42, name: foo, info: {}), [DNSRecord(type: A, prefix: (none), target: "1.2.3.4", ttl: 5m),' ' DNSRecord(id: 23, type: A, prefix: "bar", target: "", ttl: 1s, extra: {\'foo\': 23})])' ) assert repr(ZZ2) == ( 'DNSZoneWithRecords(DNSZone(id: 42, name: foo, info: {}), [DNSRecord(type: A, prefix: (none), target: "1.2.3.4", ttl: 5m),' ' DNSRecord(id: 23, type: A, prefix: "bar", target: "", ttl: 1s, extra: {\'foo\': 23})])' )