def test_deployment_access_arn_discovered_with_deleted(self): mock_deployment_access_stack_arn = 'deployment-access-stack-arn' mock_describe_stacks_result = { 'Stacks': [{ 'StackId': 'wrong-stack-id', 'StackStatus': 'DELETE_COMPLETE' }, { 'StackId': mock_deployment_access_stack_arn, 'StackStatus': 'UPDATE_COMPLETE' }] } mock_deployment_access_stack_name = MOCK_STACK_NAME + '-Access' mock_deployment_access = 'test-deployment-access' with mock.patch('stack_info.DeploymentAccessInfo', return_value=mock_deployment_access ) as mock_DeploymentAccessInfo: with mock_aws.patch_client('cloudformation', 'describe_stacks', return_value=mock_describe_stacks_result ) as mock_describe_stacks: target = stack_info.DeploymentInfo(MOCK_STACK_ARN) actual_deployment_access = target.deployment_access self.assertIs(actual_deployment_access, mock_deployment_access) mock_DeploymentAccessInfo.assert_called_once_with( mock_deployment_access_stack_arn, deployment_info=target, client=target.client) mock_describe_stacks.assert_called_once_with( StackName=mock_deployment_access_stack_name)
def test_deployment_name(self): mock_deployment_name = 'test-deployment' mock_parameters = {'DeploymentName': mock_deployment_name} with mock.patch('stack_info.DeploymentInfo.parameters', new=mock.PropertyMock(return_value=mock_parameters)): target = stack_info.DeploymentInfo(MOCK_STACK_ARN) self.assertEquals(target.deployment_name, mock_deployment_name)
def test_deployment_access_provided(self): mock_deployment_access = 'test-deployment-access' target = stack_info.DeploymentInfo( MOCK_STACK_ARN, deployment_access_info=mock_deployment_access, client=MOCK_CLIENT) actual_deployment_access = target.deployment_access self.assertIs(actual_deployment_access, mock_deployment_access)
def handler(event, context): props = properties.load(event, { 'ConfigurationBucket': properties.String(), # Currently not used 'ConfigurationKey': properties.String(), # Depend on unique upload id in key to force Cloud Formation to call handler 'RoleLogicalId': properties.String(), 'MetadataKey': properties.String(), 'PhysicalResourceId': properties.String(), 'UsePropagationDelay': properties.String(), 'RequireRoleExists': properties.String(default='true'), 'ResourceGroupStack': properties.String(default=''), 'DeploymentStack': properties.String(default='')}) if props.ResourceGroupStack is '' and props.DeploymentStack is '': raise ValidationError('A value for the ResourceGroupStack property or the DeploymentStack property must be provided.') if props.ResourceGroupStack is not '' and props.DeploymentStack is not '': raise ValidationError('A value for only the ResourceGroupStack property or the DeploymentStack property can be provided.') use_propagation_delay = props.UsePropagationDelay.lower() == 'true' data = {} stack_infos = [] if props.ResourceGroupStack is not '': resource_group_info = stack_info.ResourceGroupInfo(props.ResourceGroupStack) # create a list of stack-infos, starting at the resource group level and working our way upward stack_infos = _build_stack_infos_list(resource_group_info) else: # DeploymentStack deployment_info = stack_info.DeploymentInfo(props.DeploymentStack) # create a list of stack-infos, starting at the deployment level and working our way upward stack_infos = _build_stack_infos_list(deployment_info) # go through each of the stack infos, trying to find the specified role for stack in stack_infos: role = stack.resources.get_by_logical_id(props.RoleLogicalId, expected_type='AWS::IAM::Role', optional=True) if role is not None: break role_physical_id = None if role is not None: role_physical_id = role.physical_id if role_physical_id is None: if props.RequireRoleExists.lower() == 'true': raise ValidationError('Could not find role \'{}\'.'.format(props.RoleLogicalId)) else: if type(stack_infos[0]) is stack_info.ResourceGroupInfo: _process_resource_group_stack(event['RequestType'], stack_infos[0], role_physical_id, props.MetadataKey, use_propagation_delay) else: for resource_group_info in stack_infos[0].resource_groups: _process_resource_group_stack(event['RequestType'], resource_group_info, role_physical_id, props.MetadataKey, use_propagation_delay) custom_resource_response.succeed(event, context, data, props.PhysicalResourceId)
def test_constructor(self): target = stack_info.DeploymentInfo( MOCK_STACK_ARN, client=MOCK_CLIENT, stack_description=MOCK_STACK_DESCRIPTION) self.assertEquals(target.stack_type, stack_info.StackInfo.STACK_TYPE_DEPLOYMENT) self.assertIs(target.client, MOCK_CLIENT) self.assertIs(target.stack_description, MOCK_STACK_DESCRIPTION)
def test_deployment_access_arn_discovered_with_access_denied(self): mock_describe_stacks_result = ClientError( {'Error': { 'Code': 'ValidationError' }}, 'describe-stacks') mock_deployment_access_stack_name = MOCK_STACK_NAME + '-Access' with mock_aws.patch_client('cloudformation', 'describe_stacks', side_effect=mock_describe_stacks_result ) as mock_describe_stacks: target = stack_info.DeploymentInfo(MOCK_STACK_ARN) actual_deployment_access = target.deployment_access self.assertIsNone(actual_deployment_access) mock_describe_stacks.assert_called_once_with( StackName=mock_deployment_access_stack_name)
def test_resource_groups(self): mock_physical_resource_id_1 = 'mock_physical_resource_id_1' mock_physical_resource_id_2 = 'mock_physical_resource_id_2' mock_logical_resource_id_1 = 'mock_logical_id_1' mock_logical_resource_id_2 = 'mock_logical_id_2' mock_resource_1 = mock.MagicMock() mock_resource_1.physical_id = mock_physical_resource_id_1 mock_resource_1.logical_id = mock_logical_resource_id_1 mock_resource_1.type = 'AWS::CloudFormation::Stack' mock_resource_2 = mock.MagicMock() mock_resource_2.physical_id = mock_physical_resource_id_2 mock_resource_2.logical_id = mock_logical_resource_id_2 mock_resource_2.type = 'AWS::CloudFormation::Stack' mock_resource_3 = mock.MagicMock() mock_resource_3.physical_id = 'not-used' mock_resource_3.logical_id = 'not-used' mock_resource_3.type = 'not-a-stack' mock_resource_4 = mock.MagicMock() mock_resource_4.physical_id = 'not-used' mock_resource_4.logical_id = 'not-used' mock_resource_4.type = 'not-a-stack' mock_resources = [ mock_resource_3, mock_resource_1, mock_resource_4, mock_resource_2 ] mock_resource_group_1 = 'test-resource-group-1' mock_resource_group_2 = 'test-resource-group-2' mock_resource_groups = [mock_resource_group_1, mock_resource_group_2] with mock.patch( 'stack_info.ResourceGroupInfo', side_effect=mock_resource_groups) as mock_ResourceGroupInfo: with mock.patch('stack_info.DeploymentInfo.resources', new_callable=mock.PropertyMock, return_value=mock_resources): target = stack_info.DeploymentInfo(MOCK_STACK_ARN, client=MOCK_CLIENT) actual_resource_groups = target.resource_groups self.assertItemsEqual(actual_resource_groups, mock_resource_groups) mock_ResourceGroupInfo.assert_any_call( mock_physical_resource_id_1, resource_group_name=mock_logical_resource_id_1, client=target.client, deployment_info=target) mock_ResourceGroupInfo.assert_any_call( mock_physical_resource_id_2, resource_group_name=mock_logical_resource_id_2, client=target.client, deployment_info=target)
def test_deployment_access_arn_discovered_with_not_access_denied(self): mock_error_code = 'SomeErrorCode' mock_describe_stacks_result = ClientError( {'Error': { 'Code': mock_error_code }}, 'describe-stacks') mock_deployment_access_stack_name = MOCK_STACK_NAME + '-Access' with mock_aws.patch_client('cloudformation', 'describe_stacks', side_effect=mock_describe_stacks_result ) as mock_describe_stacks: target = stack_info.DeploymentInfo(MOCK_STACK_ARN) with self.assertRaisesRegexp(ClientError, mock_error_code): actual_deployment_access = target.deployment_access mock_describe_stacks.assert_called_once_with( StackName=mock_deployment_access_stack_name)
def test_deployment_access_arn_provided(self): mock_deployment_access = 'test-deployment-access' with mock.patch('stack_info.DeploymentAccessInfo', return_value=mock_deployment_access ) as mock_DeploymentAccessInfo: mock_deployment_access_stack_arn = 'deployment-access-stack-arn' target = stack_info.DeploymentInfo( MOCK_STACK_ARN, deployment_access_stack_arn=mock_deployment_access_stack_arn, client=MOCK_CLIENT) actual_deployment_access = target.deployment_access self.assertIs(actual_deployment_access, mock_deployment_access) mock_DeploymentAccessInfo.assert_called_once_with( mock_deployment_access_stack_arn, deployment_info=target, client=MOCK_CLIENT)
def test_project_discovered(self): mock_project_stack_id = 'test-project-stack-id' mock_project = 'test-project-info' mock_parameters = {'ProjectStackId': mock_project_stack_id} with mock.patch('stack_info.ProjectInfo', return_value=mock_project) as mock_ProjectInfo: with mock.patch( 'stack_info.DeploymentInfo.parameters', new=mock.PropertyMock(return_value=mock_parameters)): target = stack_info.DeploymentInfo(MOCK_STACK_ARN, client=MOCK_CLIENT) actual_project = target.project actual_project_2 = target.project self.assertIs(actual_project, mock_project) self.assertIs(actual_project_2, mock_project) mock_ProjectInfo.assert_called_once_with(mock_project_stack_id, client=MOCK_CLIENT)
def test_project_provided(self): mock_project_info = 'test-project-info' target = stack_info.DeploymentInfo(MOCK_STACK_ARN, project_info=mock_project_info) self.assertIs(target.project, mock_project_info)