From 85a329aa8b89f60bcb79204d2a313286e756b15a Mon Sep 17 00:00:00 2001 From: Filippos Chalvatzoglou Date: Thu, 9 Mar 2017 13:42:16 +0000 Subject: [PATCH] Currently when deleting tail blocks forever bootstrap_cfn 's utils.tail() blocks forever on stack deletion. Added a helper function to detect when the status is deleted or failed to delete (to represent the cfn_delete event finishing). Note that after delete_done it may be possible that the stack still has lingering resources, that's why we leave it vague, and only suggest that the 'cfn delete event' finished. --- bootstrap_cfn/cloudformation.py | 7 +++++ bootstrap_cfn/utils.py | 2 ++ tests/test.py | 54 +++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/bootstrap_cfn/cloudformation.py b/bootstrap_cfn/cloudformation.py index 826369b..7cbb215 100644 --- a/bootstrap_cfn/cloudformation.py +++ b/bootstrap_cfn/cloudformation.py @@ -35,6 +35,13 @@ def stack_done(self, stack_id): return True return False + def stack_delete_done(self, stack_id): + stack_events = self.conn_cfn.describe_stack_events(stack_id) + if stack_events[0].resource_type == 'AWS::CloudFormation::Stack'\ + and stack_events[0].resource_status in ['DELETE_COMPLETE', 'DELETE_FAILED']: + return True + return False + def wait_for_stack_done(self, stack_id, timeout=3600, interval=30): return utils.timeout(timeout, interval)(self.stack_done)(stack_id) diff --git a/bootstrap_cfn/utils.py b/bootstrap_cfn/utils.py index 027514b..862d880 100644 --- a/bootstrap_cfn/utils.py +++ b/bootstrap_cfn/utils.py @@ -112,6 +112,8 @@ def tail_print(e): break elif stack.stack_done(stack_name): break + elif stack.stack_delete_done(stack_name): + break events = get_events(stack, stack_name) for e in events: if e.event_id not in seen: diff --git a/tests/test.py b/tests/test.py index c3db5ec..9591fc8 100644 --- a/tests/test.py +++ b/tests/test.py @@ -235,6 +235,60 @@ def test_stack_not_done(self): self.assertFalse(cloudformation.Cloudformation( self.env.aws_profile).stack_done(self.stack_name)) + def test_stack_delete_done(self): + stack_evt_mock = mock.Mock() + rt = mock.PropertyMock(return_value='AWS::CloudFormation::Stack') + rs = mock.PropertyMock(return_value='DELETE_COMPLETE') + type(stack_evt_mock).resource_type = rt + type(stack_evt_mock).resource_status = rs + mock_config = {'describe_stack_events.return_value': [stack_evt_mock]} + + cf_mock = mock.Mock() + cf_connect_result = mock.Mock(name='cf_connect') + cf_mock.return_value = cf_connect_result + cf_connect_result.configure_mock(**mock_config) + + boto.cloudformation.connect_to_region = cf_mock + + self.assertTrue(cloudformation.Cloudformation( + self.env.aws_profile).stack_delete_done(self.stack_name)) + + def test_stack_delete_failed_but_done(self): + stack_evt_mock = mock.Mock() + rt = mock.PropertyMock(return_value='AWS::CloudFormation::Stack') + rs = mock.PropertyMock(return_value='DELETE_FAILED') + type(stack_evt_mock).resource_type = rt + type(stack_evt_mock).resource_status = rs + mock_config = {'describe_stack_events.return_value': [stack_evt_mock]} + + cf_mock = mock.Mock() + cf_connect_result = mock.Mock(name='cf_connect') + cf_mock.return_value = cf_connect_result + cf_connect_result.configure_mock(**mock_config) + + boto.cloudformation.connect_to_region = cf_mock + + self.assertTrue(cloudformation.Cloudformation( + self.env.aws_profile).stack_delete_done(self.stack_name)) + + def test_stack_not_delete_done(self): + stack_evt_mock = mock.Mock() + rt = mock.PropertyMock(return_value='AWS::CloudFormation::Stack') + rs = mock.PropertyMock(return_value='DELETE_ME') + type(stack_evt_mock).resource_type = rt + type(stack_evt_mock).resource_status = rs + mock_config = {'describe_stack_events.return_value': [stack_evt_mock]} + + cf_mock = mock.Mock() + cf_connect_result = mock.Mock(name='cf_connect') + cf_mock.return_value = cf_connect_result + cf_connect_result.configure_mock(**mock_config) + + boto.cloudformation.connect_to_region = cf_mock + + self.assertFalse(cloudformation.Cloudformation( + self.env.aws_profile).stack_delete_done(self.stack_name)) + def test_ssl_upload(self): iam_mock = mock.Mock() iam_connect_result = mock.Mock(name='iam_connect')