8000 fix CF describe-stack-events (#1431) · sharp-bits/localstack@841a16d · GitHub
[go: up one dir, main page]

10000 Skip to content

Commit 841a16d

Browse files
authored
fix CF describe-stack-events (localstack#1431)
1 parent b7540ed commit 841a16d

File tree

3 files changed

+47
-4
lines changed

3 files changed

+47
-4
lines changed

localstack/services/cloudformation/cloudformation_listener.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import re
12
import uuid
23
import logging
34
from requests.models import Response
45
from six.moves.urllib import parse as urlparse
56
from localstack.utils.aws import aws_stack
6-
from localstack.utils.common import to_str
7+
from localstack.utils.common import to_str, obj_to_xml
78
from localstack.utils.cloudformation import template_deployer
89
from localstack.services.generic_proxy import ProxyListener
910

@@ -66,6 +67,27 @@ def forward_request(self, method, path, data, headers):
6667
req_data = urlparse.parse_qs(to_str(data))
6768
action = req_data.get('Action')[0]
6869

70+
if action == 'DescribeStackEvents':
71+
# fix an issue where moto cannot handle ARNs as stack names (or missing names)
72+
stack_name = req_data.get('StackName')
73+
run_fix = not stack_name
74+
if stack_name:
75+
stack_name = stack_name[0]
76+
if stack_name.startswith('arn:aws:cloudformation'):
77+
run_fix = True
78+
stack_name = re.sub(r'arn:aws:cloudformation:[^:]+:[^:]+:stack/([^/]+)(/.+)?',
79+
r'\1', stack_name)
80+
if run_fix:
81+
stack_names = [stack_name] if stack_name else self._list_stack_names()
82+
client = aws_stack.connect_to_service('cloudformation')
83+
events = []
84+
for stack_name in stack_names:
85+
tmp = client.describe_stack_events(StackName=stack_name)['StackEvents'][:1]
86+
events.extend(tmp)
87+
events = [{'member': e} for e in events]
88+
response_content = '<StackEvents>%s</StackEvents>' % obj_to_xml(events)
89+
return make_response('DescribeStackEvents', response_content)
90+
6991< 8000 code class="diff-text syntax-highlighted-line">
if req_data:
7092
if action == 'ValidateTemplate':
7193
return validate_template(req_data)
@@ -79,6 +101,11 @@ def return_response(self, method, path, data, headers, response):
79101
if response._content:
80102
aws_stack.fix_account_id_in_arns(response)
81103

104+
def _list_stack_names(self):
105+
client = aws_stack.connect_to_service('cloudformation')
106+
stack_names = [s['StackName'] for s in client.list_stacks()['StackSummaries']]
107+
return stack_names
108+
82109

83110
# instantiate listener
84111
UPDATE_CLOUDFORMATION = ProxyListenerCloudFormation()

localstack/utils/common.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,17 @@ def base64_to_hex(b64_string):
435435
return binascii.hexlify(base64.b64decode(b64_string))
436436

437437

438+
def obj_to_xml(obj):
439+
""" Return an XML representation of the given object (dict, list, or primitive).
440+
Does NOT add a common root element if the given obj is a list.
441+
Does NOT work for nested dict structures. """
442+
if isinstance(obj, list):
443+
return ''.join([obj_to_xml(o) for o in obj])
444+
if isinstance(obj, dict):
445+
return ''.join(['<{k}>{v}</{k}>'.format(k=k, v=obj_to_xml(v)) for (k, v) in obj.items()])
446+
return str(obj)
447+
448+
438449
def now_utc():
439450
return mktime(datetime.utcnow())
440451

tests/integration/test_cloudformation.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def test_apply_template(self):
8888
# wait for deployment to finish
8989
def check_stack():
9090
stack = get_stack_details(TEST_STACK_NAME)
91-
assert stack['StackStatus'] == 'CREATE_COMPLETE'
91+
self.assertEqual(stack['StackStatus'], 'CREATE_COMPLETE')
9292

9393
retry(check_stack, retries=3, sleep=2)
9494

@@ -102,11 +102,16 @@ def check_stack():
102102
resource = describe_stack_resource(TEST_STACK_NAME, 'SQSQueueNoNameProperty')
103103
assert queue_exists(resource['PhysicalResourceId'])
104104

105+
def test_list_stack_events(self):
106+
cloudformation = aws_stack.connect_to_service('cloudformation')
107+
response = cloudformation.describe_stack_events()
108+
self.assertEqual(response['ResponseMetadata']['HTTPStatusCode'], 200)
109+
105110
def test_validate_template(self):
106111
cloudformation = aws_stack.connect_to_service('cloudformation')
107112
template = template_deployer.template_to_json(load_file(TEST_TEMPLATE_1))
108113
response = cloudformation.validate_template(TemplateBody=template)
109-
assert response['ResponseMetadata']['HTTPStatusCode'] == 200
114+
self.assertEqual(response['ResponseMetadata']['HTTPStatusCode'], 200)
110115

111116
def test_validate_invalid_json_template_should_fail(self):
112117
cloudformation = aws_stack.connect_to_service('cloudformation')
@@ -127,7 +132,7 @@ def test_list_stack_resources_returns_queue_urls(self):
127132

128133
def check_stack():
129134
stack = get_stack_details(TEST_STACK_NAME_2)
130-
assert stack['StackStatus'] == 'CREATE_COMPLETE'
135+
self.assertEqual(stack['StackStatus'], 'CREATE_COMPLETE')
131136

132137
retry(check_stack, retries=3, sleep=2)
133138

0 commit comments

Comments
 (0)
0