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_resource_group_name_discovered(self): mock_resource_group_name = 'test-name' mock_parameters = {'ResourceGroupName': mock_resource_group_name} with mock.patch('stack_info.ResourceGroupInfo.parameters', new=mock.PropertyMock(return_value=mock_parameters)): target = stack_info.ResourceGroupInfo(MOCK_STACK_ARN) actual_resource_group_name = target.resource_group_name self.assertEquals(actual_resource_group_name, mock_resource_group_name)
def test_constructor(self): target = stack_info.ResourceGroupInfo( MOCK_STACK_ARN, client=MOCK_CLIENT, stack_description=MOCK_STACK_DESCRIPTION) self.assertEquals(target.stack_type, stack_info.StackInfo.STACK_TYPE_RESOURCE_GROUP) self.assertIs(target.client, MOCK_CLIENT) self.assertIs(target.stack_description, MOCK_STACK_DESCRIPTION)
def test_deployment_discovered(self): mock_deployment_stack_id = 'test-deployment-stack-id' mock_parameters = {'DeploymentStackArn': mock_deployment_stack_id} mock_deployment = 'test-deployment' with mock.patch('stack_info.DeploymentInfo', return_value=mock_deployment) as mock_DeploymentInfo: with mock.patch( 'stack_info.ResourceGroupInfo.parameters', new=mock.PropertyMock(return_value=mock_parameters)): target = stack_info.ResourceGroupInfo(MOCK_STACK_ARN) actual_deployment = target.deployment actual_deployment_2 = target.deployment self.assertEquals(actual_deployment, mock_deployment) self.assertEquals(actual_deployment_2, mock_deployment) mock_DeploymentInfo.assert_called_once_with( mock_deployment_stack_id, client=target.client)
def test_deployment_provided(self): mock_deployment = 'test_deployment' target = stack_info.ResourceGroupInfo(MOCK_STACK_ARN, deployment_info=mock_deployment) self.assertEquals(target.deployment, mock_deployment)
def test_resource_group_name_provided(self): mock_resource_group_name = 'test_resource_group_name' target = stack_info.ResourceGroupInfo( MOCK_STACK_ARN, resource_group_name=mock_resource_group_name) self.assertEquals(target.resource_group_name, mock_resource_group_name)
def create_role(stack_arn, logical_role_name, policy_name, assume_role_service, default_statements=[], policy_metadata_filter=_default_policy_metadata_filter): '''Create a role with a policy constructed using metadata found on a stack's resources. Args: stack_arn: Identifies the stack. logical_role_name: Appended to the stack name to construct the actual role name. policy_name: Identifies the metadata used to construct the policy and the name of the policy attached to the created role. assume_role_service: Identifies the AWS service that is allowed to assume the role. default_statements (named): A list containing additional statements included in the policy. Default is []. policy_metadata_filter (name): A function that takes a single parameter and returns True if the provided value should be used. The default function always returns True. Returns: The ARN of the created role. Note: The policy attached to the role is constructed by looking in the CloudCanvas metadata object for a property identified by the policy_name parameter on each of the resources defined by the specified stack. The value of this property can be an object with an "Action" property, or it can be a list of such objects. The value, or each value in the list of values, is passed to the policy_metadata_filter function to determine if it should be used in the construction of the policy. An error occurs if the filter accepts more than one value. ''' role_name = _get_role_name(stack_arn, logical_role_name) resource_group = stack_info.ResourceGroupInfo(stack_arn) path = '/{project_name}/{deployment_name}/{resource_group_name}/{logical_role_name}/'.format( project_name=resource_group.deployment.project.project_name, deployment_name=resource_group.deployment.deployment_name, resource_group_name=resource_group.resource_group_name, logical_role_name=logical_role_name) assume_role_policy_document = ASSUME_ROLE_POLICY_DOCUMENT.replace( '{assume_role_service}', assume_role_service) print 'create_role {} with path {}'.format(role_name, path) res = iam.create_role(RoleName=role_name, AssumeRolePolicyDocument=assume_role_policy_document, Path=path) print 'create_role {} result: {}'.format(role_name, res) role_arn = res['Role']['Arn'] _set_role_policy(role_name, stack_arn, policy_name, default_statements, policy_metadata_filter) # Allow time for the role to propagate before lambda tries to assume # it, which lambda tries to do when the function is created. time.sleep(30) return role_arn