def test_describes_change_set(session, change_set): client_mock = Mock() session.return_value.client.return_value = client_mock cli.main(['describe', '--stack', STACK]) session.return_value.client.assert_called_with('cloudformation') change_set.assert_called_with(stack=STACK, client=client_mock) change_set.return_value.describe.assert_called_once()
def test_template_calls_template_with_yaml(tmpdir, logger): with Path(tmpdir): with open('test.template.json', 'w') as f: f.write('{"Description": "{{ \'test\' | title }}"}') cli.main(['template', '--yaml']) logger.info.assert_called() assert {"Description": "Test"} == yaml.safe_load(logger.info.call_args[0][0])
def test_with_organization_variables(aws_client, tmpdir, logger, paginators): aws_client.get_paginator.side_effect = paginators(list_accounts=[ACCOUNTS]) aws_client.describe_regions.return_value = EC2_REGIONS aws_client.get_caller_identity.return_value = {'Account': '1234'} example = '{"Resources": {"ModuleAccounts": {{ AWSAccounts | tojson }}, "ModuleSubAccounts": {{ AWSSubAccounts | tojson }}, "ModuleRegions": {{ AWSRegions | tojson }}, "ModuleMainAccount": {{ AWSMainAccount | tojson }}}}' with Path(tmpdir): os.mkdir('moduledir') with open('moduledir/test.template.json', 'w') as f: f.write(example) with open('test.template.json', 'w') as f: f.write( '{"Resources": {"AccountsRegionsTest": {"From": "Moduledir"}, "Accounts": {{ AWSAccounts | tojson}}, "SubAccounts": {{ AWSSubAccounts | tojson}}, "Regions": {{ AWSRegions | tojson }} }}') cli.main(['template', '--organization-variables']) logger.info.assert_called() output = logger.info.call_args[0][0] actual = yaml.safe_load(output) expected = {'Resources': {'Accounts': [{'Email': '*****@*****.**', 'Id': '1234', 'Name': 'TestName1'}, {'Email': '*****@*****.**', 'Id': '5678', 'Name': 'TestName2'}], 'SubAccounts': [{'Email': '*****@*****.**', 'Id': '5678', 'Name': 'TestName2'}], 'ModuleAccounts': [{'Email': '*****@*****.**', 'Id': '1234', 'Name': 'TestName1'}, {'Email': '*****@*****.**', 'Id': '5678', 'Name': 'TestName2'}], 'ModuleSubAccounts': [ {'Email': '*****@*****.**', 'Id': '5678', 'Name': 'TestName2'}], 'ModuleMainAccount': {'Email': '*****@*****.**', 'Id': '1234', 'Name': 'TestName1'}, 'ModuleRegions': ['us-west-1', 'us-west-2'], 'Regions': ['us-west-1', 'us-west-2']}} assert actual == expected
def test_remove_stack_set_instances(client, loader, wait, input): cli.main([ 'stack-set', 'remove-instances', '--stack-set', STACK, '--accounts', '123456789', '987654321', '--regions', 'eu-central-1', 'eu-west-1', '--retain', '--region-order', 'eu-central-1', 'eu-west-1', '--max-concurrent-percentage', '1', '--failure-tolerance-percentage', '1', ]) client.delete_stack_instances.assert_called_with( StackSetName=STACK, Accounts=['123456789', '987654321'], Regions=['eu-central-1', 'eu-west-1'], RetainStacks=True, OperationPreferences={ 'RegionOrder': ['eu-central-1', 'eu-west-1'], 'FailureTolerancePercentage': 1, 'MaxConcurrentPercentage': 1 }) wait.assert_called_with(STACK, OPERATION_ID)
def test_update_stack_set_with_main_account(session, client, logger, tmpdir, input, compare, wait): client.update_stack_set.return_value = {'OperationId': '12345'} accountid = str(uuid4()) client.get_caller_identity.return_value = {'Account': accountid} with Path(tmpdir): with open('test.template.json', 'w') as f: f.write('') cli.main([ 'stack-set', 'update', '--stack-set', STACK, '--main-account-parameter' ]) session.return_value.client.assert_any_call('sts') session.return_value.client.assert_any_call('cloudformation') client.update_stack_set.assert_called_with(StackSetName=STACK, TemplateBody=dump_template({ 'Parameters': { 'MainAccount': { 'Type': 'String', 'Default': accountid } } }))
def test_add_stack_set_instances_with_operation_preferences( client, loader, wait, input): cli.main([ 'stack-set', 'add-instances', '--accounts', '123456789', '987654321', '--regions', 'eu-central-1', 'eu-west-1', '--stack-set', STACK, '--max-concurrent-percentage', '1', '--failure-tolerance-percentage', '1', ]) client.create_stack_instances.assert_called_with( StackSetName=STACK, Accounts=['123456789', '987654321'], Regions=['eu-central-1', 'eu-west-1'], OperationPreferences={ 'FailureTolerancePercentage': 1, 'MaxConcurrentPercentage': 1 })
def test_loads_empty_config_file(mocker, tmpdir, session): stacks = mocker.patch('formica.cli.stacks') file_name = 'test.config.yaml' with Path(tmpdir): with open(file_name, 'w') as f: f.write('') cli.main(['stacks', '-c', file_name])
def test_print_stacks(session, logger): client_mock = Mock() session.return_value.client.return_value = client_mock client_mock.get_paginator.return_value.paginate.return_value = [ LIST_STACK_RESOURCES ] cli.main(['resources', '--stack', STACK]) client_mock.get_paginator.assert_called_with('list_stack_resources') client_mock.get_paginator.return_value.paginate.assert_called_with( StackName=STACK) logger.info.assert_called_with(mock.ANY) args = logger.info.call_args[0] to_search = [] to_search.extend(RESOURCE_HEADERS) to_search.extend(['AWS::Route53::HostedZone']) to_search.extend(['FlomotlikMe']) to_search.extend(['CREATE_COMPLETE']) to_search.extend(['ZAYGDOKFPYFK6']) change_set_output = args[0] for term in to_search: assert term in change_set_output assert 'None' not in change_set_output
def test_create_stack_set(client, logger, loader): cli.main([ 'stack-set', 'create', '--stack-set', STACK, '--parameters', 'A=B', 'B=C', '--tags', 'A=B', 'B=C', '--capabilities', 'CAPABILITY_IAM', '--execution-role-name', 'ExecutionRole', '--administration-role-arn', 'AdministrationRole', ]) client.create_stack_set.assert_called_with( StackSetName=STACK, TemplateBody=TEMPLATE, Parameters=CLOUDFORMATION_PARAMETERS, Tags=CLOUDFORMATION_TAGS, Capabilities=['CAPABILITY_IAM'], ExecutionRoleName='ExecutionRole', AdministrationRoleARN='AdministrationRole')
def test_new_uses_tags_for_creation(change_set, client, loader): loader.return_value.template.return_value = TEMPLATE cli.main([ 'new', '--stack', STACK, '--tags', 'A=C', 'C=D', '--profile', PROFILE, '--region', REGION, ]) change_set.assert_called_with(stack=STACK, nested_change_sets=False) change_set.return_value.create.assert_called_once_with( template=TEMPLATE, change_set_type='CREATE', parameters={}, tags={ 'A': 'C', 'C': 'D' }, capabilities=None, resource_types=False, role_arn=None, s3=False)
def test_does_not_execute_changeset_if_in_failed_state(stack_waiter, client): client.describe_change_set.return_value = {'Status': 'FAILED'} client.describe_stack_events.return_value = {'StackEvents': [{'EventId': EVENT_ID}]} client.describe_stacks.return_value = {'Stacks': [{'StackId': STACK_ID}]} with pytest.raises(SystemExit): cli.main(['deploy', '--stack', STACK]) client.execute_change_set.assert_not_called()
def test_exception_with_failed_yaml_syntax(mocker, tmpdir, session, logger): file_name = 'test.config.yaml' with Path(tmpdir): with open(file_name, 'w') as f: f.write("stacks: somestack\nprofile testprofile") with pytest.raises(SystemExit): cli.main(['stacks', '-c', file_name]) logger.error.assert_called()
def test_change_tests_tag_format(capsys): with pytest.raises(SystemExit) as pytest_wrapped_e: cli.main(['change', '--stack', STACK, '--parameters', 'A=B', '--profile', PROFILE, '--region', REGION, '--tags', 'CD']) out, err = capsys.readouterr() assert "argument --tags: CD needs to be in format KEY=VALUE" in err assert pytest_wrapped_e.value.code == 2
def test_wait(client, stack_waiter): client.describe_stacks.return_value = {'Stacks': [{'StackId': STACK_ID}]} client.describe_stack_events.return_value = { 'StackEvents': [{ 'EventId': EVENT_ID }] } cli.main(['wait', '--stack', STACK])
def test_change_uses_capabilities_for_creation(change_set, client, loader): loader.return_value.template.return_value = TEMPLATE cli.main(['change', '--stack', STACK, '--capabilities', 'A', 'B']) change_set.assert_called_with(stack=STACK, client=client) change_set.return_value.create.assert_called_once_with(template=TEMPLATE, change_set_type='UPDATE', parameters={}, tags={}, capabilities=['A', 'B'], role_arn=None, s3=False, resource_types=False)
def test_change_with_role_arn(change_set, client, loader): loader.return_value.template.return_value = TEMPLATE cli.main(['change', '--stack', STACK, '--role-arn', ROLE_ARN]) change_set.assert_called_with(stack=STACK, client=client) change_set.return_value.create.assert_called_once_with(template=TEMPLATE, change_set_type='UPDATE', parameters={}, tags={}, capabilities=None, role_arn=ROLE_ARN, s3=False, resource_types=False)
def test_executes_change_set_with_timeout(stack_waiter, client): client.describe_change_set.return_value = {'Status': 'CREATE_COMPLETE'} client.describe_stack_events.return_value = {'StackEvents': [{'EventId': EVENT_ID}]} client.describe_stacks.return_value = {'Stacks': [{'StackId': STACK_ID}]} cli.main(['deploy', '--stack', STACK, '--profile', PROFILE, '--region', REGION, '--timeout', '15']) stack_waiter.assert_called_with(STACK_ID, timeout=15) stack_waiter.return_value.wait.assert_called_with(EVENT_ID)
def test_cancel_stack_update(client, stack_waiter): client.describe_stacks.return_value = {'Stacks': [{'StackId': STACK_ID}]} client.describe_stack_events.return_value = { 'StackEvents': [{ 'EventId': EVENT_ID }] } cli.main(['cancel', '--stack', STACK]) client.cancel_update_stack.assert_called_with(StackName=STACK)
def test_exception_with_wrong_config_type(mocker, tmpdir, session, logger): file_name = 'test.config.yaml' with Path(tmpdir): with open(file_name, 'w') as f: f.write(yaml.dump({'stack': ['test', 'test2']})) with pytest.raises(SystemExit): cli.main(['stacks', '-c', file_name]) logger.error.assert_called_with( 'Config file parameter stack needs to be of type str')
def test_fails_if_no_stack_given(logger): with pytest.raises(SystemExit) as pytest_wrapped_e: cli.main(['deploy']) assert pytest_wrapped_e.value.code == 1 logger.error.assert_called() out = logger.error.call_args[0][0] assert '--stack' in out assert '--config-file' in out
def test_remove_stack_set(client, loader): cli.main([ 'stack-set', 'remove', '--stack-set', STACK, ]) client.delete_stack_set.assert_called_with(StackSetName=STACK)
def test_change_with_role_name_and_arn(change_set, client, loader): client.get_caller_identity.return_value = {'Account': ACCOUNT_ID} loader.return_value.template.return_value = TEMPLATE cli.main(['change', '--stack', STACK, '--role-name', 'UnusedRole', '--role-arn', ROLE_ARN]) change_set.assert_called_with(stack=STACK, client=client) change_set.return_value.create.assert_called_once_with(template=TEMPLATE, change_set_type='UPDATE', parameters={}, tags={}, capabilities=None, role_arn=ROLE_ARN, s3=False, resource_types=False)
def test_change_creates_update_change_set(change_set, client, loader): loader.return_value.template.return_value = TEMPLATE cli.main(['change', '--stack', STACK, '--profile', PROFILE, '--region', REGION]) change_set.assert_called_with(stack=STACK, client=client) change_set.return_value.create.assert_called_once_with(template=TEMPLATE, change_set_type='UPDATE', parameters={}, tags={}, capabilities=None, role_arn=None, s3=False, resource_types=False) change_set.return_value.describe.assert_called_once()
def test_diff_cli_with_vars(template, mocker): diff = mocker.patch('formica.diff.compare_stack') cli.main([ 'diff', '--stack', STACK, '--vars', 'V=1', '--parameters', 'P=2', '--tags', 'T=3' ]) diff.assert_called_with(stack=STACK, vars={'V': '1'}, parameters={'P': '2'}, tags={'T': '3'})
def test_diff_cli_with_vars(mocker, session): aws = mocker.patch('formica.cli.AWS') aws.current_session.return_value = session diff = mocker.patch('formica.cli.Diff') cli.main(['diff', '--stack', STACK, '--vars', 'abc=def']) diff.assert_called_with(session) diff.return_value.run.assert_called_with(STACK, {'abc': 'def'})
def test_diff_cli_call(template, mocker, client, session): diff = mocker.patch('formica.diff.compare_stack_set') cli.main([ 'stack-set', 'diff', '--stack-set', STACK, '--main-account-parameter' ]) diff.assert_called_with(stack=STACK, parameters={}, vars={}, tags={}, main_account_parameter=True)
def test_exception_with_forbiddeng_config_argument(mocker, tmpdir, session, logger): file_name = 'test.config.yaml' with Path(tmpdir): with open(file_name, 'w') as f: f.write(yaml.dump({'stacks': 'somestack'})) with pytest.raises(SystemExit): cli.main(['stacks', '-c', file_name]) logger.error.assert_called_with( 'Config file parameter stacks is not supported')
def test_diff_cli_call(mocker, session): aws = mocker.patch('formica.cli.AWS') aws.current_session.return_value = session diff = mocker.patch('formica.cli.Diff') cli.main(['diff', '--stack', STACK]) diff.assert_called_with(session) diff.return_value.run.assert_called_with(STACK, mocker.ANY)
def test_does_not_execute_changeset_if_no_changes(stack_waiter, client): client.describe_change_set.return_value = {'Status': 'FAILED', "StatusReason": "The submitted information didn't contain changes. Submit different information to create a change set."} client.describe_stack_events.return_value = {'StackEvents': [{'EventId': EVENT_ID}]} client.describe_stacks.return_value = {'Stacks': [{'StackId': STACK_ID}]} cli.main(['deploy', '--stack', STACK]) client.execute_change_set.assert_not_called() stack_waiter.assert_called_with(STACK_ID) stack_waiter.return_value.wait.assert_called_with(EVENT_ID)
def test_new_tests_parameter_format(capsys): with pytest.raises(SystemExit) as pytest_wrapped_e: cli.main([ 'new', '--stack', STACK, '--parameters', 'A=B', 'CD', '--profile', PROFILE, '--region', REGION ]) assert pytest_wrapped_e.value.code == 2 out, err = capsys.readouterr() assert 'needs to be in format KEY=VALUE' in err assert pytest_wrapped_e.value.code == 2