Example #1
0
def test_status_does_not_exist(config, capsys):
    mock = MagicMock(**{'describe_stacks.side_effect': STACK_DOES_NOT_EXIST})
    runner = Runner(mock, config)
    runner.status(7)

    captured = capsys.readouterr()
    assert captured.out == '\nStack: TestStack, Status: does not exist\n'
Example #2
0
def test_apply_change_set(client, config, capsys, monkeypatch):
    # Prevent differences in format depending upon where this runs
    monkeypatch.setenv('STACKMANAGER_TIMEZONE', 'UTC')

    runner = Runner(client, config)
    runner.apply_change_set()

    client.describe_stack_events.assert_called_once_with(StackName='TestStack')
    client.get_waiter.assert_called_once_with('stack_update_complete')
    client.get_waiter().wait.assert_called_once_with(StackName='TestStack',
                                                     WaiterConfig={
                                                         'Delay': 10,
                                                         'MaxAttempts': 360
                                                     })
    client.get_paginator.assert_called_once_with('describe_stack_events')
    client.get_paginator().paginate.assert_called_once_with(
        StackName='TestStack')
    client.update_termination_protection.assert_not_called()

    captured = capsys.readouterr()

    assert 'Executing ChangeSet TestChangeSet for TestStack' in captured.out
    assert 'ChangeSet TestChangeSet for TestStack successfully completed:' in captured.out
    assert 'Timestamp            LogicalResourceId    ResourceType                ResourceStatus      Reason' in captured.out
    assert '2020-01-01 13:23:11  TestStack            AWS::CloudFormation::Stack  UPDATE_IN_PROGRESS  User Initiated' in captured.out
    assert '2020-01-01 13:24:53  Queue                AWS::SQS::Queue             CREATE_COMPLETE     -' in captured.out
    assert '2020-01-01 13:25:01  TestStack            AWS::CloudFormation::Stack  UPDATE_COMPLETE     -' in captured.out

    # We shouldn't have the previous events in the output
    assert 'AWS::CloudFormation::Stack  CREATE_COMPLETE' not in captured.out
Example #3
0
def test_delete_no_stack(config):
    attrs = {'describe_stacks.side_effect': STACK_DOES_NOT_EXIST}
    mock = MagicMock(**attrs)
    runner = Runner(mock, config)

    with pytest.raises(ValidationError, match='Stack TestStack not found'):
        runner.delete()
Example #4
0
def test_delete_client_error(client, config):
    client.configure_mock(
        **{'delete_stack.side_effect': ClientError({}, 'delete_stack')})
    runner = Runner(client, config)
    with pytest.raises(
            StackError,
            match=
            'An error occurred \\(Unknown\\) when calling the delete_stack operation.*'
    ):
        runner.delete()
Example #5
0
def test_print_stack_status_create_complete(client, config, monkeypatch,
                                            capsys):
    # Prevent differences in format depending upon where this runs
    monkeypatch.setenv('STACKMANAGER_TIMEZONE', 'UTC')

    runner = Runner(client, config)
    runner.print_stack_status()

    captured = capsys.readouterr()
    assert captured.out == '\nStack: TestStack, Status: CREATE_COMPLETE (2019-12-31 18:30:11)\n'
Example #6
0
def test_reject(client, config, capsys):
    runner = Runner(client, config)
    runner.reject_change_set()

    client.describe_change_set.assert_called_once_with(
        StackName='TestStack', ChangeSetName='TestChangeSet')
    client.delete_change_set.assert_called_once_with(
        StackName='TestStack', ChangeSetName='TestChangeSet')

    captured = capsys.readouterr()
    assert 'Deleting ChangeSet TestChangeSet for TestStack' in captured.out
Example #7
0
def test_apply_change_set_client_error(client, config):
    client.configure_mock(**{
        'execute_change_set.side_effect':
        ClientError({}, 'execute_change_set')
    })
    runner = Runner(client, config)
    with pytest.raises(
            StackError,
            match=
            'An error occurred \\(Unknown\\) when calling the execute_change_set .*'
    ):
        runner.apply_change_set()
Example #8
0
def test_deploy_failed_only_existing_changes(client, config):
    config._config['ExistingChanges'] = 'FAILED_ONLY'

    runner = Runner(client, config)
    with pytest.raises(
            ValidationError,
            match=
            'Creation of new ChangeSet not allowed when existing valid ChangeSets found'
    ):
        runner.deploy()

    client.list_change_sets.assert_called_once_with(StackName='TestStack')
Example #9
0
def test_delete(client, config, capsys):
    runner = Runner(client, config)
    runner.delete()

    assert runner.stack is None

    client.describe_stack_events.assert_called_once_with(StackName='TestStack')
    client.delete_stack.assert_called_once_with(StackName='TestStack',
                                                RetainResources=[])
    client.get_waiter.assert_called_once_with('stack_delete_complete')
    client.get_waiter().wait.assert_called_once_with(StackName='TestStack',
                                                     WaiterConfig={
                                                         'Delay': 10,
                                                         'MaxAttempts': 360
                                                     })
Example #10
0
def test_deploy_rollback_complete(client, config):
    describe_stacks = {
        'Stacks': [{
            'StackName': 'TestStack',
            'StackStatus': StackStatus.ROLLBACK_COMPLETE.name,
            'CreationTime': '2019-12-31T18:30:11.12345+0000'
        }]
    }
    client.configure_mock(**{'describe_stacks.return_value': describe_stacks})

    runner = Runner(client, config)
    runner.deploy()

    client.delete_stack.assert_called_once_with(StackName='TestStack',
                                                RetainResources=[])
Example #11
0
def test_load_stack_expired_token(config):
    ce = ClientError({'Error': {'Code': 'ExpiredToken'}}, 'describe_stacks')
    mock = MagicMock(**{'describe_stacks.side_effect': ce})

    with pytest.raises(StackError,
                       match='An error occurred \\(ExpiredToken\\).*'):
        Runner(mock, config)
Example #12
0
def test_load_stack_does_not_exist(config):
    mock = MagicMock(**{'describe_stacks.side_effect': STACK_DOES_NOT_EXIST})
    runner = Runner(mock, config)

    mock.describe_stacks.assert_called_once_with(StackName='TestStack')
    assert runner.stack is None
    assert runner.change_set_name == 'TestChangeSet'
Example #13
0
def test_deploy_invalid_status(client, config):
    describe_stacks = {
        'Stacks': [{
            'StackName': 'TestStack',
            'StackStatus': StackStatus.UPDATE_IN_PROGRESS.name,
            'CreationTime': '2019-12-31T18:30:11.12345+0000'
        }]
    }
    client.configure_mock(**{'describe_stacks.return_value': describe_stacks})

    runner = Runner(client, config)
    with pytest.raises(
            ValidationError,
            match=
            'Stack TestStack is not in a deployable status: UPDATE_IN_PROGRESS'
    ):
        runner.deploy()
Example #14
0
def test_deploy_waiter_failed(client, config):
    # Configure waiter to throw WaiterError for some unknown other reasons
    waiter_error = WaiterError('change_set_create_complete', 'Other reason', {
        'Status': 'FAILED',
        'StatusReason': 'Some other reason'
    })
    waiter_mock = MagicMock(**{'wait.side_effect': waiter_error})
    client.configure_mock(**{'get_waiter.return_value': waiter_mock})

    runner = Runner(client, config)

    with pytest.raises(
            StackError,
            match=
            'ChangeSet creation failed - Status: FAILED, Reason: Some other reason'
    ):
        runner.deploy()
Example #15
0
def test_status(client, config, capsys, monkeypatch):
    # Prevent differences in format depending upon where this runs
    monkeypatch.setenv('STACKMANAGER_TIMEZONE', 'UTC')

    before = datetime(2019, 12, 31, 0, 0, 0, 0, tzinfo=timezone.utc)
    now = datetime.now(tz=timezone.utc)

    runner = Runner(client, config)
    runner.status((now - before).days)

    captured = capsys.readouterr()

    assert 'Stack: TestStack, Status: CREATE_COMPLETE (2019-12-31 18:30:11)' in captured.out
    assert 'Existing ChangeSets:' in captured.out
    assert '2020-01-01 00:00:00: ExistingChangeSet (CREATE_COMPLETE)' in captured.out
    assert 'Events since 2019-12-31' in captured.out
    # Verify that the events are included
    assert '2020-01-01 11:58:20  Topic                AWS::SNS::Topic             CREATE_COMPLETE     -' in captured.out
Example #16
0
def test_deploy(client, config, capsys, monkeypatch):
    # Prevent differences in format depending upon where this runs
    monkeypatch.setenv('STACKMANAGER_TIMEZONE', 'UTC')

    runner = Runner(client, config)
    runner.deploy()

    client.list_change_sets.assert_called_once_with(StackName='TestStack')
    client.create_change_set.assert_called_once_with(
        StackName='TestStack',
        ChangeSetName='TestChangeSet',
        ChangeSetType='UPDATE',
        Parameters=[{
            'ParameterKey': 'Param1',
            'ParameterValue': 'Value1'
        }, {
            'ParameterKey': 'Param2',
            'UsePreviousValue': True
        }],
        Tags=[{
            'Key': 'Tag1',
            'Value': 'Value1'
        }],
        Capabilities=['CAPABILITY_IAM'],
        TemplateBody='AWSTemplateFormatVersion : "2010-09-09"')
    client.get_waiter.assert_called_once_with('change_set_create_complete')
    client.get_waiter().wait.assert_called_once_with(
        StackName='TestStack',
        ChangeSetName='TestChangeSet',
        WaiterConfig={
            'Delay': 5,
            'MaxAttempts': 120
        })
    client.describe_change_set.assert_called_once_with(
        ChangeSetName='TestChangeSet', StackName='TestStack')

    captured = capsys.readouterr()
    assert 'Stack: TestStack, Status: CREATE_COMPLETE' in captured.out
    assert 'Existing ChangeSets:\n  2020-01-01 00:00:00: ExistingChangeSet (CREATE_COMPLETE)' in captured.out
    assert 'Creating ChangeSet TestChangeSet' in captured.out
    assert 'Action    LogicalResourceId    ResourceType     Replacement' in captured.out
    assert 'Add       Queue                AWS::SQS::Queue  -' in captured.out
    assert 'ChangeSet TestChangeSet is ready to run' in captured.out
Example #17
0
def test_deploy_no_changes(client, config, capsys):
    # Configure waiter to throw WaiterError for FAILURE due to no changes
    waiter_error = WaiterError('change_set_create_complete', 'No Changes', {
        'Status': 'FAILED',
        'StatusReason': 'No updates are to be performed'
    })
    waiter_mock = MagicMock(**{'wait.side_effect': waiter_error})
    client.configure_mock(**{'get_waiter.return_value': waiter_mock})

    runner = Runner(client, config)
    runner.deploy()

    client.list_change_sets.assert_called_once_with(StackName='TestStack')
    client.create_change_set.assert_called_once()
    client.get_waiter.assert_called_once_with('change_set_create_complete')
    client.describe_change_set.assert_not_called()
    client.update_termination_protection.assert_not_called()

    captured = capsys.readouterr()
    assert 'No changes to Stack TestStack' in captured.out
Example #18
0
def test_print_stack_status_pending(config, monkeypatch, capsys):
    # Prevent differences in format depending upon where this runs
    monkeypatch.setenv('STACKMANAGER_TIMEZONE', 'UTC')

    describe_stacks = {
        'Stacks': [{
            'StackName':
            'TestStack',
            'StackStatus':
            StackStatus.REVIEW_IN_PROGRESS.name,
            'CreationTime':
            datetime(2019, 12, 31, 18, 29, 53, 64136, tzinfo=timezone.utc)
        }]
    }

    mock = MagicMock(**{'describe_stacks.return_value': describe_stacks})
    runner = Runner(mock, config)
    runner.print_stack_status()

    captured = capsys.readouterr()
    assert captured.out == '\nStack: TestStack, Status: REVIEW_IN_PROGRESS (2019-12-31 18:29:53)\n'
Example #19
0
def test_reject_delete_stack(client, config, capsys):
    # return no changesets
    client.configure_mock(
        **{'list_change_sets.return_value': {
            'Summaries': []
        }})

    runner = Runner(client, config)
    # Update the status
    runner.stack['StackStatus'] = StackStatus.REVIEW_IN_PROGRESS.name

    runner.reject_change_set()

    client.describe_change_set.assert_called_once_with(
        StackName='TestStack', ChangeSetName='TestChangeSet')
    client.delete_change_set.assert_called_once_with(
        StackName='TestStack', ChangeSetName='TestChangeSet')
    client.list_change_sets.assert_called_once_with(StackName='TestStack')
    client.delete_stack.assert_called_once_with(StackName='TestStack')

    captured = capsys.readouterr()
    assert 'Deleting REVIEW_IN_PROGRESS Stack TestStack that has no remaining ChangeSets' in captured.out
Example #20
0
def test_deploy_no_changes_enable_termination_protection(
        client, config, capsys):
    describe_stacks = {
        'Stacks': [{
            'StackName': 'TestStack',
            'StackStatus': StackStatus.CREATE_COMPLETE.name,
            'CreationTime': '2019-12-31T18:30:11.12345+0000',
            'EnableTerminationProtection': False
        }]
    }
    # Configure waiter to throw WaiterError for FAILURE due to no changes
    waiter_error = WaiterError('change_set_create_complete', 'No Changes', {
        'Status': 'FAILED',
        'StatusReason': 'No updates are to be performed'
    })
    waiter_mock = MagicMock(**{'wait.side_effect': waiter_error})
    client.configure_mock(
        **{
            'get_waiter.return_value': waiter_mock,
            'describe_stacks.return_value': describe_stacks
        })

    runner = Runner(client, config)
    runner.deploy()

    client.list_change_sets.assert_called_once_with(StackName='TestStack')
    client.create_change_set.assert_called_once()
    client.get_waiter.assert_called_once_with('change_set_create_complete')
    client.describe_change_set.assert_not_called()

    client.update_termination_protection.assert_called_once_with(
        StackName='TestStack', EnableTerminationProtection=True)

    captured = capsys.readouterr()
    assert 'No changes to Stack TestStack' in captured.out
    assert 'Enabled Termination Protection' in captured.out
Example #21
0
def test_load_stack_pending(config):
    describe_stacks = {
        'Stacks': [{
            'StackName':
            'TestStack',
            'StackStatus':
            StackStatus.REVIEW_IN_PROGRESS.name,
            'CreationTime':
            datetime(2019, 12, 31, 18, 29, 53, 64136, tzinfo=timezone.utc)
        }]
    }

    mock = MagicMock(**{'describe_stacks.return_value': describe_stacks})
    runner = Runner(mock, config)

    mock.describe_stacks.assert_called_once_with(StackName='TestStack')
    assert runner.stack is not None
Example #22
0
def test_status_no_events(client, config, capsys):
    runner = Runner(client, config)
    runner.status(7)

    captured = capsys.readouterr()
    assert 'No events' in captured.out
Example #23
0
def test_delete_waiter_error(client, config, capsys, monkeypatch):
    # Prevent differences in format depending upon where this runs
    monkeypatch.setenv('STACKMANAGER_TIMEZONE', 'UTC')

    # Configure waiter
    waiter_error = WaiterError('stack_delete_complete', 'Delete Failed', {
        'Status': 'FAILED',
        'StatusReason': 'Delete failed'
    })
    waiter_mock = MagicMock(**{'wait.side_effect': waiter_error})

    # Override Stack events
    stack_events = {
        'StackEvents': [{
            'Timestamp':
            datetime(2020, 1, 1, 13, 35, 11, 0, tzinfo=timezone.utc),
            'LogicalResourceId':
            'Topic',
            'ResourceType':
            'AWS::SNS::Topic',
            'ResourceStatus':
            'DELETE_FAILED',
            'ResourceStatusReason':
            'Something went wrong'
        }, {
            'Timestamp':
            datetime(2020, 1, 1, 12, 0, 0, 0, tzinfo=timezone.utc),
            'LogicalResourceId':
            'TestStack',
            'ResourceType':
            'AWS::CloudFormation::Stack',
            'ResourceStatus':
            'CREATE_COMPLETE'
        }, {
            'Timestamp':
            datetime(2020, 1, 1, 11, 58, 20, 12436, tzinfo=timezone.utc),
            'LogicalResourceId':
            'Topic',
            'ResourceType':
            'AWS::SNS::Topic',
            'ResourceStatus':
            'CREATE_COMPLETE'
        }]
    }
    paginator_mock = MagicMock(**{'paginate.return_value': [stack_events]})

    client.configure_mock(
        **{
            'get_waiter.return_value': waiter_mock,
            'get_paginator.return_value': paginator_mock
        })

    runner = Runner(client, config)
    with pytest.raises(
            StackError,
            match='Waiter stack_delete_complete failed: Delete Failed'):
        runner.delete()

    captured = capsys.readouterr()
    assert 'Deletion of Stack TestStack failed:' in captured.err
    assert '2020-01-01 13:35:11  Topic                AWS::SNS::Topic  DELETE_FAILED     Something went wrong' \
           in captured.out
Example #24
0
def test_get_output(client, config):
    runner = Runner(client, config)

    assert runner.get_output('TestOutputKey') == 'TestOutputValue'
Example #25
0
def test_get_output_not_found(client, config):
    runner = Runner(client, config)

    with pytest.raises(ValidationError, match='Output OtherKey not found'):
        runner.get_output('OtherKey')
Example #26
0
def test_load_stack(client, config):
    runner = Runner(client, config)

    client.describe_stacks.assert_called_once_with(StackName='TestStack')
    assert runner.stack is not None
    assert runner.change_set_name == 'TestChangeSet'