def test_do_not_remove_changeset_if_non_existent(): cf_client_mock = Mock() change_set = ChangeSet(STACK, cf_client_mock) exception = ClientError(dict(Error=dict(Code='ChangeSetNotFound')), "DescribeChangeSet") cf_client_mock.describe_change_set.side_effect = exception change_set.remove_existing_changeset() cf_client_mock.delete_change_set.assert_not_called()
def test_creates_and_removes_bucket_for_s3_flag(client, uuid): change_set = ChangeSet(STACK, client) change_set.create(template=TEMPLATE, change_set_type=CHANGE_SET_TYPE, s3=True) bucket_name = 'formica-deploy-{}'.format(UUID) bucket_path = '{}-template.json'.format(STACK) template_url = 'https://{}.s3.amazonaws.com/{}'.format( bucket_name, bucket_path) client.create_bucket.assert_called_with( Bucket=bucket_name, CreateBucketConfiguration=dict(LocationConstraint=REGION)) client.put_object.assert_called_with(Bucket=bucket_name, Key=bucket_path, Body=TEMPLATE) client.create_change_set.assert_called_with(StackName=STACK, TemplateURL=template_url, ChangeSetName=CHANGESETNAME, ChangeSetType=CHANGE_SET_TYPE) client.delete_object.assert_called_with(Bucket=bucket_name, Key=bucket_path) client.delete_bucket.assert_called_with(Bucket=bucket_name)
def test_reraises_exception_when_not_change_set_not_found(client): change_set = ChangeSet(STACK) exception = ClientError(dict(Error=dict( Code='ValidationError')), "DescribeChangeSet") client.describe_change_set.side_effect = exception with pytest.raises(ClientError): change_set.remove_existing_changeset()
def test_submits_changeset_with_parameters(): cf_client_mock = Mock() change_set = ChangeSet(STACK, cf_client_mock) change_set.create(template=TEMPLATE, change_set_type=CHANGE_SET_TYPE, parameters=CHANGE_SET_PARAMETERS) Parameters = [{ 'ParameterKey': 'A', 'ParameterValue': 'B', 'UsePreviousValue': False }, { 'ParameterKey': 'B', 'ParameterValue': 'C', 'UsePreviousValue': False }] cf_client_mock.create_change_set.assert_called_with( StackName=STACK, TemplateBody=TEMPLATE, ChangeSetName=CHANGESETNAME, ChangeSetType=CHANGE_SET_TYPE, Parameters=Parameters) cf_client_mock.get_waiter.assert_called_with('change_set_create_complete') cf_client_mock.get_waiter.return_value.wait.assert_called_with( StackName=STACK, ChangeSetName=CHANGESETNAME)
def test_remove_existing_changeset_for_update_type(mocker, capsys): mocker.patch.object(ChangeSet, 'describe') cf_client_mock = Mock() change_set = ChangeSet(STACK, cf_client_mock) change_set.create(template=TEMPLATE, change_set_type='UPDATE') cf_client_mock.describe_change_set.assert_called_with(StackName=STACK, ChangeSetName=CHANGESETNAME) cf_client_mock.delete_change_set.assert_called_with(StackName=STACK, ChangeSetName=CHANGESETNAME)
def test_prints_changes(logger): cf_client_mock = Mock() cf_client_mock.describe_change_set.return_value = CHANGESETCHANGES change_set = ChangeSet(STACK, cf_client_mock) change_set.describe() change_set_output = '\n'.join([call[1][0] for call in logger.info.mock_calls]) to_search = [] to_search.extend(CHANGE_SET_HEADER) to_search.extend(['Remove', 'Modify', 'Add']) to_search.extend(['DeploymentBucket', 'DeploymentBucket2', 'DeploymentBucket3']) to_search.extend(['simpleteststack-deploymentbucket-1l7p61v6fxpry ', 'simpleteststack-deploymentbucket2-11ngaeftydtn7 ']) to_search.extend(['AWS::S3::Bucket']) to_search.extend(['True']) to_search.extend(['BucketName, Tags']) # Parameters to_search.extend(['bucketname=formicatestbucketname, bucketname2=formicatestbucketname2']) # Capabilities to_search.extend(['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM']) # Stack Tags to_search.extend(['StackKey=StackValue, StackKey2=StackValue2']) for term in to_search: assert term in change_set_output assert 'None' not in change_set_output
def test_change_set_with_previous_template(client): change_set = ChangeSet(STACK) change_set.create(change_set_type=CHANGE_SET_TYPE, use_previous_template=True) client.create_change_set.assert_called_with( StackName=STACK, ChangeSetName=CHANGESETNAME, ChangeSetType=CHANGE_SET_TYPE, UsePreviousTemplate=True, IncludeNestedStacks=False)
def test_only_prints_unique_changed_parameters(logger, client): client.describe_change_set.return_value = CHANGESETCHANGES_WITH_DUPLICATE_CHANGED_PARAMETER change_set = ChangeSet(STACK) change_set.describe() change_set_output = logger.info.call_args[0][0] assert change_set_output.count('BucketName') == 1
def test_remove_existing_changeset_for_update_type(mocker, capsys, client, change_set_not_found, time): mocker.patch.object(ChangeSet, 'describe') change_set = ChangeSet(STACK) client.describe_change_set.side_effect = [ {"ChangeSetId": CHANGESETNAME}, {}, change_set_not_found] change_set.create(template=TEMPLATE, change_set_type='UPDATE') client.describe_change_set.assert_called_with(StackName=STACK, ChangeSetName=CHANGESETNAME) client.delete_change_set.assert_called_with(ChangeSetName=CHANGESETNAME) time.sleep.assert_called_with(5)
def test_prints_error_message_but_exits_successfully_for_no_changes(capsys, logger, mocker, client): change_set = ChangeSet(STACK) status_reason = "The submitted information didn't contain changes. " \ "Submit different information to create a change set." error = WaiterError('name', 'reason', {'StatusReason': status_reason}) client.get_waiter.return_value.wait.side_effect = error change_set.create(template=TEMPLATE, change_set_type=CHANGE_SET_TYPE) logger.info.assert_called_with(status_reason)
def test_prints_error_message_and_does_not_fail_without_StatusReason(capsys, logger, client): change_set = ChangeSet(STACK) error = WaiterError('name', 'reason', {}) client.get_waiter.return_value.wait.side_effect = error with pytest.raises(SystemExit) as pytest_wrapped_e: change_set.create(template=TEMPLATE, change_set_type=CHANGE_SET_TYPE) logger.info.assert_called() assert pytest_wrapped_e.value.code == 1
def test_prints_error_message_for_failed_submit_and_exits(capsys, logger): cf_client_mock = Mock() change_set = ChangeSet(STACK, cf_client_mock) error = WaiterError('name', 'reason', {'StatusReason': 'StatusReason'}) cf_client_mock.get_waiter.return_value.wait.side_effect = error with pytest.raises(SystemExit) as pytest_wrapped_e: change_set.create(template=TEMPLATE, change_set_type=CHANGE_SET_TYPE) logger.info.assert_called_with('StatusReason') assert pytest_wrapped_e.value.code == 1
def test_submits_changeset_and_waits(client): change_set = ChangeSet(STACK) change_set.create(template=TEMPLATE, change_set_type=CHANGE_SET_TYPE) client.create_change_set.assert_called_with( StackName=STACK, TemplateBody=TEMPLATE, ChangeSetName=CHANGESETNAME, ChangeSetType=CHANGE_SET_TYPE, IncludeNestedStacks=False) client.get_waiter.assert_called_with( 'change_set_create_complete') client.get_waiter.return_value.wait.assert_called_with( StackName=STACK, ChangeSetName=CHANGESETNAME, WaiterConfig={'Delay': 10, 'MaxAttempts': 120})
def test_change_set_with_resource_types(): resources = { 'Resources': {resource: {'Type': resource} for resource in RESOURCES} } template = json.dumps(resources) cf_client_mock = Mock() change_set = ChangeSet(STACK, cf_client_mock) change_set.create(template=template, change_set_type=CHANGE_SET_TYPE, resource_types=True) cf_client_mock.create_change_set.assert_called_with( StackName=STACK, TemplateBody=template, ChangeSetName=CHANGESETNAME, ChangeSetType=CHANGE_SET_TYPE, ResourceTypes=list(set(RESOURCES)))
def test_submits_changeset_and_waits(): cf_client_mock = Mock() change_set = ChangeSet(STACK, cf_client_mock) change_set.create(template=TEMPLATE, change_set_type=CHANGE_SET_TYPE) cf_client_mock.create_change_set.assert_called_with( StackName=STACK, TemplateBody=TEMPLATE, ChangeSetName=CHANGESETNAME, ChangeSetType=CHANGE_SET_TYPE) cf_client_mock.get_waiter.assert_called_with( 'change_set_create_complete') cf_client_mock.get_waiter.return_value.wait.assert_called_with( StackName=STACK, ChangeSetName=CHANGESETNAME)
def test_change_set_without_named_properties(client): CHANGESET = copy.deepcopy(CHANGESETCHANGES) # Find a property change in the changeset PROPERTY_CHANGE = CHANGESET['Changes'][1]['ResourceChange']['Details'][1] assert PROPERTY_CHANGE['Target']['Attribute'] == 'Properties' assert PROPERTY_CHANGE['Target']['Name'] == 'BucketName' # Remove the "Name" attribute del PROPERTY_CHANGE['Target']['Name'] client.describe_change_set.return_value = CHANGESET change_set = ChangeSet(STACK) change_set.describe()
def test_creates_and_removes_bucket_for_s3_flag(client, temp_bucket_function, temp_bucket): change_set = ChangeSet(STACK) bucket_name = 'formica-deploy-{}'.format("test") bucket_path = '{}-template.json'.format(STACK) template_url = 'https://{}.s3.amazonaws.com/{}'.format(bucket_name, bucket_path) temp_bucket.add.return_value = bucket_path temp_bucket.name = bucket_name change_set.create(template=TEMPLATE, change_set_type=CHANGE_SET_TYPE, s3=True) temp_bucket.add.assert_called_with(TEMPLATE) temp_bucket_function.assert_called_with(STACK) client.create_change_set.assert_called_with( StackName=STACK, TemplateURL=template_url, ChangeSetName=CHANGESETNAME, ChangeSetType=CHANGE_SET_TYPE, IncludeNestedStacks=False)
def test_submits_changeset_with_stack_tags(client): change_set = ChangeSet(STACK) change_set.create(template=TEMPLATE, change_set_type=CHANGE_SET_TYPE, tags=CHANGE_SET_STACK_TAGS) Tags = [ {'Key': 'T1', 'Value': 'TV1'}, {'Key': 'T2', 'Value': 'TV2'} ] client.create_change_set.assert_called_with( StackName=STACK, TemplateBody=TEMPLATE, ChangeSetName=CHANGESETNAME, ChangeSetType=CHANGE_SET_TYPE, Tags=Tags, IncludeNestedStacks=False) client.get_waiter.assert_called_with( 'change_set_create_complete') client.get_waiter.return_value.wait.assert_called_with( StackName=STACK, ChangeSetName=CHANGESETNAME, WaiterConfig={'Delay': 10, 'MaxAttempts': 120})
def test_submits_changeset_with_stack_tags(): cf_client_mock = Mock() change_set = ChangeSet(STACK, cf_client_mock) change_set.create(template=TEMPLATE, change_set_type=CHANGE_SET_TYPE, tags=CHANGE_SET_STACK_TAGS) Tags = [ {'Key': 'T1', 'Value': 'TV1'}, {'Key': 'T2', 'Value': 'TV2'} ] cf_client_mock.create_change_set.assert_called_with( StackName=STACK, TemplateBody=TEMPLATE, ChangeSetName=CHANGESETNAME, ChangeSetType=CHANGE_SET_TYPE, Tags=Tags) cf_client_mock.get_waiter.assert_called_with( 'change_set_create_complete') cf_client_mock.get_waiter.return_value.wait.assert_called_with( StackName=STACK, ChangeSetName=CHANGESETNAME)
def test_submits_changeset_with_parameters(client): change_set = ChangeSet(STACK) change_set.create(template=TEMPLATE, change_set_type=CHANGE_SET_TYPE, parameters=CHANGE_SET_PARAMETERS) Parameters = [ {'ParameterKey': 'A', 'ParameterValue': 'B', 'UsePreviousValue': False}, {'ParameterKey': 'B', 'ParameterValue': '2', 'UsePreviousValue': False}, {'ParameterKey': 'C', 'ParameterValue': 'True', 'UsePreviousValue': False}, ] client.create_change_set.assert_called_with( StackName=STACK, TemplateBody=TEMPLATE, ChangeSetName=CHANGESETNAME, ChangeSetType=CHANGE_SET_TYPE, Parameters=Parameters, IncludeNestedStacks=False) client.get_waiter.assert_called_with( 'change_set_create_complete') client.get_waiter.return_value.wait.assert_called_with( StackName=STACK, ChangeSetName=CHANGESETNAME, WaiterConfig={'Delay': 10, 'MaxAttempts': 120})
def test_change_set_with_previous_parameters(client): Parameters = [ {'ParameterKey': 'A', 'UsePreviousValue': True}, {'ParameterKey': 'B', 'UsePreviousValue': True}, {'ParameterKey': 'C', 'ParameterValue': '12345', 'UsePreviousValue': False}, {'ParameterKey': 'D', 'ParameterValue': '7890', 'UsePreviousValue': False}, ] client.describe_stacks.return_value = { 'Stacks': [{'Parameters': [{'ParameterKey': 'A'}, {'ParameterKey': 'B'}]}] } change_set = ChangeSet(STACK) change_set.create(change_set_type=CHANGE_SET_TYPE, use_previous_template=True, use_previous_parameters=True, parameters={'C': '12345', 'D': '7890'}) client.describe_stacks.assert_called_once_with(StackName=STACK) client.create_change_set.assert_called_with( StackName=STACK, ChangeSetName=CHANGESETNAME, ChangeSetType=CHANGE_SET_TYPE, Parameters=Parameters, UsePreviousTemplate=True, IncludeNestedStacks=False)
def test_prints_nested_changes(logger, client): client.describe_change_set.side_effect = [CHANGESET_NESTED_CHANGES, CHANGESETCHANGES] change_set = ChangeSet(STACK) change_set.describe() change_set_output = '\n'.join([call[1][0] for call in logger.info.mock_calls]) print(change_set_output) to_search = [] to_search.extend(CHANGE_SET_HEADER) to_search.extend(['Changes for nested Stack: NestedStack']) to_search.extend(['NestedStack']) to_search.extend(['Remove', 'Modify', 'Add']) to_search.extend(['DeploymentBucket', 'DeploymentBucket2', 'DeploymentBucket3']) to_search.extend(['simpleteststack-deploymentbucket-1l7p61v6fxpry ', 'simpleteststack-deploymentbucket2-11ngaeftydtn7 ']) to_search.extend(['AWS::S3::Bucket']) to_search.extend(['True']) to_search.extend(['BucketName, Tags']) for term in to_search: assert term in change_set_output assert 'None' not in change_set_output
def change(args): client = AWS.current_session().client('cloudformation') loader = Loader() loader.load() change_set = ChangeSet(stack=args.stack, client=client) change_set.create(template=loader.template(), change_set_type='UPDATE', parameters=args.parameters, tags=args.tags, capabilities=args.capabilities) change_set.describe()
def new(args): client = AWS.current_session().client('cloudformation') loader = Loader() loader.load() logger.info('Creating change set for new stack, ...') change_set = ChangeSet(stack=args.stack, client=client) change_set.create(template=loader.template(), change_set_type='CREATE', parameters=args.parameters, tags=args.tags, capabilities=args.capabilities) change_set.describe() logger.info('Change set created, please deploy')
def describe(args): client = AWS.current_session().client('cloudformation') change_set = ChangeSet(stack=args.stack, client=client) change_set.describe()
def test_do_not_remove_changeset_if_non_existent(client, change_set_not_found): change_set = ChangeSet(STACK) client.describe_change_set.side_effect = change_set_not_found change_set.remove_existing_changeset() client.delete_change_set.assert_not_called()
def test_exception_if_change_set_not_deleted(client, time): change_set = ChangeSet(STACK) with pytest.raises(Exception) as pytest_exception: change_set.remove_existing_changeset()
def test_prints_nested_changes(logger, client): client.describe_change_set.side_effect = [CHANGESET_NESTED_STACK_NO_NESTED_CHANGESET] change_set = ChangeSet(STACK) change_set.describe()