From d2f9b445552bc26b98e71df74b29a3014a8e5b86 Mon Sep 17 00:00:00 2001 From: Michael Leer Date: Wed, 13 Feb 2019 12:34:12 +0000 Subject: [PATCH 01/10] create a version that supports boto3/python3, updated requirements file --- aws-ssh-config-boto3.py | 193 ++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 194 insertions(+) create mode 100644 aws-ssh-config-boto3.py diff --git a/aws-ssh-config-boto3.py b/aws-ssh-config-boto3.py new file mode 100644 index 0000000..2d424f8 --- /dev/null +++ b/aws-ssh-config-boto3.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 + +import argparse +import re +import sys +import time +import boto3 + + +AMI_NAMES_TO_USER = { + 'amzn' : 'ec2-user', + 'ubuntu' : 'ubuntu', + 'CentOS' : 'root', + 'DataStax' : 'ubuntu', + 'CoreOS' : 'core' +} + +AMI_IDS_TO_USER = { + 'ami-ada2b6c4' : 'ubuntu' +} + +AMI_IDS_TO_KEY = { + 'ami-ada2b6c4' : 'custom_key' +} + +BLACKLISTED_REGIONS = [ + +] + +def generate_id(instance, tags_filter, region): + instance_id = '' + + if tags_filter is not None: + for tag in tags_filter.split(','): + for aws_tag in instance['Instances'][0]['Tags']: + value = aws_tag['Value'] + if value: + if not instance_id: + instance_id = value + else: + instance_id += '-' + value + else: + for tag in instance['Instances'][0]['Tags']: + if not (tag['Key']).startswith('aws'): + if not instance_id: + instance_id = tag['Value'] + else: + instance_id += '-' + tag['Value'] + + if not instance_id: + instance_id = instance['Instances'][0]['InstanceId'] + + if region: + instance_id += '-' + instance['Instances'][0]['Placement']['AvailabilityZone'] + + return instance_id + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--default-user', help='Default ssh username to use if it can\'t be detected from AMI name') + parser.add_argument('--keydir', default='~/.ssh/', help='Location of private keys') + parser.add_argument('--no-identities-only', action='store_true', help='Do not include IdentitiesOnly=yes in ssh config; may cause connection refused if using ssh-agent') + parser.add_argument('--postfix', default='', help='Specify a postfix to append to all host names') + parser.add_argument('--prefix', default='', help='Specify a prefix to prepend to all host names') + parser.add_argument('--private', action='store_true', help='Use private IP addresses (public are used by default)') + parser.add_argument('--profile', help='Specify AWS credential profile to use') + parser.add_argument('--proxy', default='', help='Specify a bastion host for ProxyCommand') + parser.add_argument('--region', action='store_true', help='Append the region name at the end of the concatenation') + parser.add_argument('--ssh-key-name', default='', help='Override the ssh key to use') + parser.add_argument('--strict-hostkey-checking', action='store_true', help='Do not include StrictHostKeyChecking=no in ssh config') + parser.add_argument('--tags', help='A comma-separated list of tag names to be considered for concatenation. If omitted, all tags will be used') + parser.add_argument('--user', help='Override the ssh username for all hosts') + parser.add_argument('--white-list-region', default='', help='Which regions must be included. If omitted, all regions are considered', nargs='+') + + args = parser.parse_args() + + instances = {} + counts_total = {} + counts_incremental = {} + amis = AMI_IDS_TO_USER.copy() + + print('# Generated on ' + time.asctime(time.localtime(time.time()))) + print('# ' + ' '.join(sys.argv)) + print('# ') + print('') + if args.profile: + session = boto3.Session(profile_name=args.profile) + regions = session.client('ec2').describe_regions()['Regions'] + else: + regions = boto3.client('ec2').describe_regions()['Regions'] + for region in regions: + if args.white_list_region and region['RegionName'] not in args.white_list_region: + continue + if region['RegionName'] in BLACKLISTED_REGIONS: + continue + if args.profile: + conn = session.client('ec2', region_name=region['RegionName']) + else: + conn = boto3.client('ec2', region_name=region['RegionName']) + + for instance in conn.describe_instances()['Reservations']: + if instance['Instances'][0]['State']['Name'] != 'running': + continue + + if instance['Instances'][0]['KeyName'] is None: + continue + + if instance['Instances'][0]['LaunchTime'] not in instances: + instances[instance['Instances'][0]['LaunchTime']] = [] + + instances[instance['Instances'][0]['LaunchTime']].append(instance) + + instance_id = generate_id(instance, args.tags, args.region) + + if instance_id not in counts_total: + counts_total[instance_id] = 0 + counts_incremental[instance_id] = 0 + + counts_total[instance_id] += 1 + + if args.user: + amis[instance['Instances'][0]['ImageId']] = args.user + else: + if not instance['Instances'][0]['ImageId'] in amis: + image = conn.describe_images(Filters=[{'Name':'image-id','Values':[instance['Instances'][0]['ImageId']]}]) + + for ami, user in AMI_NAMES_TO_USER.items(): + regexp = re.compile(ami) + if len(image['Images']) > 0 and regexp.match(image['Images'][0]['Name']): + amis[instance['Instances'][0]['ImageId']] = user + break + + if instance['Instances'][0]['ImageId'] not in amis: + amis[instance['Instances'][0]['ImageId']] = args.default_user + if args.default_user is None: + image_label = image['Images'][0]['ImageId'] if image['Images'][0] is not None else instance['Instances'][0]['ImageId'] + sys.stderr.write('Can\'t lookup user for AMI \'' + image_label + '\', add a rule to the script\n') + + for k in sorted(instances): + for instance in instances[k]: + if args.private: + if instance['Instances'][0]['PrivateIpAddress']: + ip_addr = instance['Instances'][0]['PrivateIpAddress'] + else: + if instance['Instances'][0]['PublicIpAddress']: + ip_addr = instance['Instances'][0]['PublicIpAddress'] + elif instance['Instances'][0]['PrivateIpAddress']: + ip_addr = instance['Instances'][0]['PrivateIpAddress'] + else: + sys.stderr.write('Cannot lookup ip address for instance %s, skipped it.' % instance['Instances'][0]['InstanceId']) + continue + + instance_id = generate_id(instance, args.tags, args.region) + + if counts_total[instance_id] != 1: + counts_incremental[instance_id] += 1 + instance_id += '-' + str(counts_incremental[instance_id]) + + hostid = args.prefix + instance_id + args.postfix + hostid = hostid.replace(' ', '_') # get rid of spaces + + if instance['Instances'][0]['InstanceId']: + print('# id: ' + instance['Instances'][0]['InstanceId']) + print('Host ' + hostid) + print(' HostName ' + ip_addr) + + if amis[instance['Instances'][0]['ImageId']] is not None: + print(' User ' + amis[instance['Instances'][0]['ImageId']]) + + if args.keydir: + keydir = args.keydir + else: + keydir = '~/.ssh/' + + if args.ssh_key_name: + print(' IdentityFile ' + keydir + args.ssh_key_name + '.pem') + else: + key_name = AMI_IDS_TO_KEY.get(instance['Instances'][0]['ImageId'], instance['Instances'][0]['KeyName']) + + print(' IdentityFile ' + keydir + key_name.replace(' ', '_') + '.pem') + + if not args.no_identities_only: + # ensure ssh-agent keys don't flood when we know the right file to use + print(' IdentitiesOnly yes') + if not args.strict_hostkey_checking: + print(' StrictHostKeyChecking no') + if args.proxy: + print(' ProxyCommand ssh ' + args.proxy + ' -W %h:%p') + print('') + + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt index a0826ee..1068d91 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ boto +boto3 From 4c53a46862e47e7b1757130bf4839ce216903129 Mon Sep 17 00:00:00 2001 From: Michael Leer Date: Wed, 20 Mar 2019 22:58:15 +0000 Subject: [PATCH 02/10] convert if statement to try statement --- aws-ssh-config-boto3.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/aws-ssh-config-boto3.py b/aws-ssh-config-boto3.py index 2d424f8..ca56409 100644 --- a/aws-ssh-config-boto3.py +++ b/aws-ssh-config-boto3.py @@ -24,7 +24,7 @@ } BLACKLISTED_REGIONS = [ - + ] def generate_id(instance, tags_filter, region): @@ -142,13 +142,15 @@ def main(): if instance['Instances'][0]['PrivateIpAddress']: ip_addr = instance['Instances'][0]['PrivateIpAddress'] else: - if instance['Instances'][0]['PublicIpAddress']: + try: ip_addr = instance['Instances'][0]['PublicIpAddress'] - elif instance['Instances'][0]['PrivateIpAddress']: - ip_addr = instance['Instances'][0]['PrivateIpAddress'] - else: - sys.stderr.write('Cannot lookup ip address for instance %s, skipped it.' % instance['Instances'][0]['InstanceId']) - continue + except KeyError: + try: + ip_addr = instance['Instances'][0]['PrivateIpAddress'] + except KeyError: + sys.stderr.write('Cannot lookup ip address for instance %s, skipped it.' % instance['Instances'][0]['InstanceId']) + continue + instance_id = generate_id(instance, args.tags, args.region) From b37a8b665f7b16734e9c5978a3f53dc753df43a7 Mon Sep 17 00:00:00 2001 From: Michael Leer Date: Wed, 20 Mar 2019 23:00:08 +0000 Subject: [PATCH 03/10] convert to simply use "python" with python2/python3 compatiblity --- aws-ssh-config-boto3.py | 195 ---------------------------------------- aws-ssh-config.py | 112 ++++++++++++----------- 2 files changed, 55 insertions(+), 252 deletions(-) delete mode 100644 aws-ssh-config-boto3.py diff --git a/aws-ssh-config-boto3.py b/aws-ssh-config-boto3.py deleted file mode 100644 index ca56409..0000000 --- a/aws-ssh-config-boto3.py +++ /dev/null @@ -1,195 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import re -import sys -import time -import boto3 - - -AMI_NAMES_TO_USER = { - 'amzn' : 'ec2-user', - 'ubuntu' : 'ubuntu', - 'CentOS' : 'root', - 'DataStax' : 'ubuntu', - 'CoreOS' : 'core' -} - -AMI_IDS_TO_USER = { - 'ami-ada2b6c4' : 'ubuntu' -} - -AMI_IDS_TO_KEY = { - 'ami-ada2b6c4' : 'custom_key' -} - -BLACKLISTED_REGIONS = [ - -] - -def generate_id(instance, tags_filter, region): - instance_id = '' - - if tags_filter is not None: - for tag in tags_filter.split(','): - for aws_tag in instance['Instances'][0]['Tags']: - value = aws_tag['Value'] - if value: - if not instance_id: - instance_id = value - else: - instance_id += '-' + value - else: - for tag in instance['Instances'][0]['Tags']: - if not (tag['Key']).startswith('aws'): - if not instance_id: - instance_id = tag['Value'] - else: - instance_id += '-' + tag['Value'] - - if not instance_id: - instance_id = instance['Instances'][0]['InstanceId'] - - if region: - instance_id += '-' + instance['Instances'][0]['Placement']['AvailabilityZone'] - - return instance_id - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('--default-user', help='Default ssh username to use if it can\'t be detected from AMI name') - parser.add_argument('--keydir', default='~/.ssh/', help='Location of private keys') - parser.add_argument('--no-identities-only', action='store_true', help='Do not include IdentitiesOnly=yes in ssh config; may cause connection refused if using ssh-agent') - parser.add_argument('--postfix', default='', help='Specify a postfix to append to all host names') - parser.add_argument('--prefix', default='', help='Specify a prefix to prepend to all host names') - parser.add_argument('--private', action='store_true', help='Use private IP addresses (public are used by default)') - parser.add_argument('--profile', help='Specify AWS credential profile to use') - parser.add_argument('--proxy', default='', help='Specify a bastion host for ProxyCommand') - parser.add_argument('--region', action='store_true', help='Append the region name at the end of the concatenation') - parser.add_argument('--ssh-key-name', default='', help='Override the ssh key to use') - parser.add_argument('--strict-hostkey-checking', action='store_true', help='Do not include StrictHostKeyChecking=no in ssh config') - parser.add_argument('--tags', help='A comma-separated list of tag names to be considered for concatenation. If omitted, all tags will be used') - parser.add_argument('--user', help='Override the ssh username for all hosts') - parser.add_argument('--white-list-region', default='', help='Which regions must be included. If omitted, all regions are considered', nargs='+') - - args = parser.parse_args() - - instances = {} - counts_total = {} - counts_incremental = {} - amis = AMI_IDS_TO_USER.copy() - - print('# Generated on ' + time.asctime(time.localtime(time.time()))) - print('# ' + ' '.join(sys.argv)) - print('# ') - print('') - if args.profile: - session = boto3.Session(profile_name=args.profile) - regions = session.client('ec2').describe_regions()['Regions'] - else: - regions = boto3.client('ec2').describe_regions()['Regions'] - for region in regions: - if args.white_list_region and region['RegionName'] not in args.white_list_region: - continue - if region['RegionName'] in BLACKLISTED_REGIONS: - continue - if args.profile: - conn = session.client('ec2', region_name=region['RegionName']) - else: - conn = boto3.client('ec2', region_name=region['RegionName']) - - for instance in conn.describe_instances()['Reservations']: - if instance['Instances'][0]['State']['Name'] != 'running': - continue - - if instance['Instances'][0]['KeyName'] is None: - continue - - if instance['Instances'][0]['LaunchTime'] not in instances: - instances[instance['Instances'][0]['LaunchTime']] = [] - - instances[instance['Instances'][0]['LaunchTime']].append(instance) - - instance_id = generate_id(instance, args.tags, args.region) - - if instance_id not in counts_total: - counts_total[instance_id] = 0 - counts_incremental[instance_id] = 0 - - counts_total[instance_id] += 1 - - if args.user: - amis[instance['Instances'][0]['ImageId']] = args.user - else: - if not instance['Instances'][0]['ImageId'] in amis: - image = conn.describe_images(Filters=[{'Name':'image-id','Values':[instance['Instances'][0]['ImageId']]}]) - - for ami, user in AMI_NAMES_TO_USER.items(): - regexp = re.compile(ami) - if len(image['Images']) > 0 and regexp.match(image['Images'][0]['Name']): - amis[instance['Instances'][0]['ImageId']] = user - break - - if instance['Instances'][0]['ImageId'] not in amis: - amis[instance['Instances'][0]['ImageId']] = args.default_user - if args.default_user is None: - image_label = image['Images'][0]['ImageId'] if image['Images'][0] is not None else instance['Instances'][0]['ImageId'] - sys.stderr.write('Can\'t lookup user for AMI \'' + image_label + '\', add a rule to the script\n') - - for k in sorted(instances): - for instance in instances[k]: - if args.private: - if instance['Instances'][0]['PrivateIpAddress']: - ip_addr = instance['Instances'][0]['PrivateIpAddress'] - else: - try: - ip_addr = instance['Instances'][0]['PublicIpAddress'] - except KeyError: - try: - ip_addr = instance['Instances'][0]['PrivateIpAddress'] - except KeyError: - sys.stderr.write('Cannot lookup ip address for instance %s, skipped it.' % instance['Instances'][0]['InstanceId']) - continue - - - instance_id = generate_id(instance, args.tags, args.region) - - if counts_total[instance_id] != 1: - counts_incremental[instance_id] += 1 - instance_id += '-' + str(counts_incremental[instance_id]) - - hostid = args.prefix + instance_id + args.postfix - hostid = hostid.replace(' ', '_') # get rid of spaces - - if instance['Instances'][0]['InstanceId']: - print('# id: ' + instance['Instances'][0]['InstanceId']) - print('Host ' + hostid) - print(' HostName ' + ip_addr) - - if amis[instance['Instances'][0]['ImageId']] is not None: - print(' User ' + amis[instance['Instances'][0]['ImageId']]) - - if args.keydir: - keydir = args.keydir - else: - keydir = '~/.ssh/' - - if args.ssh_key_name: - print(' IdentityFile ' + keydir + args.ssh_key_name + '.pem') - else: - key_name = AMI_IDS_TO_KEY.get(instance['Instances'][0]['ImageId'], instance['Instances'][0]['KeyName']) - - print(' IdentityFile ' + keydir + key_name.replace(' ', '_') + '.pem') - - if not args.no_identities_only: - # ensure ssh-agent keys don't flood when we know the right file to use - print(' IdentitiesOnly yes') - if not args.strict_hostkey_checking: - print(' StrictHostKeyChecking no') - if args.proxy: - print(' ProxyCommand ssh ' + args.proxy + ' -W %h:%p') - print('') - - -if __name__ == '__main__': - main() diff --git a/aws-ssh-config.py b/aws-ssh-config.py index 9684263..a62dd1e 100755 --- a/aws-ssh-config.py +++ b/aws-ssh-config.py @@ -4,7 +4,7 @@ import re import sys import time -import boto.ec2 +import boto3 AMI_NAMES_TO_USER = { @@ -24,39 +24,37 @@ } BLACKLISTED_REGIONS = [ - 'cn-north-1', - 'us-gov-west-1' -] +] def generate_id(instance, tags_filter, region): instance_id = '' if tags_filter is not None: for tag in tags_filter.split(','): - value = instance.tags.get(tag, None) - if value: - if not instance_id: - instance_id = value - else: - instance_id += '-' + value + for aws_tag in instance['Instances'][0]['Tags']: + value = aws_tag['Value'] + if value: + if not instance_id: + instance_id = value + else: + instance_id += '-' + value else: - for tag, value in instance.tags.items(): - if not tag.startswith('aws'): + for tag in instance['Instances'][0]['Tags']: + if not (tag['Key']).startswith('aws'): if not instance_id: - instance_id = value + instance_id = tag['Value'] else: - instance_id += '-' + value + instance_id += '-' + tag['Value'] if not instance_id: - instance_id = instance.id + instance_id = instance['Instances'][0]['InstanceId'] if region: - instance_id += '-' + instance.placement + instance_id += '-' + instance['Instances'][0]['Placement']['AvailabilityZone'] return instance_id - def main(): parser = argparse.ArgumentParser() parser.add_argument('--default-user', help='Default ssh username to use if it can\'t be detected from AMI name') @@ -85,31 +83,32 @@ def main(): print('# ' + ' '.join(sys.argv)) print('# ') print('') - - for region in boto.ec2.regions(): - if args.white_list_region and region.name not in args.white_list_region: + if args.profile: + session = boto3.Session(profile_name=args.profile) + regions = session.client('ec2').describe_regions()['Regions'] + else: + regions = boto3.client('ec2').describe_regions()['Regions'] + for region in regions: + if args.white_list_region and region['RegionName'] not in args.white_list_region: continue - if region.name in BLACKLISTED_REGIONS: + if region['RegionName'] in BLACKLISTED_REGIONS: continue if args.profile: - conn = boto.ec2.connect_to_region(region.name, profile_name=args.profile) + conn = session.client('ec2', region_name=region['RegionName']) else: - conn = boto.ec2.connect_to_region(region.name) - - for instance in conn.get_only_instances(): - if instance.state != 'running': - continue + conn = boto3.client('ec2', region_name=region['RegionName']) - if instance.platform == 'windows': + for instance in conn.describe_instances()['Reservations']: + if instance['Instances'][0]['State']['Name'] != 'running': continue - if instance.key_name is None: + if instance['Instances'][0]['KeyName'] is None: continue - if instance.launch_time not in instances: - instances[instance.launch_time] = [] + if instance['Instances'][0]['LaunchTime'] not in instances: + instances[instance['Instances'][0]['LaunchTime']] = [] - instances[instance.launch_time].append(instance) + instances[instance['Instances'][0]['LaunchTime']].append(instance) instance_id = generate_id(instance, args.tags, args.region) @@ -120,36 +119,38 @@ def main(): counts_total[instance_id] += 1 if args.user: - amis[instance.image_id] = args.user + amis[instance['Instances'][0]['ImageId']] = args.user else: - if not instance.image_id in amis: - image = conn.get_image(instance.image_id) + if not instance['Instances'][0]['ImageId'] in amis: + image = conn.describe_images(Filters=[{'Name':'image-id','Values':[instance['Instances'][0]['ImageId']]}]) for ami, user in AMI_NAMES_TO_USER.items(): regexp = re.compile(ami) - if image and regexp.match(image.name): - amis[instance.image_id] = user + if len(image['Images']) > 0 and regexp.match(image['Images'][0]['Name']): + amis[instance['Instances'][0]['ImageId']] = user break - if instance.image_id not in amis: - amis[instance.image_id] = args.default_user + if instance['Instances'][0]['ImageId'] not in amis: + amis[instance['Instances'][0]['ImageId']] = args.default_user if args.default_user is None: - image_label = image.name if image is not None else instance.image_id + image_label = image['Images'][0]['ImageId'] if image['Images'][0] is not None else instance['Instances'][0]['ImageId'] sys.stderr.write('Can\'t lookup user for AMI \'' + image_label + '\', add a rule to the script\n') for k in sorted(instances): for instance in instances[k]: if args.private: - if instance.private_ip_address: - ip_addr = instance.private_ip_address + if instance['Instances'][0]['PrivateIpAddress']: + ip_addr = instance['Instances'][0]['PrivateIpAddress'] else: - if instance.ip_address: - ip_addr = instance.ip_address - elif instance.private_ip_address: - ip_addr = instance.private_ip_address - else: - sys.stderr.write('Cannot lookup ip address for instance %s, skipped it.' % instance.id) - continue + try: + ip_addr = instance['Instances'][0]['PublicIpAddress'] + except KeyError: + try: + ip_addr = instance['Instances'][0]['PrivateIpAddress'] + except KeyError: + sys.stderr.write('Cannot lookup ip address for instance %s, skipped it.' % instance['Instances'][0]['InstanceId']) + continue + instance_id = generate_id(instance, args.tags, args.region) @@ -160,16 +161,13 @@ def main(): hostid = args.prefix + instance_id + args.postfix hostid = hostid.replace(' ', '_') # get rid of spaces - if instance.id: - print('# id: ' + instance.id) + if instance['Instances'][0]['InstanceId']: + print('# id: ' + instance['Instances'][0]['InstanceId']) print('Host ' + hostid) print(' HostName ' + ip_addr) - try: - if amis[instance.image_id] is not None: - print(' User ' + amis[instance.image_id]) - except: - pass + if amis[instance['Instances'][0]['ImageId']] is not None: + print(' User ' + amis[instance['Instances'][0]['ImageId']]) if args.keydir: keydir = args.keydir @@ -179,7 +177,7 @@ def main(): if args.ssh_key_name: print(' IdentityFile ' + keydir + args.ssh_key_name + '.pem') else: - key_name = AMI_IDS_TO_KEY.get(instance.image_id, instance.key_name) + key_name = AMI_IDS_TO_KEY.get(instance['Instances'][0]['ImageId'], instance['Instances'][0]['KeyName']) print(' IdentityFile ' + keydir + key_name.replace(' ', '_') + '.pem') From 2071418f5369a14a6feac221d95ac54fc4b4c72a Mon Sep 17 00:00:00 2001 From: Michael Leer Date: Wed, 20 Mar 2019 23:00:29 +0000 Subject: [PATCH 04/10] remove boto requirement and ony use boto3 --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1068d91..30ddf82 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ -boto boto3 From f6f669f97a566d4d588e63474cfe6e67b1576c9b Mon Sep 17 00:00:00 2001 From: Michael Leer Date: Wed, 20 Mar 2019 23:26:28 +0000 Subject: [PATCH 05/10] pep8 / pycodestyle compliant --- aws-ssh-config.py | 143 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 107 insertions(+), 36 deletions(-) diff --git a/aws-ssh-config.py b/aws-ssh-config.py index a62dd1e..221a738 100755 --- a/aws-ssh-config.py +++ b/aws-ssh-config.py @@ -8,25 +8,26 @@ AMI_NAMES_TO_USER = { - 'amzn' : 'ec2-user', - 'ubuntu' : 'ubuntu', - 'CentOS' : 'root', - 'DataStax' : 'ubuntu', - 'CoreOS' : 'core' + 'amzn': 'ec2-user', + 'ubuntu': 'ubuntu', + 'CentOS': 'root', + 'DataStax': 'ubuntu', + 'CoreOS': 'core' } AMI_IDS_TO_USER = { - 'ami-ada2b6c4' : 'ubuntu' + 'ami-ada2b6c4': 'ubuntu' } AMI_IDS_TO_KEY = { - 'ami-ada2b6c4' : 'custom_key' + 'ami-ada2b6c4': 'custom_key' } BLACKLISTED_REGIONS = [ ] + def generate_id(instance, tags_filter, region): instance_id = '' @@ -51,27 +52,71 @@ def generate_id(instance, tags_filter, region): instance_id = instance['Instances'][0]['InstanceId'] if region: - instance_id += '-' + instance['Instances'][0]['Placement']['AvailabilityZone'] + instance_id += '-' + instance[ + 'Instances'][0]['Placement']['AvailabilityZone'] return instance_id + def main(): parser = argparse.ArgumentParser() - parser.add_argument('--default-user', help='Default ssh username to use if it can\'t be detected from AMI name') - parser.add_argument('--keydir', default='~/.ssh/', help='Location of private keys') - parser.add_argument('--no-identities-only', action='store_true', help='Do not include IdentitiesOnly=yes in ssh config; may cause connection refused if using ssh-agent') - parser.add_argument('--postfix', default='', help='Specify a postfix to append to all host names') - parser.add_argument('--prefix', default='', help='Specify a prefix to prepend to all host names') - parser.add_argument('--private', action='store_true', help='Use private IP addresses (public are used by default)') - parser.add_argument('--profile', help='Specify AWS credential profile to use') - parser.add_argument('--proxy', default='', help='Specify a bastion host for ProxyCommand') - parser.add_argument('--region', action='store_true', help='Append the region name at the end of the concatenation') - parser.add_argument('--ssh-key-name', default='', help='Override the ssh key to use') - parser.add_argument('--strict-hostkey-checking', action='store_true', help='Do not include StrictHostKeyChecking=no in ssh config') - parser.add_argument('--tags', help='A comma-separated list of tag names to be considered for concatenation. If omitted, all tags will be used') - parser.add_argument('--user', help='Override the ssh username for all hosts') - parser.add_argument('--white-list-region', default='', help='Which regions must be included. If omitted, all regions are considered', nargs='+') - + parser.add_argument( + '--default-user', + help='Default ssh username to use' + 'if it can\'t be detected from AMI name') + parser.add_argument( + '--keydir', + default='~/.ssh/', + help='Location of private keys') + parser.add_argument( + '--no-identities-only', + action='store_true', + help='Do not include IdentitiesOnly=yes in ssh config; may cause' + ' connection refused if using ssh-agent') + parser.add_argument( + '--postfix', + default='', + help='Specify a postfix to append to all host names') + parser.add_argument( + '--prefix', + default='', + help='Specify a prefix to prepend to all host names') + parser.add_argument( + '--private', + action='store_true', + help='Use private IP addresses (public are used by default)') + parser.add_argument( + '--profile', + help='Specify AWS credential profile to use') + parser.add_argument( + '--proxy', + default='', + help='Specify a bastion host for ProxyCommand') + parser.add_argument( + '--region', + action='store_true', + help='Append the region name at the end of the concatenation') + parser.add_argument( + '--ssh-key-name', + default='', + help='Override the ssh key to use') + parser.add_argument( + '--strict-hostkey-checking', + action='store_true', + help='Do not include StrictHostKeyChecking=no in ssh config') + parser.add_argument( + '--tags', + help='A comma-separated list of tag names to be considered for' + ' concatenation. If omitted, all tags will be used') + parser.add_argument( + '--user', + help='Override the ssh username for all hosts') + parser.add_argument( + '--white-list-region', + default='', + help='Which regions must be included. If omitted, all regions' + ' are considered', + nargs='+') args = parser.parse_args() instances = {} @@ -89,7 +134,8 @@ def main(): else: regions = boto3.client('ec2').describe_regions()['Regions'] for region in regions: - if args.white_list_region and region['RegionName'] not in args.white_list_region: + if (args.white_list_region + and region['RegionName'] not in args.white_list_region): continue if region['RegionName'] in BLACKLISTED_REGIONS: continue @@ -122,19 +168,37 @@ def main(): amis[instance['Instances'][0]['ImageId']] = args.user else: if not instance['Instances'][0]['ImageId'] in amis: - image = conn.describe_images(Filters=[{'Name':'image-id','Values':[instance['Instances'][0]['ImageId']]}]) + image = conn.describe_images( + Filters=[ + { + 'Name': 'image-id', + 'Values': [instance['Instances'][0]['ImageId']] + } + ] + ) for ami, user in AMI_NAMES_TO_USER.items(): regexp = re.compile(ami) - if len(image['Images']) > 0 and regexp.match(image['Images'][0]['Name']): + if (len(image['Images']) > 0 + and regexp.match(image['Images'][0]['Name'])): amis[instance['Instances'][0]['ImageId']] = user break if instance['Instances'][0]['ImageId'] not in amis: - amis[instance['Instances'][0]['ImageId']] = args.default_user + amis[ + instance['Instances'][0]['ImageId'] + ] = args.default_user if args.default_user is None: - image_label = image['Images'][0]['ImageId'] if image['Images'][0] is not None else instance['Instances'][0]['ImageId'] - sys.stderr.write('Can\'t lookup user for AMI \'' + image_label + '\', add a rule to the script\n') + image_label = image[ + 'Images' + ][0][ + 'ImageId'] if image[ + 'Images'][0] is not None else instance[ + 'Instances'][0]['ImageId'] + sys.stderr.write( + 'Can\'t lookup user for AMI \'' + + image_label + '\', add a rule to ' + 'the script\n') for k in sorted(instances): for instance in instances[k]: @@ -148,10 +212,12 @@ def main(): try: ip_addr = instance['Instances'][0]['PrivateIpAddress'] except KeyError: - sys.stderr.write('Cannot lookup ip address for instance %s, skipped it.' % instance['Instances'][0]['InstanceId']) + sys.stderr.write( + 'Cannot lookup ip address for instance %s,' + ' skipped it.' + % instance['Instances'][0]['InstanceId']) continue - instance_id = generate_id(instance, args.tags, args.region) if counts_total[instance_id] != 1: @@ -159,7 +225,7 @@ def main(): instance_id += '-' + str(counts_incremental[instance_id]) hostid = args.prefix + instance_id + args.postfix - hostid = hostid.replace(' ', '_') # get rid of spaces + hostid = hostid.replace(' ', '_') # get rid of spaces if instance['Instances'][0]['InstanceId']: print('# id: ' + instance['Instances'][0]['InstanceId']) @@ -175,14 +241,19 @@ def main(): keydir = '~/.ssh/' if args.ssh_key_name: - print(' IdentityFile ' + keydir + args.ssh_key_name + '.pem') + print(' IdentityFile ' + + keydir + args.ssh_key_name + '.pem') else: - key_name = AMI_IDS_TO_KEY.get(instance['Instances'][0]['ImageId'], instance['Instances'][0]['KeyName']) + key_name = AMI_IDS_TO_KEY.get( + instance['Instances'][0]['ImageId'], + instance['Instances'][0]['KeyName']) - print(' IdentityFile ' + keydir + key_name.replace(' ', '_') + '.pem') + print(' IdentityFile ' + + keydir + key_name.replace(' ', '_') + '.pem') if not args.no_identities_only: - # ensure ssh-agent keys don't flood when we know the right file to use + # ensure ssh-agent keys don't flood + # when we know the right file to use print(' IdentitiesOnly yes') if not args.strict_hostkey_checking: print(' StrictHostKeyChecking no') From f2779c2403cd6105d57bebb8065539320b514069 Mon Sep 17 00:00:00 2001 From: Greg Date: Wed, 22 May 2019 09:48:06 +0100 Subject: [PATCH 06/10] Handle no tags --- aws-ssh-config.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/aws-ssh-config.py b/aws-ssh-config.py index 221a738..1515e06 100755 --- a/aws-ssh-config.py +++ b/aws-ssh-config.py @@ -33,7 +33,7 @@ def generate_id(instance, tags_filter, region): if tags_filter is not None: for tag in tags_filter.split(','): - for aws_tag in instance['Instances'][0]['Tags']: + for aws_tag in instance['Instances'][0].get('Tags', []): value = aws_tag['Value'] if value: if not instance_id: @@ -41,7 +41,7 @@ def generate_id(instance, tags_filter, region): else: instance_id += '-' + value else: - for tag in instance['Instances'][0]['Tags']: + for tag in instance['Instances'][0].get('Tags', []): if not (tag['Key']).startswith('aws'): if not instance_id: instance_id = tag['Value'] @@ -148,7 +148,7 @@ def main(): if instance['Instances'][0]['State']['Name'] != 'running': continue - if instance['Instances'][0]['KeyName'] is None: + if instance['Instances'][0].get('KeyName', None) is None: continue if instance['Instances'][0]['LaunchTime'] not in instances: @@ -192,8 +192,7 @@ def main(): image_label = image[ 'Images' ][0][ - 'ImageId'] if image[ - 'Images'][0] is not None else instance[ + 'ImageId'] if len(image['Images']) and image['Images'][0] is not None else instance[ 'Instances'][0]['ImageId'] sys.stderr.write( 'Can\'t lookup user for AMI \'' From e19b4cf8f5349bfc7bd0380e0e9b10bfeff8b626 Mon Sep 17 00:00:00 2001 From: Michael Leer Date: Wed, 28 Aug 2019 11:27:08 +0100 Subject: [PATCH 07/10] Update pythonpackage.yml --- .github/workflows/pythonpackage.yml | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/pythonpackage.yml diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml new file mode 100644 index 0000000..56212b3 --- /dev/null +++ b/.github/workflows/pythonpackage.yml @@ -0,0 +1,30 @@ +name: Python package + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + max-parallel: 4 + matrix: + python-version: [2.7, 3.5, 3.6, 3.7] + + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Lint with flake8 + run: | + pip install flake8 + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics From 7cab4ee6aa50872bf3667a5d19d5658d825180c4 Mon Sep 17 00:00:00 2001 From: Ryan Romanchuk Date: Tue, 10 Mar 2020 14:58:10 -0700 Subject: [PATCH 08/10] skip appending a tag value if it's not in the given filter --- aws-ssh-config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aws-ssh-config.py b/aws-ssh-config.py index 1515e06..ff83d7c 100755 --- a/aws-ssh-config.py +++ b/aws-ssh-config.py @@ -34,6 +34,8 @@ def generate_id(instance, tags_filter, region): if tags_filter is not None: for tag in tags_filter.split(','): for aws_tag in instance['Instances'][0].get('Tags', []): + if aws_tag['Key'] != tag: + continue value = aws_tag['Value'] if value: if not instance_id: From 9cba957a188d890ed49aa3c59394925c5cc8b217 Mon Sep 17 00:00:00 2001 From: Magnus Henoch Date: Tue, 11 May 2021 10:46:41 +0100 Subject: [PATCH 09/10] Sort tags by key Tag order is non-deterministic, so instances with the same tags set might end up not getting sequential names, e.g. 'foo-bar' and 'bar-foo' instead of 'foo-bar-1' and 'foo-bar-2'. --- aws-ssh-config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-ssh-config.py b/aws-ssh-config.py index ff83d7c..3ba0711 100755 --- a/aws-ssh-config.py +++ b/aws-ssh-config.py @@ -43,7 +43,7 @@ def generate_id(instance, tags_filter, region): else: instance_id += '-' + value else: - for tag in instance['Instances'][0].get('Tags', []): + for tag in sorted(instance['Instances'][0].get('Tags', []), key=lambda tag: tag['Key']): if not (tag['Key']).startswith('aws'): if not instance_id: instance_id = tag['Value'] From 9a3563b3eac116243b503d5c3ae03ac77b3a614a Mon Sep 17 00:00:00 2001 From: Michael Leer Date: Tue, 11 May 2021 12:23:45 +0100 Subject: [PATCH 10/10] Update pythonpackage.yml add newer versions of python --- .github/workflows/pythonpackage.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 56212b3..4e9b383 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -7,9 +7,9 @@ jobs: runs-on: ubuntu-latest strategy: - max-parallel: 4 + max-parallel: 6 matrix: - python-version: [2.7, 3.5, 3.6, 3.7] + python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v1