def get_records(self): if self.name != 'test.': return [] r = Record() r.name = '_etcd-server._tcp.cluster.' + self.name r.type = 'SRV' return [r]
def group_values(lines): records = [] for _, records in itertools.groupby(lines, lambda row: row[0:2]): for __, by_value in itertools.groupby(records, lambda row: row[-3:]): recs = list( by_value) # consume the iterator so we can grab positionally first = recs[0] record = Record() record.name = first[0] record.type = first[1] if first[2].startswith('ALIAS'): _, alias_hosted_zone_id, alias_dns_name = first[2].split(':') record.alias_hosted_zone_id = alias_hosted_zone_id record.alias_dns_name = alias_dns_name else: record.resource_records = [r[2] for r in recs] record.ttl = first[3] record.region = first[4] or None record.weight = first[5] or None record.identifier = first[6] or None record.failover = first[7] or None if first[8] == 'True': record.alias_evaluate_target_health = True elif first[8] == 'False': record.alias_evaluate_target_health = False else: record.alias_evaluate_target_health = None yield record
def _create_mock_zone_and_records(route53): route53.create_hosted_zone(TEST_DOMAIN) route53.create_hosted_zone(TEST_DOMAIN2) example_com = route53.get_zone(TEST_DOMAIN) test_com = route53.get_zone(TEST_DOMAIN2) record_sets = route53.get_all_rrsets(example_com.id) record = Record(TEST_RECORD_NAME, TEST_RECORD_TYPE) record.add_value(TEST_RECORD_VALUE) record_sets.add_change_record('CREATE', record) record_sets.commit() record_sets = route53.get_all_rrsets(test_com.id) record = Record(TEST_RECORD_NAME2, TEST_RECORD_TYPE) record.add_value(TEST_RECORD_VALUE) record_sets.add_change_record('CREATE', record) record_sets.commit()
def create_record(self, hosted_zone_name, record_name, record_type, value): """ Create a DNS record. Update an existing record if one already exists. Args: hosted_zone_name (str): the domain of the Hosted Zone record_name (str): the name of the record record_type (str): the type of record (A, AAAA, CNAME, etc) value (str): a single value to insert into the record """ zone = self.route53.get_zone(hosted_zone_name) record = Record(record_name, record_type, ttl=60) record.add_value(value) logging.info("Setting Record %s of type %s to %s", record_name, record_type, value) changes = self.route53.get_all_rrsets(zone.id) changes.add_change_record('UPSERT', record) changes.commit()
def main(): argument_spec = ec2_argument_spec() argument_spec.update(dict( command = dict(choices=['get', 'create', 'delete'], required=True), zone = dict(required=True), hosted_zone_id = dict(required=False, default=None), record = dict(required=True), ttl = dict(required=False, type='int', default=3600), type = dict(choices=['A', 'CNAME', 'MX', 'AAAA', 'TXT', 'PTR', 'SRV', 'SPF', 'NS', 'SOA'], required=True), alias = dict(required=False, type='bool'), alias_hosted_zone_id = dict(required=False), alias_evaluate_target_health = dict(required=False, type='bool', default=False), value = dict(required=False), overwrite = dict(required=False, type='bool'), retry_interval = dict(required=False, default=500), private_zone = dict(required=False, type='bool', default=False), identifier = dict(required=False, default=None), weight = dict(required=False, type='int'), region = dict(required=False), health_check = dict(required=False), failover = dict(required=False,choices=['PRIMARY','SECONDARY']), vpc_id = dict(required=False), wait = dict(required=False, type='bool', default=False), wait_timeout = dict(required=False, type='int', default=300), ) ) module = AnsibleModule(argument_spec=argument_spec) if not HAS_BOTO: module.fail_json(msg='boto required for this module') if distutils.version.StrictVersion(boto.__version__) < distutils.version.StrictVersion(MINIMUM_BOTO_VERSION): module.fail_json(msg='Found boto in version %s, but >= %s is required' % (boto.__version__, MINIMUM_BOTO_VERSION)) command_in = module.params.get('command') zone_in = module.params.get('zone').lower() hosted_zone_id_in = module.params.get('hosted_zone_id') ttl_in = module.params.get('ttl') record_in = module.params.get('record').lower() type_in = module.params.get('type') value_in = module.params.get('value') alias_in = module.params.get('alias') alias_hosted_zone_id_in = module.params.get('alias_hosted_zone_id') alias_evaluate_target_health_in = module.params.get('alias_evaluate_target_health') retry_interval_in = module.params.get('retry_interval') private_zone_in = module.params.get('private_zone') identifier_in = module.params.get('identifier') weight_in = module.params.get('weight') region_in = module.params.get('region') health_check_in = module.params.get('health_check') failover_in = module.params.get('failover') vpc_id_in = module.params.get('vpc_id') wait_in = module.params.get('wait') wait_timeout_in = module.params.get('wait_timeout') region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module) value_list = () if isinstance(value_in, str): if value_in: value_list = sorted([s.strip() for s in value_in.split(',')]) elif isinstance(value_in, list): value_list = sorted(value_in) if zone_in[-1:] != '.': zone_in += "." if record_in[-1:] != '.': record_in += "." if command_in == 'create' or command_in == 'delete': if not value_in: module.fail_json(msg = "parameter 'value' required for create/delete") elif alias_in: if len(value_list) != 1: module.fail_json(msg = "parameter 'value' must contain a single dns name for alias create/delete") elif not alias_hosted_zone_id_in: module.fail_json(msg = "parameter 'alias_hosted_zone_id' required for alias create/delete") elif ( weight_in is not None or region_in is not None or failover_in is not None ) and identifier_in is None: module.fail_json(msg= "If you specify failover, region or weight you must also specify identifier") if command_in == 'create': if ( weight_in is not None or region_in is not None or failover_in is not None ) and identifier_in is None: module.fail_json(msg= "If you specify failover, region or weight you must also specify identifier") elif ( weight_in is None and region_in is None and failover_in is None ) and identifier_in is not None: module.fail_json(msg= "You have specified identifier which makes sense only if you specify one of: weight, region or failover.") if vpc_id_in and not private_zone_in: module.fail_json(msg="parameter 'private_zone' must be true when specifying parameter" " 'vpc_id'") # connect to the route53 endpoint try: conn = Route53Connection(**aws_connect_kwargs) except boto.exception.BotoServerError as e: module.fail_json(msg = e.error_message) # Find the named zone ID zone = get_zone_by_name(conn, module, zone_in, private_zone_in, hosted_zone_id_in, vpc_id_in) # Verify that the requested zone is already defined in Route53 if zone is None: errmsg = "Zone %s does not exist in Route53" % zone_in module.fail_json(msg = errmsg) record = {} found_record = False wanted_rset = Record(name=record_in, type=type_in, ttl=ttl_in, identifier=identifier_in, weight=weight_in, region=region_in, health_check=health_check_in, failover=failover_in) for v in value_list: if alias_in: wanted_rset.set_alias(alias_hosted_zone_id_in, v, alias_evaluate_target_health_in) else: wanted_rset.add_value(v) sets = conn.get_all_rrsets(zone.id, name=record_in, type=type_in, identifier=identifier_in) for rset in sets: # Due to a bug in either AWS or Boto, "special" characters are returned as octals, preventing round # tripping of things like * and @. decoded_name = rset.name.replace(r'\052', '*') decoded_name = decoded_name.replace(r'\100', '@') #Need to save this changes in rset, because of comparing rset.to_xml() == wanted_rset.to_xml() in next block rset.name = decoded_name if identifier_in is not None: identifier_in = str(identifier_in) if rset.type == type_in and decoded_name.lower() == record_in.lower() and rset.identifier == identifier_in: found_record = True record['zone'] = zone_in record['type'] = rset.type record['record'] = decoded_name record['ttl'] = rset.ttl record['value'] = ','.join(sorted(rset.resource_records)) record['values'] = sorted(rset.resource_records) if hosted_zone_id_in: record['hosted_zone_id'] = hosted_zone_id_in record['identifier'] = rset.identifier record['weight'] = rset.weight record['region'] = rset.region record['failover'] = rset.failover record['health_check'] = rset.health_check if hosted_zone_id_in: record['hosted_zone_id'] = hosted_zone_id_in if rset.alias_dns_name: record['alias'] = True record['value'] = rset.alias_dns_name record['values'] = [rset.alias_dns_name] record['alias_hosted_zone_id'] = rset.alias_hosted_zone_id record['alias_evaluate_target_health'] = rset.alias_evaluate_target_health else: record['alias'] = False record['value'] = ','.join(sorted(rset.resource_records)) record['values'] = sorted(rset.resource_records) if command_in == 'create' and rset.to_xml() == wanted_rset.to_xml(): module.exit_json(changed=False) break if command_in == 'get': if type_in == 'NS': ns = record['values'] else: # Retrieve name servers associated to the zone. ns = conn.get_zone(zone_in).get_nameservers() module.exit_json(changed=False, set=record, nameservers=ns) if command_in == 'delete' and not found_record: module.exit_json(changed=False) changes = ResourceRecordSets(conn, zone.id) if command_in == 'create' or command_in == 'delete': if command_in == 'create' and found_record: if not module.params['overwrite']: module.fail_json(msg = "Record already exists with different value. Set 'overwrite' to replace it") command = 'UPSERT' else: command = command_in.upper() changes.add_change_record(command, wanted_rset) try: result = invoke_with_throttling_retries(commit, changes, retry_interval_in, wait_in, wait_timeout_in) except boto.route53.exception.DNSServerError as e: txt = e.body.split("<Message>")[1] txt = txt.split("</Message>")[0] if "but it already exists" in txt: module.exit_json(changed=False) else: module.fail_json(msg = txt) except TimeoutError: module.fail_json(msg='Timeout waiting for changes to replicate') module.exit_json(changed=True)
# Find the named zone ID zone = get_zone_by_name(conn, module, zone_in, private_zone_in) # Verify that the requested zone is already defined in Route53 if zone is None: errmsg = "Zone %s does not exist in Route53" % zone_in module.fail_json(msg=errmsg) record = {} found_record = False wanted_rset = Record(name=record_in, type=type_in, ttl=ttl_in, identifier=identifier_in, weight=weight_in, region=region_in, health_check=health_check_in, failover=failover_in) for v in value_list: if alias_in: wanted_rset.set_alias(alias_hosted_zone_id_in, v) else: wanted_rset.add_value(v) sets = conn.get_all_rrsets(zone.id, name=record_in, type=type_in, identifier=identifier_in) for rset in sets: # Due to a bug in either AWS or Boto, "special" characters are returned as octals, preventing round
def test_record_commit(self): rrsets = ResourceRecordSets(self.service_connection) rrsets.add_change_record( 'CREATE', Record('vanilla.example.com', 'A', 60, ['1.2.3.4'])) rrsets.add_change_record( 'CREATE', Record('alias.example.com', 'AAAA', alias_hosted_zone_id='Z123OTHER', alias_dns_name='target.other', alias_evaluate_target_health=True)) rrsets.add_change_record( 'CREATE', Record('wrr.example.com', 'CNAME', 60, ['cname.target'], weight=10, identifier='weight-1')) rrsets.add_change_record( 'CREATE', Record('lbr.example.com', 'TXT', 60, ['text record'], region='us-west-2', identifier='region-1')) rrsets.add_change_record( 'CREATE', Record('failover.example.com', 'A', 60, ['2.2.2.2'], health_check='hc-1234', failover='PRIMARY', identifier='primary')) changes_xml = rrsets.to_xml() # the whitespacing doesn't match exactly, so we'll pretty print and drop all new lines # not the best, but actual_xml = re.sub( r"\s*[\r\n]+", "\n", xml.dom.minidom.parseString(changes_xml).toprettyxml()) expected_xml = re.sub( r"\s*[\r\n]+", "\n", xml.dom.minidom.parseString(b""" <ChangeResourceRecordSetsRequest xmlns="https://route53.amazonaws.com/doc/2013-04-01/"> <ChangeBatch> <Comment>None</Comment> <Changes> <Change> <Action>CREATE</Action> <ResourceRecordSet> <Name>vanilla.example.com</Name> <Type>A</Type> <TTL>60</TTL> <ResourceRecords> <ResourceRecord> <Value>1.2.3.4</Value> </ResourceRecord> </ResourceRecords> </ResourceRecordSet> </Change> <Change> <Action>CREATE</Action> <ResourceRecordSet> <Name>alias.example.com</Name> <Type>AAAA</Type> <AliasTarget> <HostedZoneId>Z123OTHER</HostedZoneId> <DNSName>target.other</DNSName> <EvaluateTargetHealth>true</EvaluateTargetHealth> </AliasTarget> </ResourceRecordSet> </Change> <Change> <Action>CREATE</Action> <ResourceRecordSet> <Name>wrr.example.com</Name> <Type>CNAME</Type> <SetIdentifier>weight-1</SetIdentifier> <Weight>10</Weight> <TTL>60</TTL> <ResourceRecords> <ResourceRecord> <Value>cname.target</Value> </ResourceRecord> </ResourceRecords> </ResourceRecordSet> </Change> <Change> <Action>CREATE</Action> <ResourceRecordSet> <Name>lbr.example.com</Name> <Type>TXT</Type> <SetIdentifier>region-1</SetIdentifier> <Region>us-west-2</Region> <TTL>60</TTL> <ResourceRecords> <ResourceRecord> <Value>text record</Value> </ResourceRecord> </ResourceRecords> </ResourceRecordSet> </Change> <Change> <Action>CREATE</Action> <ResourceRecordSet> <Name>failover.example.com</Name> <Type>A</Type> <SetIdentifier>primary</SetIdentifier> <Failover>PRIMARY</Failover> <TTL>60</TTL> <ResourceRecords> <ResourceRecord> <Value>2.2.2.2</Value> </ResourceRecord> </ResourceRecords> <HealthCheckId>hc-1234</HealthCheckId> </ResourceRecordSet> </Change> </Changes> </ChangeBatch> </ChangeResourceRecordSetsRequest> """).toprettyxml()) # Note: the alias XML should not include the TTL, even if it's specified in the object model self.assertEqual(actual_xml, expected_xml)
def main(): argument_spec = ec2_argument_spec() argument_spec.update( dict( state=dict( type='str', required=True, choices=['absent', 'create', 'delete', 'get', 'present'], aliases=['command']), zone=dict(type='str'), hosted_zone_id=dict(type='str'), record=dict(type='str', required=True), ttl=dict(type='int', default=3600), type=dict(type='str', required=True, choices=[ 'A', 'AAAA', 'CAA', 'CNAME', 'MX', 'NS', 'PTR', 'SOA', 'SPF', 'SRV', 'TXT' ]), alias=dict(type='bool'), alias_hosted_zone_id=dict(type='str'), alias_evaluate_target_health=dict(type='bool', default=False), value=dict(type='list'), overwrite=dict(type='bool'), retry_interval=dict(type='int', default=500), private_zone=dict(type='bool', default=False), identifier=dict(type='str'), weight=dict(type='int'), region=dict(type='str'), health_check=dict(type='str'), failover=dict(type='str', choices=['PRIMARY', 'SECONDARY']), vpc_id=dict(type='str'), wait=dict(type='bool', default=False), wait_timeout=dict(type='int', default=300), )) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, required_one_of=[['zone', 'hosted_zone_id']], # If alias is True then you must specify alias_hosted_zone as well required_together=[['alias', 'alias_hosted_zone_id']], # state=present, absent, create, delete THEN value is required required_if=( ('state', 'present', ['value']), ('state', 'create', ['value']), ('state', 'absent', ['value']), ('state', 'delete', ['value']), ), # failover, region and weight are mutually exclusive mutually_exclusive=[('failover', 'region', 'weight')], # failover, region and weight require identifier required_by=dict( failover=('identifier', ), region=('identifier', ), weight=('identifier', ), ), ) if not HAS_BOTO: module.fail_json(msg='boto required for this module') if distutils.version.StrictVersion( boto.__version__) < distutils.version.StrictVersion( MINIMUM_BOTO_VERSION): module.fail_json( msg='Found boto in version %s, but >= %s is required' % (boto.__version__, MINIMUM_BOTO_VERSION)) if module.params['state'] in ('present', 'create'): command_in = 'create' elif module.params['state'] in ('absent', 'delete'): command_in = 'delete' elif module.params['state'] == 'get': command_in = 'get' zone_in = (module.params.get('zone') or '').lower() hosted_zone_id_in = module.params.get('hosted_zone_id') ttl_in = module.params.get('ttl') record_in = module.params.get('record').lower() type_in = module.params.get('type') value_in = module.params.get('value') or [] alias_in = module.params.get('alias') alias_hosted_zone_id_in = module.params.get('alias_hosted_zone_id') alias_evaluate_target_health_in = module.params.get( 'alias_evaluate_target_health') retry_interval_in = module.params.get('retry_interval') if module.params['vpc_id'] is not None: private_zone_in = True else: private_zone_in = module.params.get('private_zone') identifier_in = module.params.get('identifier') weight_in = module.params.get('weight') region_in = module.params.get('region') health_check_in = module.params.get('health_check') failover_in = module.params.get('failover') vpc_id_in = module.params.get('vpc_id') wait_in = module.params.get('wait') wait_timeout_in = module.params.get('wait_timeout') region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module) if zone_in[-1:] != '.': zone_in += "." if record_in[-1:] != '.': record_in += "." if command_in == 'create' or command_in == 'delete': if alias_in and len(value_in) != 1: module.fail_json( msg= "parameter 'value' must contain a single dns name for alias records" ) if (weight_in is None and region_in is None and failover_in is None) and identifier_in is not None: module.fail_json( msg= "You have specified identifier which makes sense only if you specify one of: weight, region or failover." ) # connect to the route53 endpoint try: conn = Route53Connection(**aws_connect_kwargs) except boto.exception.BotoServerError as e: module.fail_json(msg=e.error_message) # Find the named zone ID zone_id = hosted_zone_id_in or get_zone_id_by_name( conn, module, zone_in, private_zone_in, vpc_id_in) # Verify that the requested zone is already defined in Route53 if zone_id is None: errmsg = "Zone %s does not exist in Route53" % (zone_in or hosted_zone_id_in) module.fail_json(msg=errmsg) record = {} found_record = False wanted_rset = Record(name=record_in, type=type_in, ttl=ttl_in, identifier=identifier_in, weight=weight_in, region=region_in, health_check=health_check_in, failover=failover_in) for v in value_in: if alias_in: wanted_rset.set_alias(alias_hosted_zone_id_in, v, alias_evaluate_target_health_in) else: wanted_rset.add_value(v) need_to_sort_records = (type_in == 'CAA') # Sort records for wanted_rset if necessary (keep original list) unsorted_records = wanted_rset.resource_records if need_to_sort_records: wanted_rset.resource_records = sorted(unsorted_records) sets = invoke_with_throttling_retries(conn.get_all_rrsets, zone_id, name=record_in, type=type_in, identifier=identifier_in) sets_iter = iter(sets) while True: try: rset = invoke_with_throttling_retries(next, sets_iter) except StopIteration: break # Due to a bug in either AWS or Boto, "special" characters are returned as octals, preventing round # tripping of things like * and @. decoded_name = rset.name.replace(r'\052', '*') decoded_name = decoded_name.replace(r'\100', '@') # Need to save this changes in rset, because of comparing rset.to_xml() == wanted_rset.to_xml() in next block rset.name = decoded_name if identifier_in is not None: identifier_in = str(identifier_in) if rset.type == type_in and decoded_name.lower() == record_in.lower( ) and rset.identifier == identifier_in: if need_to_sort_records: # Sort records rset.resource_records = sorted(rset.resource_records) found_record = True record['zone'] = zone_in record['type'] = rset.type record['record'] = decoded_name record['ttl'] = rset.ttl record['identifier'] = rset.identifier record['weight'] = rset.weight record['region'] = rset.region record['failover'] = rset.failover record['health_check'] = rset.health_check record['hosted_zone_id'] = zone_id if rset.alias_dns_name: record['alias'] = True record['value'] = rset.alias_dns_name record['values'] = [rset.alias_dns_name] record['alias_hosted_zone_id'] = rset.alias_hosted_zone_id record[ 'alias_evaluate_target_health'] = rset.alias_evaluate_target_health else: record['alias'] = False record['value'] = ','.join(sorted(rset.resource_records)) record['values'] = sorted(rset.resource_records) if command_in == 'create' and rset.to_xml() == wanted_rset.to_xml( ): module.exit_json(changed=False) # We need to look only at the first rrset returned by the above call, # so break here. The returned elements begin with the one matching our # requested name, type, and identifier, if such an element exists, # followed by all others that come after it in alphabetical order. # Therefore, if the first set does not match, no subsequent set will # match either. break if command_in == 'get': if type_in == 'NS': ns = record.get('values', []) else: # Retrieve name servers associated to the zone. z = invoke_with_throttling_retries(conn.get_zone, zone_in) ns = invoke_with_throttling_retries(z.get_nameservers) module.exit_json(changed=False, set=record, nameservers=ns) if command_in == 'delete' and not found_record: module.exit_json(changed=False) changes = ResourceRecordSets(conn, zone_id) if command_in == 'create' or command_in == 'delete': if command_in == 'create' and found_record: if not module.params['overwrite']: module.fail_json( msg= "Record already exists with different value. Set 'overwrite' to replace it" ) command = 'UPSERT' else: command = command_in.upper() # Restore original order of records wanted_rset.resource_records = unsorted_records changes.add_change_record(command, wanted_rset) if not module.check_mode: try: invoke_with_throttling_retries(commit, changes, retry_interval_in, wait_in, wait_timeout_in) except boto.route53.exception.DNSServerError as e: txt = e.body.split("<Message>")[1] txt = txt.split("</Message>")[0] if "but it already exists" in txt: module.exit_json(changed=False) else: module.fail_json(msg=txt) except TimeoutError: module.fail_json(msg='Timeout waiting for changes to replicate') module.exit_json(changed=True)
def create_dns_record(self, fqdn, record_type, zone_id, records, ttl=60, routing_policy='simple', weight=None, identifier=None, region=None, health_check_id=None, failover="primary"): botoconn = self.__get_boto_conn() if routing_policy == 'simple': weight = None identifier = None region = None health_check_id = None failover = None elif routing_policy == 'weighted': region = None failover = None if not weight: raise AttributeError( "weight must be provided for weighted routing policy") if not identifier: raise AttributeError( "identifier must be provided for weighted routing policy") elif routing_policy == 'latency': weight = None failover = None if not region: raise AttributeError( "region must be provided for latency routing policy") if not identifier: raise AttributeError( "identifier must be provided for latency routing policy") elif routing_policy == 'failover': weight = None region = None if not failover: raise AttributeError( "failover must be provided for failover routing policy") if not identifier: raise AttributeError( "identifier must be provided for failover routing policy") health_check = None if health_check_id: self.db.execute( "select healthcheck_id from route53_healthchecks where id=%s", (health_check_id, )) row = self.db.fetchone() if not row: raise ResourceNotFound( "Could not find information on health check {0}".format( health_check_id)) health_check = row[0] zones = botoconn.get_zones() zone = None # unfortunately boto's get_zone only takes a zone name which is not necessarily unique :( for z in zones: if z.id == zone_id: zone = z break if not zone: raise ResourceNotFound("Zone ID {0} not found".format(zone_id)) rrset = zone.get_records() record_type = record_type.upper() if failover is not None: failover = failover.upper() rec = Record(name=fqdn, type=record_type, ttl=ttl, resource_records=records, identifier=identifier, weight=weight, region=region, health_check=health_check, failover=failover) rrset.add_change_record('CREATE', rec) response = rrset.commit() if 'ChangeResourceRecordSetsResponse' in response: name = rec.name if name[len(name) - 1] == '.': name = name[0:len(name) - 1] ident = rec.identifier if not rec.identifier: ident = "" self.db.execute( "insert into route53_records set zone_id=%s, name=%s, type=%s, identifier=%s, resource_records=%s, ttl=%s, alias_hosted_zone_id=%s, " "alias_dns_name=%s, weight=%s, region=%s, healthcheck_id=%s on duplicate key update resource_records=%s, ttl=%s, alias_hosted_zone_id=%s, " "alias_dns_name=%s, weight=%s, region=%s, healthcheck_id=%s", (zone_id, name, rec.type, ident, "\n".join( rec.resource_records), rec.ttl, rec.alias_hosted_zone_id, rec.alias_dns_name, rec.weight, rec.region, rec.health_check, "\n".join( rec.resource_records), rec.ttl, rec.alias_hosted_zone_id, rec.alias_dns_name, rec.weight, rec.region, rec.health_check)) self.dbconn.commit() self.logger.info("Created new dns entry for {0} -> {1}".format( fqdn, " \\n ".join(records))) self.db.execute( "update route53_zones z set record_sets = (select count(*) from route53_records where zone_id=z.zone_id)" ) self.dbconn.commit()