forked from krux/aws-analysis-tools
-
Notifications
You must be signed in to change notification settings - Fork 0
/
search_ec2_tags.py
executable file
·180 lines (146 loc) · 6.46 KB
/
search_ec2_tags.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#!/usr/bin/env kaws-python
### -*- coding: utf-8 -*-
###
### (c) 2014 Krux Digital, Inc.
### Author: Jeff Pierce <jeff.pierce@krux.com>
###
"""
Searches EC2 tags and returns a list of servers that match the query terms.
"""
##########################
### Standard Libraries ###
##########################
from __future__ import absolute_import
import sys
######################
### Krux Libraries ###
######################
import krux.cli
#############################
### Third Party Libraries ###
#############################
import boto.ec2
def parse_query(query_to_parse):
"""
Converts a comma or space-separated string of query terms into a list to use
as filters. The use of positional arguments on the CLI provides lists (which
we join and resplit to avoid any formatting issues), while strings passed
from pssh.py just get split since those should never come over as a list.
"""
if isinstance(query_to_parse, list):
_query = ','.join(query_to_parse)
else:
_query = query_to_parse.replace(' ',',')
split_query = _query.split(',')
### Pick up passed --region query from pssh.py
parsed_query = [x for x in split_query if not x.startswith('--region=')]
region_query = [x for x in split_query if x.startswith('--region')]
parsed_regions = ','.join([x.split('=')[1] for x in region_query])
return parsed_query, parsed_regions
def search_tags(query_terms,passed_regions=None):
"""
Searches EC2 instances based on parsed search terms returned by parse_query()
Skips GovCloud and China regions, and can be further filtered by region.
"""
regions = boto.ec2.regions()
filters = []
query = {}
inst_names = []
### Set filters
if passed_regions is not None and passed_regions:
### lambda: if we've specified a region, only pick regions that
### match the provided regions
if isinstance(passed_regions, list):
filters.extend([lambda r: r.name in passed_regions])
else:
filters.extend([lambda r: r.name in passed_regions.split(',')])
### lambdas: remove govcloud and China to improve search speed since
### we don't have access to them.
filters.extend([
lambda r: '-gov-' not in r.name,
lambda r: not r.name.startswith('cn-')
])
### Filter out unneeded regions from our regions list.
for some_filter in filters:
regions = filter(some_filter, regions)
### Populate query dictionary to send to AWS
for search_param in query_terms:
### Splits the query at : if it's a tag:value search parameter and adds
### those to the query dictionary (or appends the value to an existing
### tag), otherwise it creates a key of tag-value (if it doesn't already
### exist) with the search term as the value and performs a value only
### search.
###
### Examples:
### Name:period searches for tag Name and value *period*
### s_periodic searches for value matching *s_periodic*.
### Name:period*,Name:webapp* searches for tag Name with values
### *period* and *webapp* as an OR search.
### Check to see if we're searching for an s_class where the split
### would cause a problem
if search_param.startswith('s_') and not search_param.startswith('s_classes'):
search_term = [search_param]
else:
search_term = search_param.split(':',1)
### If the query contains just a value, like with a query for s_periodic,
### add it to the value search, len(search_term) will be 1 and gets added
### to tag value. We always search to ensure that the key for the dictionary
### hasn't already been made before adding it, otherwise, we append the
### value to the already existing key. This goes for a tag:value query
### as well.
if len(search_term) == 1:
if "tag-value" not in query:
query.update({"tag-value": ['*' + search_term[0] + '*']})
else:
query["tag-value"] = query.get('tag-value') + ['*' + search_term[0] + '*']
### But, if the query is in tag:value format (like s_classes:s_periodic),
### len(search_term) will be 2, and if the query was s_classes:s_periodic,
### search_term would be [ 's_classes', 's_periodic' ], so we assign
### search_term[0] to the tag variable, and search_term[1] to the val
### variable.
else:
tag, val = search_term
if 'tag:%s' % tag not in query:
query.update({"tag:%s" % tag: ['*' + val + '*']})
else:
query["tag:%s" % tag] = query.get("tag:%s" % tag) + ['*' + val + '*']
### Search each region for matching tags/values and return them as a list.
for region in regions:
ec2 = region.connect()
for res in ec2.get_all_instances(filters=query):
instance = res.instances[0]
inst_names.append(instance.tags.get('Name'))
return inst_names
class Application(krux.cli.Application):
def __init__(self):
### Call superclass to get krux-stdlib
super(Application, self).__init__(name = 'search-ec2-tags')
def add_cli_arguments(self, parser):
group = krux.cli.get_group(parser, self.name)
group.add_argument(
'--regions',
nargs = 1,
help = "Defines the EC2 region to search under. Provide a comma separated string to search multiple regions.",
default = False
)
parser.add_argument(
'query',
nargs = "*",
default = None,
help = "Defines the search terms to use. Terms can be separated by a comma or a space, but not both."
)
def main():
app = Application()
### Since we can't require positional arguments using the required flag
### when defining the add_argument for query, we check to see if the length
### of apps.args.query (which is a list) is greater than 0. If it is,
### proceed normally, otherwise, print a simple usage statement and exit
### with status 1.
if len(app.args.query) > 0:
parsed_query, regions = parse_query(app.args.query)
print "Matched the following hosts: " + ', '.join(search_tags(parsed_query, app.args.regions))
else:
print 'search-ec2-tags.py requires a search term. Please run it with one.'
sys.exit(1)
if __name__ == '__main__':
main()