def list_tokens(output): '''List tokens''' data = get_tokens() rows = [] for key, val in sorted(data.items()): access_token = val.get('access_token') rows.append({'name': key, 'access_token': access_token, 'scope': val.get('scope'), 'creation_time': val.get('creation_time'), 'expires_in': format_expires(val)}) with OutputFormat(output): print_table('name access_token scope creation_time expires_in'.split(), rows, titles={'creation_time': 'Created'}, max_column_widths={'access_token': 36})
def scm_source(config, team, artifact, tag, url, output): '''Show SCM source information such as GIT revision''' url = set_pierone_url(config, url) api = PierOne(url) token = get_token() tags = get_tags(url, team, artifact, token) if not tags: raise click.UsageError('Artifact or Team does not exist! ' 'Please double check for spelling mistakes.') if not tag: tag = [t['name'] for t in tags] rows = [] for t in tag: image = DockerImage(url, team, artifact, t) try: scm_source = api.get_scm_source(image) row = scm_source except ArtifactNotFound: row = {} row['tag'] = t matching_tag = [d for d in tags if d['name'] == t] row['created_by'] = ''.join([d['created_by'] for d in matching_tag]) if matching_tag: row['created_time'] = parse_time(''.join( [d['created'] for d in matching_tag])) rows.append(row) rows.sort(key=lambda row: (row['tag'], row.get('created_time'))) with OutputFormat(output): print_table( [ 'tag', 'author', 'url', 'revision', 'status', 'created_time', 'created_by' ], rows, titles={ 'tag': 'Tag', 'created_by': 'By', 'created_time': 'Created', 'url': 'URL', 'revision': 'Revision', 'status': 'Status' }, max_column_widths={'revision': 10})
def list_profiles(obj, output): '''List profiles''' if obj['config']: rows = [] for name, config in obj['config'].items(): row = { 'name': name, 'role': get_role_label(config.get('saml_role')), 'url': config.get('saml_identity_provider_url'), 'user': config.get('saml_user')} rows.append(row) rows.sort(key=lambda r: r['name']) with OutputFormat(output): print_table(sorted(rows[0].keys()), rows)
def scm_source(config, team, artifact, tag, output): '''Show SCM source information such as GIT revision''' token = get_token() tags = get_tags(config.get('url'), team, artifact, token['access_token']) if not tag: tag = [t['name'] for t in tags] rows = [] for t in tag: row = request( config.get('url'), '/teams/{}/artifacts/{}/tags/{}/scm-source'.format( team, artifact, t), token['access_token']).json() if not row: row = {} row['tag'] = t matching_tag = [d for d in tags if d['name'] == t] row['created_by'] = ''.join([d['created_by'] for d in matching_tag]) if matching_tag: row['created_time'] = datetime.datetime.strptime( ''.join([d['created'] for d in matching_tag]), '%Y-%m-%dT%H:%M:%S.%f%z').timestamp() rows.append(row) rows.sort(key=lambda row: (row['tag'], row.get('created_time'))) with OutputFormat(output): print_table( [ 'tag', 'author', 'url', 'revision', 'status', 'created_time', 'created_by' ], rows, titles={ 'tag': 'Tag', 'created_by': 'By', 'created_time': 'Created', 'url': 'URL', 'revision': 'Revision', 'status': 'Status' }, max_column_widths={'revision': 10})
def cves(config, team, artifact, tag, url, output): '''List all CVE's found by Clair service for a specific artifact tag''' set_pierone_url(config, url) rows = [] token = get_token() for artifact_tag in get_tags(config.get('url'), team, artifact, token): if artifact_tag['name'] == tag: installed_software = get_clair_features( artifact_tag.get('clair_details'), token) for software_pkg in installed_software: for cve in software_pkg.get('Vulnerabilities', []): rows.append({ 'cve': cve['Name'], 'severity': cve['Severity'].upper(), 'affected_feature': '{}:{}'.format(software_pkg['Name'], software_pkg['Version']), 'fixing_feature': cve.get('FixedBy') and '{}:{}'.format( software_pkg['Name'], cve['FixedBy']), 'link': cve['Link'], }) severity_rating = [ 'CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'NEGLIGIBLE', 'UNKNOWN', 'PENDING' ] rows.sort(key=lambda row: severity_rating.index(row['severity'])) with OutputFormat(output): titles = { 'cve': 'CVE', 'severity': 'Severity', 'affected_feature': 'Affected Feature', 'fixing_feature': 'Fixing Feature', 'link': 'Link' } print_table( ['cve', 'severity', 'affected_feature', 'fixing_feature', 'link'], rows, titles=titles, styles=CVE_STYLES)
def list_access_requests(obj, user, odd_host, status, limit, offset, output): '''List access requests filtered by user, host and status''' config = load_config(obj) if user == '*': user = None if odd_host == '*': odd_host = None elif odd_host == 'MY-ODD-HOST': odd_host = config.get('odd_host') token = zign.api.get_existing_token('piu') if not token: raise click.UsageError('No valid OAuth token named "piu" found.') access_token = token.get('access_token') params = { 'username': user, 'hostname': odd_host, 'status': status, 'limit': limit, 'offset': offset } r = requests.get( config.get('even_url').rstrip('/') + '/access-requests', params=params, headers={'Authorization': 'Bearer {}'.format(access_token)}) r.raise_for_status() rows = [] for req in r.json(): req['created_time'] = datetime.datetime.strptime( req['created'], '%Y-%m-%dT%H:%M:%S.%f%z').timestamp() rows.append(req) rows.sort(key=lambda x: x['created_time']) with OutputFormat(output): print_table( 'username hostname remote_host reason lifetime_minutes status status_reason created_time' .split(), rows, styles=STYLES, titles=TITLES, max_column_widths=MAX_COLUMN_WIDTHS)
def tags(config, team: str, artifact, url, output, limit): '''List all tags for a given team''' set_pierone_url(config, url) token = get_token() if limit is None: # show 20 rows if artifact was given, else show only 3 limit = 20 if artifact else 3 if not artifact: artifact = get_artifacts(config.get('url'), team, token) if not artifact: raise click.UsageError('The Team you are looking for does not exist or ' 'we could not find any artifacts registered in Pierone! ' 'Please double check for spelling mistakes.') registry = config.get('url') if registry.startswith('https://'): registry = registry[8:] slice_from = - limit rows = [] for art in artifact: image = DockerImage(registry=registry, team=team, artifact=art, tag=None) try: tags = get_image_tags(image, token) except Unauthorized as e: raise click.ClickException(str(e)) else: if not tags: raise click.UsageError('Artifact or Team does not exist! ' 'Please double check for spelling mistakes.') rows.extend(tags[slice_from:]) # sorts are guaranteed to be stable, i.e. tags will be sorted by time (as returned from REST service) rows.sort(key=lambda row: (row['team'], row['artifact'])) with OutputFormat(output): titles = { 'created_time': 'Created', 'created_by': 'By' } print_table(['team', 'artifact', 'tag', 'created_time', 'created_by'], rows, titles=titles)
def list_stacks(region, stack_ref, all, output, w, watch): '''List Cloud Formation stacks''' region = get_region(region) check_credentials(region) stack_refs = get_stack_refs(stack_ref) for _ in watching(w, watch): rows = [] for stack in get_stacks(stack_refs, region, all=all): rows.append({'stack_name': stack.name, 'version': stack.version, 'status': stack.stack_status, 'creation_time': calendar.timegm(stack.creation_time.timetuple()), 'description': stack.template_description}) rows.sort(key=lambda x: (x['stack_name'], x['version'])) with OutputFormat(output): print_table('stack_name version status creation_time description'.split(), rows, styles=STYLES, titles=TITLES)
def instances(stack_ref, all, terminated, region, output, w, watch): '''List the stack's EC2 instances''' stack_refs = get_stack_refs(stack_ref) region = get_region(region) check_credentials(region) conn = boto.ec2.connect_to_region(region) elb = boto.ec2.elb.connect_to_region(region) if all: filters = None else: # filter out instances not part of any stack filters = {'tag-key': 'aws:cloudformation:stack-name'} for _ in watching(w, watch): rows = [] for instance in conn.get_only_instances(filters=filters): cf_stack_name = instance.tags.get('aws:cloudformation:stack-name') stack_name = instance.tags.get('StackName') stack_version = instance.tags.get('StackVersion') if not stack_refs or matches_any(cf_stack_name, stack_refs): instance_health = get_instance_health(elb, cf_stack_name) if instance.state.upper() != 'TERMINATED' or terminated: rows.append({'stack_name': stack_name or '', 'version': stack_version or '', 'resource_id': instance.tags.get('aws:cloudformation:logical-id'), 'instance_id': instance.id, 'public_ip': instance.ip_address, 'private_ip': instance.private_ip_address, 'state': instance.state.upper().replace('-', '_'), 'lb_status': instance_health.get(instance.id), 'launch_time': parse_time(instance.launch_time)}) rows.sort(key=lambda r: (r['stack_name'], r['version'], r['instance_id'])) with OutputFormat(output): print_table(('stack_name version resource_id instance_id public_ip ' + 'private_ip state lb_status launch_time').split(), rows, styles=STYLES, titles=TITLES)
def list_access_requests(config_file, user, odd_host, status, limit, offset, output, region): '''List access requests filtered by user, host and status''' config = load_config(config_file) if user == '*': user = None if odd_host == '*': odd_host = None elif odd_host is None: odd_host = piu.utils.find_odd_host(region) or config.get('odd_host') access_token = zign.api.get_token('piu', ['piu']) params = { 'username': user, 'hostname': odd_host, 'status': status, 'limit': limit, 'offset': offset } r = requests.get( config.get('even_url').rstrip('/') + '/access-requests', params=params, headers={'Authorization': 'Bearer {}'.format(access_token)}) r.raise_for_status() rows = [] for req in r.json(): req['created_time'] = parse_time(req['created']) rows.append(req) rows.sort(key=lambda x: x['created_time']) with OutputFormat(output): print_table( 'username hostname remote_host reason lifetime_minutes status status_reason created_time' .split(), rows, styles=STYLES, titles=TITLES, max_column_widths=MAX_COLUMN_WIDTHS)
def domains(stack_ref, region, output, w, watch): '''List the stack's Route53 domains''' stack_refs = get_stack_refs(stack_ref) region = get_region(region) check_credentials(region) cf = boto.cloudformation.connect_to_region(region) route53 = boto.route53.connect_to_region(region) records_by_name = {} for _ in watching(w, watch): rows = [] for stack in get_stacks(stack_refs, region): if stack.stack_status == 'ROLLBACK_COMPLETE': # performance optimization: do not call EC2 API for "dead" stacks continue resources = cf.describe_stack_resources(stack.stack_id) for res in resources: if res.resource_type == 'AWS::Route53::RecordSet': name = res.physical_resource_id if name not in records_by_name: zone_name = name.split('.', 1)[1] zone = route53.get_zone(zone_name) for rec in zone.get_records(): records_by_name[(rec.name.rstrip('.'), rec.identifier)] = rec record = records_by_name.get((name, stack.stack_name)) or records_by_name.get((name, None)) rows.append({'stack_name': stack.name, 'version': stack.version, 'resource_id': res.logical_resource_id, 'domain': res.physical_resource_id, 'weight': record.weight if record else None, 'type': record.type if record else None, 'value': ','.join(record.resource_records) if record else None, 'create_time': calendar.timegm(res.timestamp.timetuple())}) with OutputFormat(output): print_table('stack_name version resource_id domain weight type value create_time'.split(), rows, styles=STYLES, titles=TITLES)
def types(config, output): '''List violation types''' url = config.get('url') if not url: raise click.ClickException('Missing configuration URL. Please run "stups configure".') token = get_token() r = request(url, '/api/violation-types', token) r.raise_for_status() data = r.json() rows = [] for row in data: row['created_time'] = parse_time(row['created']) rows.append(row) rows.sort(key=lambda r: r['id']) with OutputFormat(output): print_table(['id', 'violation_severity', 'created_time', 'help_text'], rows, titles={'created_time': 'Created', 'violation_severity': 'Sev.'})
def image(config, image, url, output): '''List tags that point to this image''' set_pierone_url(config, url) token = get_token() try: resp = request(config.get('url'), '/tags/{}'.format(image), token) except requests.HTTPError as error: status_code = error.response.status_code if status_code == 404: click.echo('Image {} not found'.format(image)) elif status_code == 412: click.echo('Prefix {} matches more than one image.'.format(image)) else: raise error return tags = resp.json() with OutputFormat(output): print_table(['team', 'artifact', 'name'], tags, titles={'name': 'Tag', 'artifact': 'Artifact', 'team': 'Team'})
def issues(config, output): '''List open issues''' token = config.get('github_access_token') repositories = get_repositories() rows = [] for issue in get_my_issues(token): if not issue.get('pull_request'): repo = repositories.get(issue['repository']['url']) if repo: issue['repository'] = repo['full_name'] issue['created_time'] = parse_time(issue['created_at']) issue['created_by'] = issue['user']['login'] issue['labels'] = ', '.join( [l['name'] for l in issue['labels']]) rows.append(issue) rows.sort(key=lambda x: (x['repository'], x['number'])) with OutputFormat(output): print_table([ 'repository', 'number', 'title', 'labels', 'created_time', 'created_by' ], rows)
def repositories(config, show_issues, output): '''List repositories''' token = config.get('github_access_token') repositories = get_repositories() if show_issues: for issue in get_my_issues(token): repo = repositories.get(issue['repository']['url']) if repo: repo['open_issues'] = repo.get('open_issues', 0) + 1 if issue.get('pull_request'): repo['open_pull_requests'] = repo.get( 'open_pull_requests', 0) + 1 rows = [] for url, repo in sorted(repositories.items()): rows.append(repo) with OutputFormat(output): columns = ['full_name', 'stargazers_count', 'forks_count'] if show_issues: columns += ['open_issues', 'open_pull_requests'] print_table(columns, rows)
def if_vpc_empty(account: AccountData, region: str): ec2 = account.session.resource('ec2', region) ec2c = account.session.client('ec2', region) def instance_state(instance_id): if instance_id: return ec2.Instance(id=instance_id).state.get('Name') def if_stups_tool(ni: dict): instance_id = ni.get('Attachment', {}).get('InstanceId') if instance_id: instance = ec2.Instance(id=instance_id) availability_zones = get_az_names(account.session, region) stups_names = ('Odd (SSH Bastion Host)',) + tuple(['NAT {}'.format(x) for x in availability_zones]) if get_tag(instance.tags, 'Name') in stups_names: return True if get_tag(instance.tags, 'aws:cloudformation:logical-id') == 'OddServerInstance': return True allocation_id = ni.get('Association', {}).get('AllocationId') if allocation_id: for gateway in ec2c.describe_nat_gateways()['NatGateways']: if gateway.get('NatGatewayAddresses', {})[0].get('AllocationId') == allocation_id: return True return False account_is_free = True rows = [] for ni in ec2c.describe_network_interfaces()['NetworkInterfaces']: can_remove = if_stups_tool(ni) if not can_remove: account_is_free = False # print(' '.join([str(ni), str(ni.groups), str(ni.attachment), ni.description])) rows.append({'network_id': ni.get('NetworkInterfaceId'), 'group_name': ', '.join([group['GroupName'] for group in ni.get('Groups')]), 'description': ni.get('Description'), 'status': ni.get('Attachment', {}).get('Status'), 'instance_owner_id': ni.get('Attachment', {}).get('InstanceOwnerId'), 'instance_id': ni.get('Attachment', {}).get('InstanceId', ''), 'state': instance_state(ni.get('Attachment', {}).get('InstanceId')), 'allocation_id': ni.get('Association', {}).get('AllocationId'), 'account_name': account.name, 'can_remove': '✔' if can_remove else '✘' }) rows.sort(key=lambda x: (x['account_name'], x['group_name'], x['instance_id'])) with OutputFormat('text'): print_table(''' can_remove account_name network_id allocation_id description group_name status instance_owner_id instance_id state '''.split(), rows, styles={ 'running': {'fg': 'green'}, 'stopped': {'fg': 'red', 'bold': True}, '✔': {'bg': 'green'}, '✘': {'bg': 'red', 'bold': True}, }) return account_is_free
def inspect_contents(config, team, artifact, tag, url, output, limit): '''List image contents (files in tar layers)''' set_pierone_url(config, url) token = get_token() tags = get_tags(config.get('url'), team, artifact, token) if not tag: tag = [t['name'] for t in tags] CHUNK_SIZE = 8192 TYPES = {b'5': 'D', b'0': ' '} rows = [] for t in tag: row = request(config.get('url'), '/v2/{}/{}/manifests/{}'.format(team, artifact, t), token).json() if row.get('layers'): layers = reversed([lay.get('digest') for lay in row.get('layers')]) else: layers = [lay.get('blobSum') for lay in row.get('fsLayers')] if layers: found = 0 for i, layer in enumerate(layers): layer_id = layer if layer_id: response = request( config.get('url'), '/v2/{}/{}/blobs/{}'.format(team, artifact, layer_id), token) with tempfile.NamedTemporaryFile(prefix='tmp-layer-', suffix='.tar') as fd: for chunk in response.iter_content(CHUNK_SIZE): fd.write(chunk) fd.flush() with tarfile.open(fd.name) as archive: has_member = False for member in archive.getmembers(): rows.append({ 'layer_index': i, 'layer_id': layer_id, 'type': TYPES.get(member.type), 'mode': oct(member.mode)[-4:], 'name': member.name, 'size': member.size, 'created_time': member.mtime }) has_member = True if has_member: found += 1 if found >= limit: break rows.sort(key=lambda row: (row['layer_index'], row['name'])) with OutputFormat(output): print_table([ 'layer_index', 'layer_id', 'mode', 'name', 'size', 'created_time' ], rows, titles={ 'created_time': 'Created', 'layer_index': 'Idx' }, max_column_widths={'layer_id': 16})
def domains(stack_ref, region, output, w, watch): '''List the stack's Route53 domains''' stack_refs = get_stack_refs(stack_ref) region = get_region(region) check_credentials(region) cf = boto3.resource('cloudformation', region) records_by_name = {} for _ in watching(w, watch): rows = [] for stack in get_stacks(stack_refs, region): if stack.StackStatus == 'ROLLBACK_COMPLETE': # performance optimization: do not call EC2 API for "dead" stacks continue for res in cf.Stack(stack.StackId).resource_summaries.all(): if res.resource_type == 'AWS::Route53::RecordSet': name = res.physical_resource_id if name not in records_by_name: zone_name = name.split('.', 1)[1] for rec in get_records(zone_name): records_by_name[(rec['Name'].rstrip('.'), rec.get('SetIdentifier'))] = rec record = records_by_name.get( (name, stack.StackName)) or records_by_name.get( (name, None)) row = { 'stack_name': stack.name, 'version': stack.version, 'resource_id': res.logical_id, 'domain': res.physical_resource_id, 'weight': None, 'type': None, 'value': None, 'create_time': calendar.timegm(res.last_updated_timestamp.timetuple()) } if record: row.update({ 'weight': str(record.get('Weight', '')), 'type': record.get('Type'), 'value': ','.join([ r['Value'] for r in record.get('ResourceRecords') ]) }) rows.append(row) with OutputFormat(output): print_table( 'stack_name version resource_id domain weight type value create_time' .split(), rows, styles=STYLES, titles=TITLES)
def main_list(local): ssh_agent_setup.setup() repo_path = pygit2.discover_repository(os.getcwd()) repo = pygit2.Repository(repo_path) prune = False # havn't got the cred callback working if prune: with Action("Pruning old remotes...") as action: for remote in repo.remotes: remote.fetch(prune=pygit2.GIT_FETCH_PRUNE, callbacks=SSHAgentCallbacks()) action.progress() # end # end print(repo.describe()) info = {} fill_branch_info(info, repo.branches, islocal=True) fill_branch_info(info, repo.branches, islocal=False) fill_merged_info(info, isremote=False) fill_merged_info(info, isremote=True) rows = [] for name in info.keys(): data = info[name] if local and not data.local: continue rows.append({ "flags": "{}{}{}{}.".format( "*" if data.head else " ", "M" if data.up2date or data.merged else ".", "P" if data.pushed else ".", "D" if data.gone else ".", ), "local": "local" if data.local else ".....", "remote": "remote" if data.remote else "......", "name": name, "log": first_line(repo[data.target].message), }) # end max_width, _ = os.get_terminal_size(0) used = 5 + 1 + 5 + 1 + 6 + 1 + 30 + 1 remaining = max_width - 1 - used WIDTHS = { "flags": 5, "local": 5, "remote": 6, "name": 30, "log": remaining } with OutputFormat("text"): print_table(["flags", "local", "remote", "name", "log"], rows, max_column_widths=WIDTHS) click.secho(" " * (max_width - 1), fg="black", bg="white") print("* = current checkout, M = branch is merged into master") print( "P = local has been pushed into it's remote, D = No matching upstream branch for local" )
def instances(stack_ref, all, terminated, docker_image, piu, odd_host, region, output, w, watch): '''List the stack's EC2 instances''' stack_refs = get_stack_refs(stack_ref) region = get_region(region) check_credentials(region) ec2 = boto3.resource('ec2', region) elb = boto3.client('elb', region) if all: filters = [] else: # filter out instances not part of any stack filters = [{ 'Name': 'tag-key', 'Values': ['aws:cloudformation:stack-name'] }] opt_docker_column = ' docker_source' if docker_image else '' for _ in watching(w, watch): rows = [] for instance in ec2.instances.filter(Filters=filters): cf_stack_name = get_tag(instance.tags, 'aws:cloudformation:stack-name') stack_name = get_tag(instance.tags, 'StackName') stack_version = get_tag(instance.tags, 'StackVersion') if not stack_refs or matches_any(cf_stack_name, stack_refs): instance_health = get_instance_health(elb, cf_stack_name) if instance.state['Name'].upper( ) != 'TERMINATED' or terminated: docker_source = get_instance_docker_image_source( instance) if docker_image else '' rows.append({ 'stack_name': stack_name or '', 'version': stack_version or '', 'resource_id': get_tag(instance.tags, 'aws:cloudformation:logical-id'), 'instance_id': instance.id, 'public_ip': instance.public_ip_address, 'private_ip': instance.private_ip_address, 'state': instance.state['Name'].upper().replace('-', '_'), 'lb_status': instance_health.get(instance.id), 'docker_source': docker_source, 'launch_time': instance.launch_time.timestamp() }) rows.sort( key=lambda r: (r['stack_name'], r['version'], r['instance_id'])) with OutputFormat(output): print_table( ('stack_name version resource_id instance_id public_ip ' + 'private_ip state lb_status{} launch_time'.format( opt_docker_column)).split(), rows, styles=STYLES, titles=TITLES) if piu is not None: for row in rows: if row['private_ip'] is not None: call([ 'piu', 'request-access', row['private_ip'], '{} via senza'.format(piu), '-O', odd_host ])
def status(stack_ref, region, output, w, watch): '''Show stack status information''' stack_refs = get_stack_refs(stack_ref) region = get_region(region) check_credentials(region) ec2 = boto3.resource('ec2', region) elb = boto3.client('elb', region) cf = boto3.resource('cloudformation', region) for _ in watching(w, watch): rows = [] for stack in sorted(get_stacks(stack_refs, region)): instance_health = get_instance_health(elb, stack.StackName) main_dns_resolves = False http_status = None for res in cf.Stack(stack.StackId).resource_summaries.all(): if res.resource_type == 'AWS::Route53::RecordSet': name = res.physical_resource_id if not name: # physical resource ID will be empty during stack creation continue if 'version' in res.logical_id.lower(): try: requests.get('https://{}/'.format(name), timeout=2) http_status = 'OK' except: http_status = 'ERROR' else: try: answers = dns.resolver.query(name, 'CNAME') except: answers = [] for answer in answers: if answer.target.to_text().startswith('{}-'.format( stack.StackName)): main_dns_resolves = True instances = list( ec2.instances.filter(Filters=[{ 'Name': 'tag:aws:cloudformation:stack-id', 'Values': [stack.StackId] }])) rows.append({ 'stack_name': stack.name, 'version': stack.version, 'status': stack.StackStatus, 'total_instances': len(instances), 'running_instances': len([i for i in instances if i.state['Name'] == 'running']), 'healthy_instances': len([i for i in instance_health.values() if i == 'IN_SERVICE']), 'lb_status': ','.join(set(instance_health.values())), 'main_dns': main_dns_resolves, 'http_status': http_status }) with OutputFormat(output): print_table(( 'stack_name version status total_instances running_instances healthy_instances ' + 'lb_status http_status main_dns').split(), rows, styles=STYLES, titles=TITLES)
def images(stack_ref, region, output, hide_older_than, show_instances): '''Show all used AMIs and available Taupage AMIs''' stack_refs = get_stack_refs(stack_ref) region = get_region(region) check_credentials(region) ec2 = boto3.resource('ec2', region) instances_by_image = collections.defaultdict(list) for inst in ec2.instances.all(): if inst.state['Name'] == 'terminated': # do not count TERMINATED EC2 instances continue stack_name = get_tag(inst.tags, 'aws:cloudformation:stack-name') if not stack_refs or matches_any(stack_name, stack_refs): instances_by_image[inst.image_id].append(inst) images = {} for image in ec2.images.filter(ImageIds=list(instances_by_image.keys())): images[image.id] = image if not stack_refs: filters = [{ 'Name': 'name', 'Values': ['*Taupage-*'] }, { 'Name': 'state', 'Values': ['available'] }] for image in ec2.images.filter(Filters=filters): images[image.id] = image rows = [] cutoff = datetime.datetime.now() - datetime.timedelta(days=hide_older_than) for image in images.values(): row = image.meta.data.copy() creation_time = parse_time(image.creation_date) row['creation_time'] = creation_time row['instances'] = ', '.join( sorted(i.id for i in instances_by_image[image.id])) row['total_instances'] = len(instances_by_image[image.id]) stacks = set() for instance in instances_by_image[image.id]: stack_name = get_tag(instance.tags, 'aws:cloudformation:stack-name') # EC2 instance might not be part of a CF stack if stack_name: stacks.add(stack_name) row['stacks'] = ', '.join(sorted(stacks)) # if creation_time > cutoff.timestamp() or row['total_instances']: rows.append(row) rows.sort(key=lambda x: x.get('Name')) with OutputFormat(output): cols = 'ImageId Name OwnerId Description stacks total_instances creation_time' if show_instances: cols = cols.replace('total_instances', 'instances') print_table(cols.split(), rows, titles=TITLES, max_column_widths=MAX_COLUMN_WIDTHS)