def test_template(monkeypatch): m_client = MagicMock() m_client.return_value = m_client m_client.describe_stacks.return_value = MOCK_STACK1 m_client.get_template.return_value = { 'TemplateBody': { 'answer_to_life_the_universe_and_everything': 42 } } monkeypatch.setattr('boto3.client', m_client) stack = CloudFormationStack.get_by_stack_name('myapp') assert stack.template == {'answer_to_life_the_universe_and_everything': 42} m_client.get_template.assert_called_once_with(StackName='myapp-42') # The 2nd time it should use a cached value m_client.get_template.reset_mock() assert stack.template == {'answer_to_life_the_universe_and_everything': 42} assert m_client.get_template_assert_not_called() # After reset it should fetch the template again m_client.get_template.reset_mock() stack.reset() assert stack.template == {'answer_to_life_the_universe_and_everything': 42} m_client.get_template.assert_called_once_with(StackName='myapp-42')
def test_get_by_stack_name_not_found(monkeypatch): m_client = MagicMock() m_client.return_value = m_client m_client.describe_stacks.side_effect = botocore.exceptions.ClientError( {'Error': {'Code': 'ValidationError', 'Message': 'Stack with id myapp does not exist'}}, 'foobar') monkeypatch.setattr('boto3.client', m_client) with pytest.raises(StackNotFound): CloudFormationStack.get_by_stack_name('myapp') m_client.describe_stacks.side_effect = botocore.exceptions.ClientError( {'Error': {'Code': 'ValidationError', 'Message': 'Random'}}, 'foobar') with pytest.raises(botocore.exceptions.ClientError): CloudFormationStack.get_by_stack_name('myapp')
def test_stack_update(monkeypatch): m_client = MagicMock() m_client.return_value = m_client m_client.describe_stacks.return_value = MOCK_STACK1 m_client.get_template.return_value = { 'TemplateBody': { 'answer_to_life_the_universe_and_everything': 42 } } monkeypatch.setattr('boto3.client', m_client) stack = CloudFormationStack.get_by_stack_name('myapp') stack.template["What not to do"] = "Panic" stack.parameters['SomethingElse'] = "CompletelyDifferent" stack.update() parameters = [{ 'ParameterValue': '42', 'ParameterKey': 'ImageVersion' }, { 'ParameterKey': 'SomethingElse', 'ParameterValue': 'CompletelyDifferent' }] template = { "answer_to_life_the_universe_and_everything": 42, "What not to do": "Panic" } m_client.update_stack.assert_called_once_with( Capabilities=['CAPABILITY_IAM'], Parameters=parameters, StackName='myapp-42', TemplateBody=json.dumps(template)) m_client.update_stack.side_effect = botocore.exceptions.ClientError( { 'Error': { 'Code': 'ValidationError', 'Message': 'No updates are to be performed.' } }, 'foobar') with pytest.raises(StackNotUpdated): stack.update() m_client.update_stack.side_effect = botocore.exceptions.ClientError( {'Error': { 'Code': 'ValidationError', 'Message': 'Random' }}, 'foobar') with pytest.raises(botocore.exceptions.ClientError): stack.update()
def test_get_by_stack_name(monkeypatch): m_client = MagicMock() m_client.return_value = m_client m_client.describe_stacks.return_value = MOCK_STACK1 monkeypatch.setattr('boto3.client', m_client) stack = CloudFormationStack.get_by_stack_name('myapp') assert stack.name == 'myapp-42' assert stack.tags['StackName'] == 'myapp' assert not stack.disable_rollback
def test_get_by_stack_name_not_found(monkeypatch): m_client = MagicMock() m_client.return_value = m_client m_client.describe_stacks.side_effect = botocore.exceptions.ClientError( { 'Error': { 'Code': 'ValidationError', 'Message': 'Stack with id myapp does not exist' } }, 'foobar') monkeypatch.setattr('boto3.client', m_client) with pytest.raises(StackNotFound): CloudFormationStack.get_by_stack_name('myapp') m_client.describe_stacks.side_effect = botocore.exceptions.ClientError( {'Error': { 'Code': 'ValidationError', 'Message': 'Random' }}, 'foobar') with pytest.raises(botocore.exceptions.ClientError): CloudFormationStack.get_by_stack_name('myapp')
def test_stack_update(monkeypatch): m_client = MagicMock() m_client.return_value = m_client m_client.describe_stacks.return_value = MOCK_STACK1 m_client.get_template.return_value = {'TemplateBody': {'answer_to_life_the_universe_and_everything': 42}} monkeypatch.setattr('boto3.client', m_client) stack = CloudFormationStack.get_by_stack_name('myapp') stack.template["What not to do"] = "Panic" stack.parameters['SomethingElse'] = "CompletelyDifferent" stack.update() parameters = [{'ParameterValue': '42', 'ParameterKey': 'ImageVersion'}, {'ParameterKey': 'SomethingElse', 'ParameterValue': 'CompletelyDifferent'}] template = {"answer_to_life_the_universe_and_everything": 42, "What not to do": "Panic"} m_client.update_stack.assert_called_once_with(Capabilities=['CAPABILITY_IAM'], Parameters=parameters, StackName='myapp-42', TemplateBody=json.dumps(template)) m_client.update_stack.side_effect = botocore.exceptions.ClientError( {'Error': {'Code': 'ValidationError', 'Message': 'No updates are to be performed.'}}, 'foobar') with pytest.raises(StackNotUpdated): stack.update() m_client.update_stack.side_effect = botocore.exceptions.ClientError( {'Error': {'Code': 'ValidationError', 'Message': 'Random'}}, 'foobar') with pytest.raises(botocore.exceptions.ClientError): stack.update()
def test_template(monkeypatch): m_client = MagicMock() m_client.return_value = m_client m_client.describe_stacks.return_value = MOCK_STACK1 m_client.get_template.return_value = {'TemplateBody': {'answer_to_life_the_universe_and_everything': 42}} monkeypatch.setattr('boto3.client', m_client) stack = CloudFormationStack.get_by_stack_name('myapp') assert stack.template == {'answer_to_life_the_universe_and_everything': 42} m_client.get_template.assert_called_once_with(StackName='myapp-42') # The 2nd time it should use a cached value m_client.get_template.reset_mock() assert stack.template == {'answer_to_life_the_universe_and_everything': 42} assert m_client.get_template_assert_not_called() # After reset it should fetch the template again m_client.get_template.reset_mock() stack.reset() assert stack.template == {'answer_to_life_the_universe_and_everything': 42} m_client.get_template.assert_called_once_with(StackName='myapp-42')
def test_cf_resources(monkeypatch): summary = [{'LastUpdatedTimestamp': datetime(2016, 7, 20, 7, 3, 7, 108000, tzinfo=timezone.utc), 'LogicalResourceId': 'AppLoadBalancer', 'PhysicalResourceId': 'myapp1-1', 'ResourceStatus': 'CREATE_COMPLETE', 'ResourceType': 'AWS::ElasticLoadBalancing::LoadBalancer'}, {'LastUpdatedTimestamp': datetime(2016, 7, 20, 7, 3, 45, 70000, tzinfo=timezone.utc), 'LogicalResourceId': 'AppLoadBalancerMainDomain', 'PhysicalResourceId': 'myapp1.example.com', 'ResourceStatus': 'CREATE_COMPLETE', 'ResourceType': 'AWS::Route53::RecordSet'}, {'LastUpdatedTimestamp': datetime(2016, 7, 20, 7, 3, 45, 70000, tzinfo=timezone.utc), 'LogicalResourceId': 'ThisWillBeIgnored', 'ResourceStatus': 'CREATE_COMPLETE', 'ResourceType': 'AWS::Route53::RecordSet'}, {'LastUpdatedTimestamp': datetime(2016, 7, 20, 7, 3, 43, 871000, tzinfo=timezone.utc), 'LogicalResourceId': 'AppLoadBalancerVersionDomain', 'PhysicalResourceId': 'myapp1-1.example.com', 'ResourceStatus': 'CREATE_COMPLETE', 'ResourceType': 'AWS::Route53::RecordSet'}, {'LastUpdatedTimestamp': datetime(2016, 7, 20, 7, 7, 38, 495000, tzinfo=timezone.utc), 'LogicalResourceId': 'AppServer', 'PhysicalResourceId': 'myapp1-1-AppServer-00000', 'ResourceStatus': 'CREATE_COMPLETE', 'ResourceType': 'AWS::AutoScaling::AutoScalingGroup'}, {'LastUpdatedTimestamp': datetime(2016, 7, 20, 7, 5, 10, 48000, tzinfo=timezone.utc), 'LogicalResourceId': 'AppServerConfig', 'PhysicalResourceId': 'myapp1-1-AppServerConfig-00000', 'ResourceStatus': 'CREATE_COMPLETE', 'ResourceType': 'AWS::AutoScaling::LaunchConfiguration'}, {'LastUpdatedTimestamp': datetime(2016, 7, 20, 7, 5, 6, 745000, tzinfo=timezone.utc), 'LogicalResourceId': 'AppServerInstanceProfile', 'PhysicalResourceId': 'myapp1-1-AppServerInstanceProfile-000', 'ResourceStatus': 'CREATE_COMPLETE', 'ResourceType': 'AWS::IAM::InstanceProfile'}] response = {'ResponseMetadata': {'HTTPStatusCode': 200, 'RequestId': '0000'}, 'StackResourceSummaries': summary} mock_client = MagicMock() mock_client.return_value = mock_client mock_client.list_stack_resources.return_value = response mock_client.describe_stacks.return_value = MOCK_STACK1 monkeypatch.setattr('boto3.client', mock_client) mock_route53 = MagicMock() mock_route53.side_effect = [[MagicMock(set_identifier=None)], [MagicMock(set_identifier='myapp-42')]] monkeypatch.setattr('senza.manaus.cloudformation.Route53.get_records', mock_route53) stack = CloudFormationStack.get_by_stack_name('myapp') resources = list(stack.resources) mock_route53.assert_any_call(name='myapp1.example.com') mock_route53.assert_any_call(name='myapp1-1.example.com') assert len(resources) == 2
def test_cf_resources(monkeypatch): summary = [{ 'LastUpdatedTimestamp': datetime(2016, 7, 20, 7, 3, 7, 108000, tzinfo=timezone.utc), 'LogicalResourceId': 'AppLoadBalancer', 'PhysicalResourceId': 'myapp1-1', 'ResourceStatus': 'CREATE_COMPLETE', 'ResourceType': 'AWS::ElasticLoadBalancing::LoadBalancer' }, { 'LastUpdatedTimestamp': datetime(2016, 7, 20, 7, 3, 45, 70000, tzinfo=timezone.utc), 'LogicalResourceId': 'AppLoadBalancerMainDomain', 'PhysicalResourceId': 'myapp1.example.com', 'ResourceStatus': 'CREATE_COMPLETE', 'ResourceType': 'AWS::Route53::RecordSet' }, { 'LastUpdatedTimestamp': datetime(2016, 7, 20, 7, 3, 45, 70000, tzinfo=timezone.utc), 'LogicalResourceId': 'ThisWillBeIgnored', 'ResourceStatus': 'CREATE_COMPLETE', 'ResourceType': 'AWS::Route53::RecordSet' }, { 'LastUpdatedTimestamp': datetime(2016, 7, 20, 7, 3, 43, 871000, tzinfo=timezone.utc), 'LogicalResourceId': 'AppLoadBalancerVersionDomain', 'PhysicalResourceId': 'myapp1-1.example.com', 'ResourceStatus': 'CREATE_COMPLETE', 'ResourceType': 'AWS::Route53::RecordSet' }, { 'LastUpdatedTimestamp': datetime(2016, 7, 20, 7, 7, 38, 495000, tzinfo=timezone.utc), 'LogicalResourceId': 'AppServer', 'PhysicalResourceId': 'myapp1-1-AppServer-00000', 'ResourceStatus': 'CREATE_COMPLETE', 'ResourceType': 'AWS::AutoScaling::AutoScalingGroup' }, { 'LastUpdatedTimestamp': datetime(2016, 7, 20, 7, 5, 10, 48000, tzinfo=timezone.utc), 'LogicalResourceId': 'AppServerConfig', 'PhysicalResourceId': 'myapp1-1-AppServerConfig-00000', 'ResourceStatus': 'CREATE_COMPLETE', 'ResourceType': 'AWS::AutoScaling::LaunchConfiguration' }, { 'LastUpdatedTimestamp': datetime(2016, 7, 20, 7, 5, 6, 745000, tzinfo=timezone.utc), 'LogicalResourceId': 'AppServerInstanceProfile', 'PhysicalResourceId': 'myapp1-1-AppServerInstanceProfile-000', 'ResourceStatus': 'CREATE_COMPLETE', 'ResourceType': 'AWS::IAM::InstanceProfile' }] response = { 'ResponseMetadata': { 'HTTPStatusCode': 200, 'RequestId': '0000' }, 'StackResourceSummaries': summary } mock_client = MagicMock() mock_client.return_value = mock_client mock_client.list_stack_resources.return_value = response mock_client.describe_stacks.return_value = MOCK_STACK1 monkeypatch.setattr('boto3.client', mock_client) mock_route53 = MagicMock() mock_route53.side_effect = [[MagicMock(set_identifier=None)], [MagicMock(set_identifier='myapp-42')]] monkeypatch.setattr('senza.manaus.cloudformation.Route53.get_records', mock_route53) stack = CloudFormationStack.get_by_stack_name('myapp') resources = list(stack.resources) mock_route53.assert_any_call(name='myapp1.example.com') mock_route53.assert_any_call(name='myapp1-1.example.com') assert len(resources) == 2