class TestAWSCloudFormationHook(unittest.TestCase): def setUp(self): self.hook = AWSCloudFormationHook(aws_conn_id='aws_default') def create_stack(self, stack_name): timeout = 15 template_body = json.dumps( {'Resources': {"myResource": {"Type": "emr", "Properties": {"myProperty": "myPropertyValue"}}}} ) self.hook.create_stack( stack_name=stack_name, params={ 'TimeoutInMinutes': timeout, 'TemplateBody': template_body, 'Parameters': [{'ParameterKey': 'myParam', 'ParameterValue': 'myParamValue'}], }, ) @mock_cloudformation def test_get_conn_returns_a_boto3_connection(self): self.assertIsNotNone(self.hook.get_conn().describe_stacks()) @mock_cloudformation def test_get_stack_status(self): stack_name = 'my_test_get_stack_status_stack' stack_status = self.hook.get_stack_status(stack_name=stack_name) self.assertIsNone(stack_status) self.create_stack(stack_name) stack_status = self.hook.get_stack_status(stack_name=stack_name) self.assertEqual(stack_status, 'CREATE_COMPLETE', 'Incorrect stack status returned.') @mock_cloudformation def test_create_stack(self): stack_name = 'my_test_create_stack_stack' self.create_stack(stack_name) stacks = self.hook.get_conn().describe_stacks()['Stacks'] self.assertGreater(len(stacks), 0, 'CloudFormation should have stacks') matching_stacks = [x for x in stacks if x['StackName'] == stack_name] self.assertEqual(len(matching_stacks), 1, f'stack with name {stack_name} should exist') stack = matching_stacks[0] self.assertEqual(stack['StackStatus'], 'CREATE_COMPLETE', 'Stack should be in status CREATE_COMPLETE') @mock_cloudformation def test_delete_stack(self): stack_name = 'my_test_delete_stack_stack' self.create_stack(stack_name) self.hook.delete_stack(stack_name=stack_name) stacks = self.hook.get_conn().describe_stacks()['Stacks'] matching_stacks = [x for x in stacks if x['StackName'] == stack_name] self.assertEqual(len(matching_stacks), 0, f'stack with name {stack_name} should not exist')
class CloudFormationCreateStackSensor(BaseSensorOperator): """ Waits for a stack to be created successfully on AWS CloudFormation. :param stack_name: The name of the stack to wait for (templated) :type stack_name: str :param aws_conn_id: ID of the Airflow connection where credentials and extra configuration are stored :type aws_conn_id: str :param poke_interval: Time in seconds that the job should wait between each try :type poke_interval: int """ template_fields = ['stack_name'] ui_color = '#C5CAE9' @apply_defaults def __init__(self, stack_name, aws_conn_id='aws_default', region_name=None, *args, **kwargs): super().__init__(*args, **kwargs) self.stack_name = stack_name self.hook = AWSCloudFormationHook(aws_conn_id=aws_conn_id, region_name=region_name) def poke(self, context): stack_status = self.hook.get_stack_status(self.stack_name) if stack_status == 'CREATE_COMPLETE': return True if stack_status in ('CREATE_IN_PROGRESS', None): return False raise ValueError(f'Stack {self.stack_name} in bad state: {stack_status}')