def test_record_eq_record_different_values(self): """Record with different values is not equal""" zone = Zone('test.example.com') data = {'type': 'A', 'ttl': 30, 'values': ['1.1.1.1', '2.2.2.2']} record_current = Record(zone, 'test-record', data) data = {'type': 'A', 'ttl': 30, 'values': ['1.1.1.1', '3.3.3.3']} record_desired = Record(zone, 'test-record', data) self.assertTrue(record_current != record_desired)
def test_record_eq_record_different_values_order(self): """Record with same values in different order is still equal""" zone = Zone('test.example.com') data = {'type': 'A', 'ttl': 30, 'values': ['1.1.1.1', '2.2.2.2']} record_current = Record(zone, 'test-record', data) data = {'type': 'A', 'ttl': 30, 'values': ['2.2.2.2', '1.1.1.1']} record_desired = Record(zone, 'test-record', data) self.assertTrue(record_current == record_desired)
def test_zone_cant_have_duplicate_records(self): """Zone cannot add multiple records with same name""" zone = Zone('test.example.com') recordA = Record(zone, 'test-record', {'type': 'A', 'ttl': 300}) recordB = Record(zone, 'test-record', {'type': 'A', 'ttl': 300}) zone.add_record(recordA) with self.assertRaises(DuplicateException): zone.add_record(recordB)
def test_add_multiple_records_to_zone(self): """Zone can add multiple records with different names""" zone = Zone('test.example.com') recordA = Record(zone, 'test-recorda', {'type': 'A', 'ttl': 300}) recordB = Record(zone, 'test-recordb', {'type': 'A', 'ttl': 300}) zone.add_record(recordA) zone.add_record(recordB) self.assertDictEqual(zone.records, { 'test-recorda': recordA, 'test-recordb': recordB, })
def test_record_without_ttl_should_fail(self): """Record data without a ttl should fail""" zone = Zone('test.example.com') with self.assertRaises(InvalidRecordData) as e: Record(zone, 'test-record', {'type': 'A'}) self.assertEqual('missing key \'ttl\' in Record data', str(e.exception))
def build_current_state(awsapi): """ Build a State object that represents the current state :param awsapi: the aws API object to use :type awsapi: AWSApi :return: returns a tuple that contains the State object and whether there \ were any errors :rtype: (State, bool) """ state = State('aws') errors = False awsapi.map_route53_resources() aws_state = awsapi.get_route53_zones() for account_name, zones in aws_state.items(): account = Account(account_name) for zone in zones: zone_name = zone['Name'] new_zone = Zone(zone_name, zone) for record in zone['records']: # Can't manage SOA records, so ignore it if record['Type'] in ['SOA']: continue # Can't manage NS records at apex, so ignore them if record['Type'] == 'NS' and record['Name'] == zone_name: continue record_name = removesuffix(record['Name'], zone_name) new_record = Record( new_zone, record_name, { 'type': record['Type'], 'ttl': record['TTL'], 'values': [v['Value'] for v in record['ResourceRecords']], }, record) new_zone.add_record(new_record) account.add_zone(new_zone) state.add_account(account) return state, errors
def test_repr_apex(self): """Record at the apex (empty name) is represented properly""" zone = Zone('test.example.com') record = Record(zone, '', {'type': 'A', 'ttl': 300}) self.assertEqual(f'{record}', 'Record<A>')
def test_repr(self): """Record is represented properly""" zone = Zone('test.example.com') record = Record(zone, "test-record", {'type': 'A', 'ttl': 300}) self.assertEqual(f'{record}', 'Record<A, test-record>')
def test_record_eq_record_different_ttl(self): """Record with a different TTL is not equal""" zone = Zone('test.example.com') record_current = Record(zone, 'test-record', {'type': 'A', 'ttl': 30}) record_desired = Record(zone, 'test-record', {'type': 'A', 'ttl': 300}) self.assertTrue(record_current != record_desired)
def test_record_eq_record(self): """Record with the same type, ttl and values are equal""" zone = Zone('test.example.com') record_current = Record(zone, 'test-record', {'type': 'A', 'ttl': 300}) record_desired = Record(zone, 'test-record', {'type': 'A', 'ttl': 300}) self.assertTrue(record_current == record_desired)
def test_record_without_values(self): """Record can have no values""" zone = Zone('test.example.com') record = Record(zone, 'test-record', {'type': 'A', 'ttl': 300}) self.assertListEqual(record.values, [])
def test_record_with_invalid_type_should_fail(self): """Record can only have a supported type""" zone = Zone('test.example.com') with self.assertRaises(InvalidRecordType) as e: Record(zone, 'test-record', {'type': 'FOO', 'ttl': 300}) self.assertEqual('Type FOO is not supported', str(e.exception))
def test_record_fqdn(self): """Record can return it's fqdn""" zone = Zone('test.example.com') record = Record(zone, 'test-record', {'type': 'A', 'ttl': 300}) self.assertEqual(record.fqdn, 'test-record.test.example.com')
def test_record_name(self): """Record can return it's name""" zone = Zone('test.example.com') record = Record(zone, 'test-record', {'type': 'A', 'ttl': 300}) self.assertEqual(record.name, 'test-record')
def test_record_returns_values(self): """Record can return it's values""" zone = Zone('test.example.com') record = Record(zone, 'test-record', {'type': 'A', 'ttl': 300}) record.add_targets(['1.1.1.1', '2.2.2.2', '3.3.3.3']) self.assertListEqual(record.values, ['1.1.1.1', '2.2.2.2', '3.3.3.3'])
def build_desired_state(zones): """ Build a State object that represents the desired state :param zones: a representation of DNS zones as retrieved from app-interface :type zones: dict :return: returns a tuple that contains the State object and whether there \ were any errors :rtype: (State, bool) """ state = State('app-interface') errors = False for zone in zones: account_name = zone['account']['name'] account = state.get_account(account_name) if not account: account = Account(account_name) new_zone = Zone(zone['name'], zone) for record in zone['records']: new_record = Record(new_zone, record['name'], { 'type': record['type'], 'ttl': record['ttl'] or DEFAULT_RECORD_TTL }, record) targets = [] record_target = record.get('target') if record_target: if record['type'] == 'TXT': # TXT records values need to be enclosed in double quotes targets.append(f'"{record_target}"') else: targets.append(record_target) record_targets = record.get('targets') if record_targets: targets.extend(record_targets) record_target_cluster = record.get('target_cluster') if record_target_cluster: cluster = record_target_cluster cluster_name = cluster['name'] elb_fqdn = cluster.get('elbFQDN') if not elb_fqdn: logging.error(f'[{account}] elbFQDN not set for cluster ' f'{cluster_name}') errors = True continue targets.append(elb_fqdn) if not targets: logging.error(f'[{account}] no targets found for ' f'{new_record} in {new_zone}') errors = True continue new_record.add_targets(targets) new_zone.add_record(new_record) try: account.add_zone(new_zone) except DuplicateException as e: logging.error(e) errors = True if not state.get_account(account_name): state.add_account(account) return state, errors
def test_add_record_to_zone(self): """Zone can add a record to zone and return it""" zone = Zone('test.example.com') record = Record(zone, 'test-record', {'type': 'A', 'ttl': 300}) zone.add_record(record) self.assertEqual(zone.records.get('test-record'), record)