-
-
Save benneic/6034599 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -*- coding: utf-8 -*- | |
| #!/usr/bin/env python | |
| # -*- coding: utf-8 -*- | |
| # | |
| # Author: Roman Kalyakin | |
| # | |
| # EC2 to Route53 magic | |
| # | |
| # https://gist.github.com/4449827 | |
| # | |
| # Modifications: | |
| # - key and secret not required (boto will look in /etc/boto.cfg) | |
| # - only print messages when things change or debug mode is on | |
| # | |
| try: | |
| from boto import utils, ec2, route53 | |
| from boto.route53.record import ResourceRecordSets | |
| except: | |
| raise Exception('Boto is not found. $ pip install boto') | |
| import subprocess | |
| class EC2ToRoute53(object): | |
| RESOLV_CONF = '/etc/resolvconf/resolv.conf.d/base' | |
| HOSTNAME_CONF = '/etc/hostname' | |
| def __init__(self, access_key=None, secret=None): | |
| self.access_key = access_key | |
| self.secret = secret | |
| def get_instance_metadata(self, instance_id=None): | |
| if instance_id is None: | |
| meta = utils.get_instance_metadata() | |
| if not meta: | |
| raise Exception('This machine seems to be not an EC2 instance.') | |
| instance_id = meta['instance-id'] | |
| ec2_connection = ec2.connection.EC2Connection(self.access_key, self.secret) | |
| reservations = ec2_connection.get_all_instances([instance_id]) | |
| reservation = reservations[0] | |
| instance = reservation.instances[0] | |
| return { | |
| 'instance-id': instance.id, | |
| 'private-ip': instance.private_ip_address, | |
| 'public-ip': instance.ip_address, | |
| 'tags': instance.tags, | |
| } | |
| def add_a_record_to_zone(self, hostname, ip, zone, debug=False): | |
| route53_connection = route53.connection.Route53Connection(self.access_key, self.secret) | |
| zone_id = route53_connection.get_hosted_zone_by_name(zone) | |
| if not zone_id: | |
| raise Exception('No such zone found: {}'.format(zone)) | |
| zone_id = zone_id['GetHostedZoneResponse']['HostedZone']['Id'].replace('/hostedzone/', '') | |
| fqdn = '{}.{}'.format(hostname, zone) | |
| changes = ResourceRecordSets(route53_connection, zone_id) | |
| # find existing record | |
| existing_records = route53_connection.get_all_rrsets(zone_id, 'A', fqdn, maxitems=1) | |
| if existing_records: | |
| record = existing_records[0] | |
| if record.resource_records == [ip]: | |
| if debug: | |
| print 'Record A {} with {} already exists. Skipping.'.format(fqdn, ip) | |
| return | |
| # schedule removal | |
| print 'Removing existing A record {}'.format(fqdn) | |
| changes.changes.append(("DELETE", record)) | |
| # add new | |
| print 'Adding A record {} for {}'.format(fqdn, ip) | |
| add = changes.add_change('CREATE', fqdn, 'A') | |
| add.add_value(ip) | |
| # go for it | |
| try: | |
| changes.commit() | |
| print 'Done.' | |
| except route53.exception.DNSServerError, e: | |
| print e.error_message | |
| return True | |
| def update_hostname_and_search_domains(self, hostname, domains, debug=False): | |
| try: | |
| if domains: | |
| f = open(self.RESOLV_CONF, 'w') | |
| f.truncate() | |
| for domain in domains: | |
| f.write("search {}\n".format(domain)) | |
| f.close() | |
| subprocess.call(['resolvconf', '-u']) | |
| if debug: | |
| print 'Updated {}'.format(self.RESOLV_CONF) | |
| except Exception, e: | |
| print 'Could not update search domains: {}'.format(e) | |
| try: | |
| f = open(self.HOSTNAME_CONF, 'w') | |
| f.truncate() | |
| f.write(hostname) | |
| f.close() | |
| subprocess.call(['hostname', '-F', self.HOSTNAME_CONF]) | |
| if debug: | |
| print 'Updated {}'.format(self.HOSTNAME_CONF) | |
| except Exception, e: | |
| print 'Could not update hostname: {}'.format(e) | |
| if __name__ == '__main__': | |
| import argparse | |
| parser = argparse.ArgumentParser( | |
| description='Create a Route53 record from EC2 instance tag value and optinally set hostname and search domains on the server.', | |
| formatter_class=argparse.ArgumentDefaultsHelpFormatter) | |
| parser.add_argument('-K', '--access-key', help='AWS Access Key') | |
| parser.add_argument('-S', '--secret', help='AWS Secret') | |
| parser.add_argument('-T', '--tag-name', default='Name', help='EC2 Instance tag name to read hostname from.') | |
| parser.add_argument('-I', '--instance-id', help='Instance ID. Default to current machine instance ID.') | |
| parser.add_argument('-P', '--public-zone', help='Zone name to add public IP record to.') | |
| parser.add_argument('-R', '--private-zone', help='Zone name to add private IP record to.') | |
| parser.add_argument('-K53', '--access-key-53', help='AWS Access Key for Route53. Defaults to one in -K.') | |
| parser.add_argument('-S53', '--secret-53', help='AWS Secret for Route53. Defaults to one in -S.') | |
| parser.add_argument('-U', '--update', action='store_true', default=False, help='Update hostname and search zones.') | |
| parser.add_argument('-D', '--debug', action='store_true', default=False, help='Print instance metadata and quit.') | |
| args = parser.parse_args() | |
| if args.access_key_53 is None and args.access_key is not None: | |
| if not args.secret: | |
| raise Exception('Must provide AWS Secret when AWS Access Key is provided.') | |
| args.access_key_53 = args.access_key | |
| args.secret_53 = args.secret | |
| ec2_worker = EC2ToRoute53(args.access_key, args.secret) | |
| r53_worker = EC2ToRoute53(args.access_key_53, args.secret_53) | |
| instance_metadata = ec2_worker.get_instance_metadata(args.instance_id) | |
| if args.tag_name not in instance_metadata['tags']: | |
| raise Exception('No {} tag for instance.'.format(args.tag_name)) | |
| hostname = instance_metadata['tags'][args.tag_name] | |
| if args.debug: | |
| print '' | |
| print 'Metadata for host: ', hostname | |
| print instance_metadata | |
| print '' | |
| search_domains = [] | |
| if args.private_zone: | |
| r53_worker.add_a_record_to_zone(hostname, instance_metadata['private-ip'], args.private_zone, debug=args.debug) | |
| search_domains.append(args.private_zone) | |
| if args.public_zone: | |
| r53_worker.add_a_record_to_zone(hostname, instance_metadata['public-ip'], args.public_zone, debug=args.debug) | |
| search_domains.append(args.public_zone) | |
| if args.update: | |
| r53_worker.update_hostname_and_search_domains(hostname, search_domains) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment