--- /dev/null
+#!/usr/bin/env python
+'''\
+Generate a JSON object containing the names of all the AWS Autoscaling
+Groups in an account and the IPs of the Instances within them, suitable
+for use as an Ansible inventory.
+'''
+
+import argparse
+import boto3
+import json
+import sys
+from multiprocessing.dummy import Pool as ThreadPool
+from functools import partial
+
+
+DEFAULT_REGIONS = ['us-east-1', 'us-west-2']
+HOSTVARS = {}
+
+
+def allASGInstances(asgc):
+ 'Return a tuple of a dict of each ASG name listing the instance IDs within it, and a list of all instance IDs.'
+ asgs = {}
+ instanceIds = []
+ args = {}
+ while True:
+ response = asgc.describe_auto_scaling_groups(**args)
+ for asg in response['AutoScalingGroups']:
+ asgs[asg['AutoScalingGroupName']] = [i['InstanceId'] for i in asg['Instances']]
+ instanceIds += asgs[asg['AutoScalingGroupName']]
+ if 'NextToken' not in response:
+ break
+ args = {'NextToken': response['NextToken']}
+ return (asgs, instanceIds)
+
+
+def allInstanceIPs(ec2c, InstanceIds=None, publicIPs=False):
+ 'Return a dict of each Instance ID with its addresses.'
+ instances = {}
+ args = {}
+ IPType = 'PublicIpAddress' if publicIPs else 'PrivateIpAddress'
+ if InstanceIds is not None:
+ args['InstanceIds'] = InstanceIds
+ while True:
+ response = ec2c.describe_instances(**args)
+ for resv in response['Reservations']:
+ for inst in resv['Instances']:
+ if IPType in inst:
+ instances[inst['InstanceId']] = inst[IPType]
+ if 'NextToken' not in response:
+ break
+ args = {'NextToken': response['NextToken']}
+ return instances
+
+
+def regionInventory(sessionArgs, publicIPs=False):
+ 'Return dict results for one region.'
+ session = boto3.session.Session(**sessionArgs)
+ asgc = session.client('autoscaling')
+ ec2c = session.client('ec2')
+
+ # get dict of ASG names and associated Instance Ids, plus list of all Instance Ids referenced by ASGs
+ (ASGs, AllInstanceIds) = allASGInstances(asgc)
+
+ # get list of instance IPs for all instance Ids used by ASGs
+ AllInstanceIPs = allInstanceIPs(ec2c, InstanceIds=AllInstanceIds, publicIPs=publicIPs)
+
+ # a group for every Instance Id
+ inventory = {iid:[AllInstanceIPs[iid]] for iid in AllInstanceIPs}
+
+ # add ASG dict, replacing ASG Instance Id with instance IP
+ inventory.update({asg:[AllInstanceIPs[iid] for iid in ASGs[asg]] for asg in ASGs})
+
+ return inventory
+
+
+def mergeDictOfLists(a, b):
+ 'There is likely a better way of doing this, but right now I have a headache.'
+ for key in b:
+ if key in a:
+ a[key] += b[key]
+ else:
+ a[key] = b[key]
+ return a
+
+
+parser = argparse.ArgumentParser(description='dynamic Ansible inventory from AWS Autoscaling Groups')
+parser.add_argument('--public', action='store_true', help='inventory public IPs (default: private IPs)')
+parser.add_argument('--profile', metavar='PROFILE', dest='profile_name', help='AWS Profile (default: current IAM Role)')
+parser.add_argument('--regions', metavar='REGION', nargs='+', default=DEFAULT_REGIONS, help='AWS Regions (default: %(default)s)')
+parser.add_argument('--list', action='store_true')
+parser.add_argument('--host', nargs=1)
+args = parser.parse_args()
+
+if args.host:
+ print(json.dumps(HOSTVARS))
+ sys.exit()
+
+# create sessionArgs for each region
+regionArgs = [{'region_name': region} for region in args.regions]
+if args.profile_name:
+ for arg in regionArgs:
+ arg.update({'profile_name': args.profile_name})
+
+# pin the non-variant option
+invf = partial(regionInventory, publicIPs=args.public)
+
+# query regions concurrently
+pool = ThreadPool(len(regionArgs))
+regionInventories = pool.map(invf, regionArgs)
+pool.close()
+pool.join()
+
+# combine regions
+inventory = reduce(mergeDictOfLists, regionInventories, {})
+inventory['_meta'] = {'hostvars': HOSTVARS}
+
+print(json.dumps(inventory))