awsquery 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- awsquery/__init__.py +7 -0
- awsquery/cli.py +312 -0
- awsquery/config.py +61 -0
- awsquery/core.py +698 -0
- awsquery/default_filters.yaml +76 -0
- awsquery/filters.py +308 -0
- awsquery/formatters.py +372 -0
- awsquery/policy.json +2521 -0
- awsquery/security.py +95 -0
- awsquery/utils.py +115 -0
- awsquery-0.1.0.dist-info/METADATA +447 -0
- awsquery-0.1.0.dist-info/RECORD +16 -0
- awsquery-0.1.0.dist-info/WHEEL +5 -0
- awsquery-0.1.0.dist-info/entry_points.txt +2 -0
- awsquery-0.1.0.dist-info/licenses/LICENSE +21 -0
- awsquery-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Default column filters for AWS service actions
|
|
2
|
+
# Phase 3A: Core Services (Priority 1)
|
|
3
|
+
|
|
4
|
+
ec2:
|
|
5
|
+
describe_instances:
|
|
6
|
+
columns:
|
|
7
|
+
- "Tags.Name"
|
|
8
|
+
- "InstanceId"
|
|
9
|
+
- "InstanceType"
|
|
10
|
+
- "State.Name"
|
|
11
|
+
- "PublicIpAddress"
|
|
12
|
+
- "PrivateIpAddress"
|
|
13
|
+
description: "Default columns for EC2 instance listing"
|
|
14
|
+
|
|
15
|
+
describe_security_groups:
|
|
16
|
+
columns:
|
|
17
|
+
- "GroupName"
|
|
18
|
+
- "GroupId"
|
|
19
|
+
- "Description"
|
|
20
|
+
- "VpcId"
|
|
21
|
+
description: "Default columns for security group listing"
|
|
22
|
+
|
|
23
|
+
describe_volumes:
|
|
24
|
+
columns:
|
|
25
|
+
- "VolumeId"
|
|
26
|
+
- "VolumeType"
|
|
27
|
+
- "Size"
|
|
28
|
+
- "State"
|
|
29
|
+
- "AvailabilityZone"
|
|
30
|
+
- "Tags.Name"
|
|
31
|
+
description: "Default columns for EBS volume listing"
|
|
32
|
+
|
|
33
|
+
s3:
|
|
34
|
+
list_buckets:
|
|
35
|
+
columns:
|
|
36
|
+
- "Name"
|
|
37
|
+
- "CreationDate"
|
|
38
|
+
description: "Default columns for S3 bucket listing"
|
|
39
|
+
|
|
40
|
+
list_objects_v2:
|
|
41
|
+
columns:
|
|
42
|
+
- "Key"
|
|
43
|
+
- "LastModified"
|
|
44
|
+
- "Size"
|
|
45
|
+
- "StorageClass"
|
|
46
|
+
description: "Default columns for S3 object listing"
|
|
47
|
+
|
|
48
|
+
lambda:
|
|
49
|
+
list_functions:
|
|
50
|
+
columns:
|
|
51
|
+
- "FunctionName"
|
|
52
|
+
- "Runtime"
|
|
53
|
+
- "LastModified"
|
|
54
|
+
- "CodeSize"
|
|
55
|
+
- "Description"
|
|
56
|
+
description: "Default columns for Lambda function listing"
|
|
57
|
+
|
|
58
|
+
rds:
|
|
59
|
+
describe_db_instances:
|
|
60
|
+
columns:
|
|
61
|
+
- "DBInstanceIdentifier"
|
|
62
|
+
- "DBInstanceClass"
|
|
63
|
+
- "Engine"
|
|
64
|
+
- "DBInstanceStatus"
|
|
65
|
+
- "AllocatedStorage"
|
|
66
|
+
- "AvailabilityZone"
|
|
67
|
+
description: "Default columns for RDS instance listing"
|
|
68
|
+
|
|
69
|
+
cloudformation:
|
|
70
|
+
describe_stacks:
|
|
71
|
+
columns:
|
|
72
|
+
- "StackName"
|
|
73
|
+
- "StackStatus"
|
|
74
|
+
- "CreationTime"
|
|
75
|
+
- "LastUpdated"
|
|
76
|
+
description: "Default columns for CloudFormation stack listing"
|
awsquery/filters.py
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"""Filtering logic for AWS Query Tool."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from .formatters import convert_parameter_name, flatten_dict_keys, transform_tags_structure
|
|
6
|
+
from .utils import debug_print, simplify_key
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def filter_resources(resources, value_filters):
|
|
10
|
+
"""Filter resources by value filters (ALL must match)"""
|
|
11
|
+
if not value_filters:
|
|
12
|
+
return resources
|
|
13
|
+
|
|
14
|
+
for filter_text in value_filters:
|
|
15
|
+
debug_print(f"Applying value filter: {filter_text}")
|
|
16
|
+
|
|
17
|
+
# Apply tag transformation before filtering
|
|
18
|
+
transformed_resources = []
|
|
19
|
+
for resource in resources:
|
|
20
|
+
transformed = transform_tags_structure(resource)
|
|
21
|
+
transformed_resources.append(transformed)
|
|
22
|
+
|
|
23
|
+
filtered: list[dict] = []
|
|
24
|
+
for resource in transformed_resources:
|
|
25
|
+
flattened = flatten_dict_keys(resource)
|
|
26
|
+
|
|
27
|
+
searchable_items = []
|
|
28
|
+
|
|
29
|
+
searchable_items.extend([key.lower() for key in flattened.keys()])
|
|
30
|
+
|
|
31
|
+
searchable_items.extend([str(value).lower() for value in flattened.values()])
|
|
32
|
+
|
|
33
|
+
if len(filtered) + len([r for r in resources if r != resource]) < 3:
|
|
34
|
+
debug_print(f"Sample flattened keys: {list(flattened.keys())[:5]}")
|
|
35
|
+
debug_print(f"Sample searchable items: {searchable_items[:10]}")
|
|
36
|
+
|
|
37
|
+
matches_all = True
|
|
38
|
+
for filter_text in value_filters:
|
|
39
|
+
filter_lower = filter_text.lower()
|
|
40
|
+
if not any(filter_lower in item for item in searchable_items):
|
|
41
|
+
matches_all = False
|
|
42
|
+
break
|
|
43
|
+
else:
|
|
44
|
+
matching_items = [item for item in searchable_items if filter_lower in item]
|
|
45
|
+
debug_print(
|
|
46
|
+
f"Filter '{filter_text}' matched: {matching_items[:3]}"
|
|
47
|
+
f"{'...' if len(matching_items) > 3 else ''}"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if matches_all:
|
|
51
|
+
filtered.append(resource)
|
|
52
|
+
|
|
53
|
+
debug_print(f"Found {len(filtered)} resources matching filters (out of {len(resources)} total)")
|
|
54
|
+
return filtered
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def parse_multi_level_filters_for_mode(argv, mode="single"):
|
|
58
|
+
"""Parse command line args with -- separators for proper filtering based on mode
|
|
59
|
+
|
|
60
|
+
Mode behavior:
|
|
61
|
+
single: resource_filters=[], everything else as value/column filters
|
|
62
|
+
multi: proper semantic meaning of separators:
|
|
63
|
+
- Args before first -- = resource filters
|
|
64
|
+
- Args between first and second -- = value filters
|
|
65
|
+
- Args after second -- = column filters
|
|
66
|
+
"""
|
|
67
|
+
separator_positions = []
|
|
68
|
+
for i, arg in enumerate(argv):
|
|
69
|
+
if arg == "--":
|
|
70
|
+
separator_positions.append(i)
|
|
71
|
+
|
|
72
|
+
segments = []
|
|
73
|
+
start = 0
|
|
74
|
+
|
|
75
|
+
for pos in separator_positions:
|
|
76
|
+
segments.append(argv[start:pos])
|
|
77
|
+
start = pos + 1
|
|
78
|
+
|
|
79
|
+
segments.append(argv[start:])
|
|
80
|
+
|
|
81
|
+
first_segment = segments[0] if segments else []
|
|
82
|
+
|
|
83
|
+
third_segment: list[str]
|
|
84
|
+
if len(segments) == 1:
|
|
85
|
+
second_segment = []
|
|
86
|
+
third_segment = []
|
|
87
|
+
elif len(segments) == 2:
|
|
88
|
+
second_segment = segments[1]
|
|
89
|
+
third_segment = []
|
|
90
|
+
else:
|
|
91
|
+
second_segment = segments[1]
|
|
92
|
+
third_segment = segments[2]
|
|
93
|
+
|
|
94
|
+
base_command = []
|
|
95
|
+
service_found = False
|
|
96
|
+
action_found = False
|
|
97
|
+
extra_args = []
|
|
98
|
+
|
|
99
|
+
for arg in first_segment:
|
|
100
|
+
if arg.startswith("-"):
|
|
101
|
+
base_command.append(arg)
|
|
102
|
+
elif not service_found:
|
|
103
|
+
base_command.append(arg)
|
|
104
|
+
service_found = True
|
|
105
|
+
elif not action_found:
|
|
106
|
+
base_command.append(arg)
|
|
107
|
+
action_found = True
|
|
108
|
+
else:
|
|
109
|
+
extra_args.append(arg)
|
|
110
|
+
|
|
111
|
+
if mode == "single":
|
|
112
|
+
resource_filters: list[str] = []
|
|
113
|
+
|
|
114
|
+
if len(segments) == 1:
|
|
115
|
+
value_filters = extra_args
|
|
116
|
+
column_filters = []
|
|
117
|
+
elif len(segments) == 2:
|
|
118
|
+
value_filters = extra_args
|
|
119
|
+
column_filters = second_segment
|
|
120
|
+
else:
|
|
121
|
+
value_filters = extra_args + second_segment
|
|
122
|
+
column_filters = third_segment
|
|
123
|
+
|
|
124
|
+
elif mode == "multi":
|
|
125
|
+
if len(segments) == 1:
|
|
126
|
+
resource_filters = []
|
|
127
|
+
value_filters = extra_args
|
|
128
|
+
column_filters = []
|
|
129
|
+
elif len(segments) == 2:
|
|
130
|
+
resource_filters = []
|
|
131
|
+
value_filters = extra_args
|
|
132
|
+
column_filters = second_segment
|
|
133
|
+
else:
|
|
134
|
+
resource_filters = extra_args
|
|
135
|
+
value_filters = second_segment
|
|
136
|
+
column_filters = third_segment
|
|
137
|
+
|
|
138
|
+
else:
|
|
139
|
+
raise ValueError(f"Invalid mode '{mode}'. Must be 'single' or 'multi'.")
|
|
140
|
+
|
|
141
|
+
debug_print(
|
|
142
|
+
f"Multi-level parsing (mode={mode}) - Base: {base_command}, "
|
|
143
|
+
f"Resource: {resource_filters}, Value: {value_filters}, "
|
|
144
|
+
f"Column: {column_filters}"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return base_command, resource_filters, value_filters, column_filters
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def extract_parameter_values(resources, parameter_name):
|
|
151
|
+
"""Extract parameter values from list operation results"""
|
|
152
|
+
if not resources:
|
|
153
|
+
return []
|
|
154
|
+
|
|
155
|
+
values = []
|
|
156
|
+
|
|
157
|
+
if resources and isinstance(resources[0], str):
|
|
158
|
+
debug_print(
|
|
159
|
+
f"Resources are simple strings, using them directly for parameter '{parameter_name}'"
|
|
160
|
+
)
|
|
161
|
+
return resources
|
|
162
|
+
|
|
163
|
+
# Apply tag transformation before parameter extraction
|
|
164
|
+
transformed_resources = []
|
|
165
|
+
for resource in resources:
|
|
166
|
+
transformed = transform_tags_structure(resource)
|
|
167
|
+
transformed_resources.append(transformed)
|
|
168
|
+
|
|
169
|
+
pascal_case_name = convert_parameter_name(parameter_name)
|
|
170
|
+
search_names = [parameter_name, pascal_case_name]
|
|
171
|
+
|
|
172
|
+
debug_print(f"Looking for parameter values using names: {search_names}")
|
|
173
|
+
|
|
174
|
+
for resource in transformed_resources:
|
|
175
|
+
flat = flatten_dict_keys(resource)
|
|
176
|
+
|
|
177
|
+
found_value = None
|
|
178
|
+
for search_name in search_names:
|
|
179
|
+
if search_name in flat:
|
|
180
|
+
value = flat[search_name]
|
|
181
|
+
if value:
|
|
182
|
+
found_value = str(value)
|
|
183
|
+
break
|
|
184
|
+
|
|
185
|
+
if found_value:
|
|
186
|
+
values.append(found_value)
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
for search_name in search_names:
|
|
190
|
+
for key, value in flat.items():
|
|
191
|
+
if key.lower() == search_name.lower() and value:
|
|
192
|
+
found_value = str(value)
|
|
193
|
+
break
|
|
194
|
+
if found_value:
|
|
195
|
+
break
|
|
196
|
+
|
|
197
|
+
if found_value:
|
|
198
|
+
values.append(found_value)
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
for search_name in search_names:
|
|
202
|
+
matching_keys = [k for k in flat.keys() if search_name.lower() in k.lower()]
|
|
203
|
+
if matching_keys:
|
|
204
|
+
key = matching_keys[0]
|
|
205
|
+
value = flat[key]
|
|
206
|
+
if value:
|
|
207
|
+
values.append(str(value))
|
|
208
|
+
break
|
|
209
|
+
|
|
210
|
+
if found_value:
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
# Standard field fallback when parameter-specific field not found
|
|
214
|
+
standard_fields = []
|
|
215
|
+
param_lower = parameter_name.lower()
|
|
216
|
+
|
|
217
|
+
if param_lower.endswith("name"):
|
|
218
|
+
standard_fields.append("Name")
|
|
219
|
+
elif param_lower.endswith("id"):
|
|
220
|
+
standard_fields.append("Id")
|
|
221
|
+
elif param_lower.endswith(("arn", "ARN")):
|
|
222
|
+
standard_fields.extend(["Arn", "ARN"])
|
|
223
|
+
elif param_lower.endswith("key"):
|
|
224
|
+
standard_fields.append("Key")
|
|
225
|
+
elif param_lower.endswith("value"):
|
|
226
|
+
standard_fields.append("Value")
|
|
227
|
+
else:
|
|
228
|
+
# Common AWS resource types that typically have a Name field
|
|
229
|
+
resource_types_with_names = [
|
|
230
|
+
"bucket",
|
|
231
|
+
"cluster",
|
|
232
|
+
"instance",
|
|
233
|
+
"volume",
|
|
234
|
+
"snapshot",
|
|
235
|
+
"image",
|
|
236
|
+
"vpc",
|
|
237
|
+
"subnet",
|
|
238
|
+
"queue",
|
|
239
|
+
"topic",
|
|
240
|
+
"table",
|
|
241
|
+
"function",
|
|
242
|
+
"role",
|
|
243
|
+
"user",
|
|
244
|
+
"group",
|
|
245
|
+
"policy",
|
|
246
|
+
"stack",
|
|
247
|
+
"template",
|
|
248
|
+
"pipeline",
|
|
249
|
+
"repository",
|
|
250
|
+
"branch",
|
|
251
|
+
"commit",
|
|
252
|
+
"build",
|
|
253
|
+
"project",
|
|
254
|
+
"job",
|
|
255
|
+
"task",
|
|
256
|
+
"service",
|
|
257
|
+
"container",
|
|
258
|
+
"node",
|
|
259
|
+
"nodegroup",
|
|
260
|
+
"database",
|
|
261
|
+
"endpoint",
|
|
262
|
+
"domain",
|
|
263
|
+
"certificate",
|
|
264
|
+
"key",
|
|
265
|
+
"secret",
|
|
266
|
+
"parameter",
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
if param_lower in resource_types_with_names:
|
|
270
|
+
standard_fields.append("Name")
|
|
271
|
+
debug_print(f"Parameter '{parameter_name}' is a resource type, will try Name field")
|
|
272
|
+
|
|
273
|
+
if standard_fields:
|
|
274
|
+
debug_print(
|
|
275
|
+
f"No specific field found for '{parameter_name}', "
|
|
276
|
+
f"trying standard fields: {standard_fields}"
|
|
277
|
+
)
|
|
278
|
+
for standard_field in standard_fields:
|
|
279
|
+
if standard_field in flat:
|
|
280
|
+
value = flat[standard_field]
|
|
281
|
+
if value:
|
|
282
|
+
debug_print(
|
|
283
|
+
f"Found standard field '{standard_field}' "
|
|
284
|
+
f"for parameter '{parameter_name}'"
|
|
285
|
+
)
|
|
286
|
+
values.append(str(value))
|
|
287
|
+
found_value = str(value)
|
|
288
|
+
break
|
|
289
|
+
|
|
290
|
+
for key, value in flat.items():
|
|
291
|
+
if key.lower() == standard_field.lower() and value:
|
|
292
|
+
debug_print(
|
|
293
|
+
f"Found standard field '{key}' (case-insensitive) "
|
|
294
|
+
f"for parameter '{parameter_name}'"
|
|
295
|
+
)
|
|
296
|
+
values.append(str(value))
|
|
297
|
+
found_value = str(value)
|
|
298
|
+
break
|
|
299
|
+
|
|
300
|
+
if found_value:
|
|
301
|
+
break
|
|
302
|
+
|
|
303
|
+
debug_print(
|
|
304
|
+
f"Extracted {len(values)} values for parameter '{parameter_name}': "
|
|
305
|
+
f"{values[:3]}{'...' if len(values) > 3 else ''}"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
return values
|