1
+ import contextlib
1
2
import json
3
+ import os
2
4
5
+ import pytest
6
+ from botocore .exceptions import ClientError
7
+
8
+ from localstack .constants import TEST_AWS_REGION_NAME
9
+ from localstack .testing .aws .util import is_aws_cloud
3
10
from localstack .testing .pytest import markers
4
11
from localstack .utils .common import short_uid
12
+ from localstack .utils .sync import retry
13
+
14
+
15
+ @pytest .fixture
16
+ def resourcegroups_create_group (aws_client ):
17
+ groups = []
18
+
19
+ def _create_group (** kwargs ):
20
+ response = aws_client .resource_groups .create_group (** kwargs )
21
+ groups .append (response ["Group" ]["Name" ])
22
+ return response
23
+
24
+ yield _create_group
25
+
26
+ for group_name in groups :
27
+ with contextlib .suppress (
28
+ ClientError , KeyError
29
+ ): # adding KeyError to the list because Moto has a bug
30
+ aws_client .resource_groups .delete_group (GroupName = group_name )
31
+
32
+
33
+ @pytest .fixture
34
+ def sqs_create_queue_in_region (aws_client_factory ):
35
+ queue_urls = {}
36
+
37
+ def factory (region , ** kwargs ):
38
+ if "QueueName" not in kwargs :
39
+ kwargs ["QueueName" ] = "test-queue-%s" % short_uid ()
40
+ response = aws_client_factory (region_name = region ).sqs .create_queue (** kwargs )
41
+ url = response ["QueueUrl" ]
42
+ queue_urls .setdefault (region , []).append (url )
43
+
44
+ return url
45
+
46
+ yield factory
47
+
48
+ # cleanup
49
+ for queues_region , queue_urls in queue_urls .items ():
50
+ sqs_client = aws_client_factory (region_name = queues_region ).sqs
51
+ for queue_url in queue_urls :
52
+ with contextlib .suppress (ClientError ):
53
+ sqs_client .delete_queue (QueueUrl = queue_url )
5
54
6
55
56
+ @pytest .fixture (autouse = True )
57
+ def resource_groups_snapshot_transformers (snapshot ):
58
+ snapshot .add_transformers_list (
59
+ [
60
+ snapshot .transform .key_value ("Name" ),
61
+ snapshot .transform .key_value ("NextToken" ),
62
+ ]
63
+ )
64
+
65
+
66
+ @markers .snapshot .skip_snapshot_verify (
67
+ paths = [
68
+ "$..GroupArn" , # Moto is always returning the region as `us-west-1`, seems to be hard-coded
69
+ "$..GroupConfiguration" ,
70
+ "$..NextToken" ,
71
+ ]
72
+ )
7
73
class TestResourceGroups :
8
- @markers .aws .unknown
9
- def test_create_group (self , aws_client ):
10
- resource_group_client = aws_client .resource_groups
11
- name = "resource_group-{}" .format (short_uid ())
12
- response = resource_group_client .create_group (
74
+ """
75
+ List of Services integrations with resourcegroups
76
+ see: https://docs.aws.amazon.com/ARG/latest/userguide/integrated-services-list.html
77
+ List of supported resources:
78
+ see: https://docs.aws.amazon.com/ARG/latest/userguide/supported-resources.html
79
+ """
80
+
81
+ @markers .aws .validated
82
+ def test_create_group (self , aws_client , resourcegroups_create_group , snapshot ):
83
+ name = f"resource_group-{ short_uid ()} "
84
+ response = resourcegroups_create_group (
13
85
Name = name ,
14
86
Description = "description" ,
15
87
ResourceQuery = {
@@ -28,20 +100,285 @@ def test_create_group(self, aws_client):
28
100
},
29
101
Tags = {"resource_group_tag_key" : "resource_group_tag_value" },
30
102
)
103
+ snapshot .match ("create-group" , response )
31
104
assert name == response ["Group" ]["Name" ]
32
105
assert "TAG_FILTERS_1_0" == response ["ResourceQuery" ]["Type" ]
33
106
assert "resource_group_tag_value" == response ["Tags" ]["resource_group_tag_key" ]
34
107
35
- response = resource_group_client .get_group (GroupName = name )
108
+ response = aws_client .resource_groups .get_group (GroupName = name )
109
+ snapshot .match ("get-group" , response )
36
110
assert "description" == response ["Group" ]["Description" ]
37
111
38
- response = resource_group_client .list_groups ()
112
+ response = aws_client .resource_groups .list_groups ()
113
+ snapshot .match ("list-groups" , response )
39
114
assert 1 == len (response ["GroupIdentifiers" ])
40
115
assert 1 == len (response ["Groups" ])
41
116
42
- response = resource_group_client .delete_group (GroupName = name )
117
+ response = aws_client .resource_groups .delete_group (GroupName = name )
118
+ snapshot .match ("delete-group" , response )
43
119
assert name == response ["Group" ]["Name" ]
44
120
45
- response = resource_group_client .list_groups ()
121
+ response = aws_client .resource_groups .list_groups ()
122
+ snapshot .match ("list-groups-after-delete" , response )
46
123
assert 0 == len (response ["GroupIdentifiers" ])
47
124
assert 0 == len (response ["Groups" ])
125
+
126
+ @markers .aws .validated
127
+ @pytest .mark .skipif (
128
+ condition = not is_aws_cloud (), reason = "Not implemented in moto (ListGroupResources)"
129
+ )
130
+ def test_resource_groups_tag_query (
131
+ self , aws_client , snapshot , resourcegroups_create_group , s3_bucket , sqs_create_queue
132
+ ):
133
+ snapshot .add_transformer (snapshot .transform .resource_name ())
134
+ group_name = f"resource_group-{ short_uid ()} "
135
+ response = resourcegroups_create_group (
136
+ Name = group_name ,
137
+ Description = "test-tag-query",
138
+ ResourceQuery = {
139
+ "Type" : "TAG_FILTERS_1_0" ,
140
+ "Query" : json .dumps (
141
+ {
142
+ "ResourceTypeFilters" : ["AWS::AllSupported" ],
143
+ "TagFilters" : [
144
+ {
145
+ "Key" : "Stage" ,
146
+ "Values" : ["test-resource-group" ],
147
+ }
148
+ ],
149
+ }
150
+ ),
151
+ },
152
+ Tags = {"GroupTag" : "GroupTag1" },
153
+ )
154
+ snapshot .match ("create-group" , response )
155
+
156
+ response = aws_client .resource_groups .list_group_resources (Group = group_name )
157
+ snapshot .match ("list-group-resources-empty" , response )
158
+
159
+ # create SQS queue
160
+ tagged_queue_url = sqs_create_queue ()
161
+ # tag queue
162
+ tags = {"Stage" : "test-resource-group" }
163
+ aws_client .sqs .tag_queue (QueueUrl = tagged_queue_url , Tags = tags )
164
+
165
+ aws_client .s3 .put_bucket_tagging (
166
+ Bucket = s3_bucket , Tagging = {"TagSet" : [{"Key" : "Stage" , "Value" : "test-resource-group" }]}
167
+ )
168
+
169
+ not_tagged_queue_url = sqs_create_queue ()
170
+ tags = {"Stage" : "not-part-resource-group" }
171
+ aws_client .sqs .tag_queue (QueueUrl = not_tagged_queue_url , Tags = tags )
172
+
173
+ response = aws_client .resource_groups .list_group_resources (Group = group_name )
174
+ snapshot .match ("list-group-resources" , response )
175
+
176
+ queue_tags = aws_client .sqs .list_queue_tags (QueueUrl = tagged_queue_url )
177
+ snapshot .match ("get-queue-tags" , queue_tags )
178
+
179
+ aws_client .sqs .untag_queue (QueueUrl = tagged_queue_url , TagKeys = ["Stage" ])
180
+
181
+ def _get_group_resources ():
182
+ _response = aws_client .resource_groups .list_group_resources (Group = group_name )
183
+ assert len (response ["Resources" ]) == 1
184
+ return _response
185
+
186
+ response = retry (_get_group_resources , retries = 3 , sleep = 1 )
187
+ snapshot .match ("list-group-resources-after-queue-removal" , response )
188
+
189
+ @markers .aws .validated
190
+ @pytest .mark .skipif (
191
+ condition = not is_aws_cloud (), reason = "Not implemented in moto (ListGroupResources)"
192
+ )
193
+ def test_resource_groups_different_region (
194
+ self , aws_client_factory , snapshot , resourcegroups_create_group , sqs_create_queue_in_region
195
+ ):
196
+ """Resource groups can only have resources from the same Region, the one of the group"""
197
+ region_1 = TEST_AWS_REGION_NAME
198
+ region_2 = "us-east-2"
199
+ resourcegroups_client = aws_client_factory (region_name = region_1 ).resource_groups
200
+ snapshot .add_transformer (snapshot .transform .resource_name ())
201
+ group_name = f"resource_group-{ short_uid ()} "
202
+ response = resourcegroups_create_group (
203
+ Name = group_name ,
204
+ Description = "test-tag-query" ,
205
+ ResourceQuery = {
206
+ "Type" : "TAG_FILTERS_1_0" ,
207
+ "Query" : json .dumps (
208
+ {
209
+ "ResourceTypeFilters" : ["AWS::AllSupported" ],
210
+ "TagFilters" : [
211
+ {
212
+ "Key" : "Stage" ,
213
+ "Values" : ["test-resource-group" ],
214
+ }
215
+ ],
216
+ }
217
+ ),
218
+ },
219
+ Tags = {"GroupTag" : "GroupTag1" },
220
+ )
221
+ snapshot .match ("create-group" , response )
222
+
223
+ # create 2 SQS queues in different regions with tags
224
+ tags = {"Stage" : "test-resource-group" }
225
+ sqs_create_queue_in_region (region = region_1 , tags = tags )
226
+ sqs_create_queue_in_region (region = region_2 , tags = tags )
227
+
228
+ response = resourcegroups_client .list_group_resources (Group = group_name )
229
+ snapshot .match ("list-group-resources" , response )
230
+
231
+ @markers .aws .validated
232
+ @pytest .mark .skipif (
233
+ condition = not is_aws_cloud (), reason = "Not implemented in moto (ListGroupResources)"
234
+ )
235
+ def test_resource_type_filters (
236
+ self , aws_client , snapshot , resourcegroups_create_group , s3_bucket , sqs_create_queue
237
+ ):
238
+ """Resource group can filter with a ResourceType, like `AWS::S3::Bucket`"""
239
+ snapshot .add_transformer (snapshot .transform .resource_name ())
240
+ group_name = f"resource_group-{ short_uid ()} "
241
+ response = resourcegroups_create_group (
242
+ Name = group_name ,
243
+ Description = "test-tag-query" ,
244
+ ResourceQuery = {
245
+ "Type" : "TAG_FILTERS_1_0" ,
246
+ "Query" : json .dumps (
247
+ {
248
+ "ResourceTypeFilters" : ["AWS::S3::Bucket" ],
249
+ "TagFilters" : [
250
+ {
251
+ "Key" : "Stage" ,
252
+ "Values" : ["test-resource-group" ],
253
+ }
254
+ ],
255
+ }
256
+ ),
257
+ },
258
+ Tags = {"GroupTag" : "GroupTag1" },
259
+ )
260
+ snapshot .match ("create-group" , response )
261
+
262
+ # create SQS queue with tags
263
+ sqs_create_queue (tags = {"Stage" : "test-resource-group" })
264
+
265
+ aws_client .s3 .put_bucket_tagging (
266
+ Bucket = s3_bucket , Tagging = {"TagSet" : [{"Key" : "Stage" , "Value" : "test-resource-group" }]}
267
+ )
268
+
269
+ response = aws_client .resource_groups .list_group_resources (Group = group_name )
270
+ snapshot .match ("list-group-resources" , response )
271
+
272
+ @markers .aws .validated
273
+ @pytest .mark .skipif (
274
+ condition = not is_aws_cloud (), reason = "Not implemented in moto (ListGroupResources)"
275
+ )
276
+ def test_cloudformation_query (
277
+ self , aws_client , deploy_cfn_template , snapshot , resourcegroups_create_group
278
+ ):
279
+ snapshot .add_transformers_list (
280
+ [
281
+ snapshot .transform .key_value ("StackIdentifier" ),
282
+ snapshot .transform .resource_name (),
283
+ ]
284
+ )
285
+ stack = deploy_cfn_template (
286
+ template_path = os .path .join (
287
+ os .path .dirname (__file__ ), "../../templates/deploy_template_2.yaml"
288
+ ),
289
+ parameters = {"CompanyName" : "MyCompany" , "MyEmail1" : "my@email.com" },
290
+ )
291
+ assert len (stack .outputs ) == 3
292
+ topic_arn = stack .outputs ["MyTopic" ]
293
+
294
+ group_name = f"resource_group-{ short_uid ()} "
295
+ response = resourcegroups_create_group (
296
+ Name = group_name ,
297
+ Description = "test-cfn-query" ,
298
+ ResourceQuery = {
299
+ "Type" : "CLOUDFORMATION_STACK_1_0" ,
300
+ "Query" : json .dumps (
301
+ {
302
+ "ResourceTypeFilters" : ["AWS::AllSupported" ],
303
+ "StackIdentifier" : stack .stack_id ,
304
+ }
305
+ ),
306
+ },
307
+ )
308
+ snapshot .match ("create-group" , response )
309
+
310
+ response = aws_client .resource_groups .list_group_resources (Group = group_name )
311
+ snapshot .match ("list-group-resources" , response )
312
+
313
+ assert topic_arn in [
314
+ resource ["ResourceArn" ] for resource in response ["ResourceIdentifiers" ]
315
+ ]
316
+
317
+ stack .destroy ()
318
+
319
+ response = aws_client .resource_groups .list_group_resources (Group = group_name )
320
+ snapshot .match ("list-group-resources-after-destroy" , response )
321
+
322
+ with pytest .raises (ClientError ) as e :
323
+ resourcegroups_create_group (
324
+ Name = "going-to-fail" ,
325
+ Description = "test-cfn-query" ,
326
+ ResourceQuery = {
327
+ "Type" : "CLOUDFORMATION_STACK_1_0" ,
328
+ "Query" : json .dumps (
329
+ {
330
+ "ResourceTypeFilters" : ["AWS::AllSupported" ],
331
+ "StackIdentifier" : stack .stack_id ,
332
+ }
333
+ ),
334
+ },
335
+ )
336
+ snapshot .match ("create-group-with-delete-stack" , e .value .response )
337
+
338
+ @markers .aws .validated
339
+ @pytest .mark .skipif (
340
+ condition = not is_aws_cloud (), reason = "Not implemented in moto (SearchResources)"
341
+ )
342
+ def test_search_resources (self , aws_client , sqs_create_queue , snapshot ):
343
+ snapshot .add_transformer (snapshot .transform .resource_name ())
344
+ # create SQS queue with tags
345
+ queue_url = sqs_create_queue (tags = {"Stage" : "test-resource-group" })
346
+ queue_tags = aws_client .sqs .list_queue_tags (QueueUrl = queue_url )
347
+ snapshot .match ("queue-tags" , queue_tags )
348
+
349
+ def _get_resources (resource_types : list [str ], expected : int ):
350
+ _response = aws_client .resource_groups .search_resources (
351
+ ResourceQuery = {
352
+ "Type" : "TAG_FILTERS_1_0" ,
353
+ "Query" : json .dumps (
354
+ {
355
+ "ResourceTypeFilters" : resource_types ,
356
+ "Ta
D38D
gFilters" : [
357
+ {
358
+ "Key" : "Stage" ,
359
+ "Values" : ["test-resource-group" ],
360
+ }
361
+ ],
362
+ }
363
+ ),
364
+ }
365
+ )
366
+ assert len (_response ["ResourceIdentifiers" ]) == expected
367
+ return _response
368
+
369
+ retries = 10 if is_aws_cloud () else 3
370
+ sleep = 1 if is_aws_cloud () else 0.1
371
+
372
+ response = retry (
373
+ _get_resources ,
374
+ resource_types = ["AWS::AllSupported" ],
375
+ expected = 1 ,
376
+ retries = retries ,
377
+ sleep = sleep ,
378
+ )
379
+ snapshot .match ("list-group-resources-sqs" , response )
380
+
381
+ response = retry (
382
+ _get_resources , resource_types = ["AWS::S3::Bucket" ], expected = 0 , retries = 1 , sleep = 1
383
+ )
384
+ snapshot .match ("list-group-resources-s3" , response )
0 commit comments