def test_deployment_preference_with_codedeploy_custom_configuration(self): deployment_type = "TestDeploymentConfiguration" deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add(self.function_logical_id, {'Type': deployment_type}) deployment_group = deployment_preference_collection.deployment_group(self.function_logical_id) self.assertEqual(deployment_type, deployment_group.DeploymentConfigName)
def test_deployment_preference_with_conditional_custom_configuration(self): deployment_type = {'Fn::If': ['IsDevEnv', {'Fn::If': ['IsDevEnv1', 'AllAtOnce', 'TestDeploymentConfiguration']}, 'Canary10Percent15Minutes']} expected_deployment_config_name = {'Fn::If': ['IsDevEnv', {'Fn::If': ['IsDevEnv1', {'Fn::Sub': [ 'CodeDeployDefault.Lambda${ConfigName}', { 'ConfigName': 'AllAtOnce' } ] }, 'TestDeploymentConfiguration']}, {'Fn::Sub': [ 'CodeDeployDefault.Lambda${ConfigName}', { 'ConfigName': 'Canary10Percent15Minutes' } ] } ] } deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add(self.function_logical_id, {'Type': deployment_type}) deployment_group = deployment_preference_collection.deployment_group(self.function_logical_id) print(deployment_group.DeploymentConfigName) self.assertEqual(expected_deployment_config_name, deployment_group.DeploymentConfigName)
def test_deployment_group_with_minimal_parameters(self): expected_deployment_group = CodeDeployDeploymentGroup(self.function_logical_id + 'DeploymentGroup') expected_deployment_group.ApplicationName = {'Ref': CODEDEPLOY_APPLICATION_LOGICAL_ID} expected_deployment_group.AutoRollbackConfiguration = {'Enabled': True, 'Events': ['DEPLOYMENT_FAILURE', 'DEPLOYMENT_STOP_ON_ALARM', 'DEPLOYMENT_STOP_ON_REQUEST']} expected_deployment_group.DeploymentConfigName = { 'Fn::Sub': [ 'CodeDeployDefault.Lambda${ConfigName}', { 'ConfigName': self.deployment_type_global } ] } expected_deployment_group.DeploymentStyle = {'DeploymentType': 'BLUE_GREEN', 'DeploymentOption': 'WITH_TRAFFIC_CONTROL'} expected_deployment_group.ServiceRoleArn = {'Fn::GetAtt': [CODE_DEPLOY_SERVICE_ROLE_LOGICAL_ID, 'Arn']} deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add(self.function_logical_id, {'Type': self.deployment_type_global}) deployment_group = deployment_preference_collection.deployment_group(self.function_logical_id) self.assertEqual(deployment_group.to_dict(), expected_deployment_group.to_dict())
def test_deployment_preference_with_alarms_not_list(self): deployment_preference = { "Type": "TestDeploymentConfiguration", "Alarms": "Alarm1", } deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add(self.function_logical_id, deployment_preference) with self.assertRaises(InvalidResourceException) as e: deployment_preference_collection.deployment_group( self.function_logical_id) self.assertEqual( e.exception.message, "Resource with id [{}] is invalid. Alarms must be a list".format( self.function_logical_id), )
def test_deployment_preference_with_alarms_intrinsic_if_noref_else(self): deployment_preference = { "Type": "TestDeploymentConfiguration", "Alarms": { "Fn::If": [ "MyCondition", ["Alarm1", "Alarm2"], [{ "Ref": "AWS::NoValue" }] ] }, } expected_alarm_configuration = { "Fn::If": [ "MyCondition", { "Enabled": True, "Alarms": [{ "Name": "Alarm1" }, { "Name": "Alarm2" }] }, {} ], } deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add(self.function_logical_id, deployment_preference) deployment_group = deployment_preference_collection.deployment_group( self.function_logical_id) self.assertEqual(expected_alarm_configuration, deployment_group.AlarmConfiguration)
def test_deployment_group_with_all_parameters(self): expected_deployment_group = CodeDeployDeploymentGroup(self.function_logical_id + "DeploymentGroup") expected_deployment_group.AlarmConfiguration = { "Enabled": True, "Alarms": [{"Name": {"Ref": "alarm1"}}, {"Name": {"Ref": "alarm2"}}], } expected_deployment_group.ApplicationName = {"Ref": CODEDEPLOY_APPLICATION_LOGICAL_ID} expected_deployment_group.AutoRollbackConfiguration = { "Enabled": True, "Events": ["DEPLOYMENT_FAILURE", "DEPLOYMENT_STOP_ON_ALARM", "DEPLOYMENT_STOP_ON_REQUEST"], } expected_deployment_group.DeploymentConfigName = { "Fn::Sub": ["CodeDeployDefault.Lambda${ConfigName}", {"ConfigName": self.deployment_type_global}] } expected_deployment_group.DeploymentStyle = { "DeploymentType": "BLUE_GREEN", "DeploymentOption": "WITH_TRAFFIC_CONTROL", } expected_deployment_group.ServiceRoleArn = {"Fn::GetAtt": [CODE_DEPLOY_SERVICE_ROLE_LOGICAL_ID, "Arn"]} deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add(self.function_logical_id, self.global_deployment_preference_yaml_dict()) deployment_group = deployment_preference_collection.deployment_group(self.function_logical_id) self.assertEqual(deployment_group.to_dict(), expected_deployment_group.to_dict())
def test_deployment_preference_with_conditional_custom_configuration(self): deployment_type = { "Fn::If": [ "IsDevEnv", {"Fn::If": ["IsDevEnv1", "AllAtOnce", "TestDeploymentConfiguration"]}, "Canary10Percent15Minutes", ] } expected_deployment_config_name = { "Fn::If": [ "IsDevEnv", { "Fn::If": [ "IsDevEnv1", {"Fn::Sub": ["CodeDeployDefault.Lambda${ConfigName}", {"ConfigName": "AllAtOnce"}]}, "TestDeploymentConfiguration", ] }, {"Fn::Sub": ["CodeDeployDefault.Lambda${ConfigName}", {"ConfigName": "Canary10Percent15Minutes"}]}, ] } deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add(self.function_logical_id, {"Type": deployment_type}) deployment_group = deployment_preference_collection.deployment_group(self.function_logical_id) print(deployment_group.DeploymentConfigName) self.assertEqual(expected_deployment_config_name, deployment_group.DeploymentConfigName)
def test_deployment_preference_with_alarms_intrinsic_if_missing_arg(self): deployment_preference = { "Type": "TestDeploymentConfiguration", "Alarms": { "Fn::If": ["MyCondition", ["Alarm1", "Alarm2"]] }, } deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add(self.function_logical_id, deployment_preference) with self.assertRaises(InvalidResourceException) as e: deployment_preference_collection.deployment_group( self.function_logical_id) self.assertEqual( e.exception.message, "Resource with id [{}] is invalid. Fn::If requires 3 arguments". format(self.function_logical_id), )
def test_deployment_preference_with_codedeploy_predifined_configuration(self): deployment_type = "Canary10Percent5Minutes" expected_deployment_config_name = { "Fn::Sub": ["CodeDeployDefault.Lambda${ConfigName}", {"ConfigName": deployment_type}] } deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add(self.function_logical_id, {"Type": deployment_type}) deployment_group = deployment_preference_collection.deployment_group(self.function_logical_id) print(deployment_group.DeploymentConfigName) self.assertEqual(expected_deployment_config_name, deployment_group.DeploymentConfigName)
def test_deployment_preference_with_alarms_empty(self): deployment_preference = { "Type": "TestDeploymentConfiguration", "Alarms": [], } deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add(self.function_logical_id, deployment_preference) deployment_group = deployment_preference_collection.deployment_group( self.function_logical_id) self.assertIsNone(deployment_group.AlarmConfiguration)
def test_deployment_preference_with_alarms_ref_novalue(self): deployment_preference = { "Type": "TestDeploymentConfiguration", "Alarms": { "Ref": "AWS::NoValue" }, } deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add(self.function_logical_id, deployment_preference) deployment_group = deployment_preference_collection.deployment_group( self.function_logical_id) self.assertIsNone(deployment_group.AlarmConfiguration)
def test_deployment_preference_with_alarms(self): deployment_preference = { "Type": "TestDeploymentConfiguration", "Alarms": ["Alarm1", "Alarm2", "Alarm3"], } expected_alarm_configuration = { "Enabled": True, "Alarms": [{ "Name": "Alarm1" }, { "Name": "Alarm2" }, { "Name": "Alarm3" }], } deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add(self.function_logical_id, deployment_preference) deployment_group = deployment_preference_collection.deployment_group( self.function_logical_id) self.assertEqual(expected_alarm_configuration, deployment_group.AlarmConfiguration)
def translate(self, sam_template, parameter_values, feature_toggle=None): """Loads the SAM resources from the given SAM manifest, replaces them with their corresponding CloudFormation resources, and returns the resulting CloudFormation template. :param dict sam_template: the SAM manifest, as loaded by json.load() or yaml.load(), or as provided by \ CloudFormation transforms. :param dict parameter_values: Map of template parameter names to their values. It is a required parameter that should at least be an empty map. By providing an empty map, the caller explicitly opts-into the idea that some functionality that relies on resolving parameter references might not work as expected (ex: auto-creating new Lambda Version when CodeUri contains reference to template parameter). This is why this parameter is required :returns: a copy of the template with SAM resources replaced with the corresponding CloudFormation, which may \ be dumped into a valid CloudFormation JSON or YAML template """ self.feature_toggle = (feature_toggle if feature_toggle else FeatureToggle( FeatureToggleDefaultConfigProvider(), stage=None, account_id=None, region=None)) self.function_names = dict() self.redeploy_restapi_parameters = dict() sam_parameter_values = SamParameterValues(parameter_values) sam_parameter_values.add_default_parameter_values(sam_template) sam_parameter_values.add_pseudo_parameter_values(self.boto_session) parameter_values = sam_parameter_values.parameter_values # Create & Install plugins sam_plugins = prepare_plugins(self.plugins, parameter_values) self.sam_parser.parse(sam_template=sam_template, parameter_values=parameter_values, sam_plugins=sam_plugins) template = copy.deepcopy(sam_template) macro_resolver = ResourceTypeResolver(sam_resources) intrinsics_resolver = IntrinsicsResolver(parameter_values) mappings_resolver = IntrinsicsResolver( template.get("Mappings", {}), {FindInMapAction.intrinsic_name: FindInMapAction()}) deployment_preference_collection = DeploymentPreferenceCollection() supported_resource_refs = SupportedResourceReferences() shared_api_usage_plan = SharedApiUsagePlan() document_errors = [] changed_logical_ids = {} for logical_id, resource_dict in self._get_resources_to_iterate( sam_template, macro_resolver): try: macro = macro_resolver.resolve_resource_type( resource_dict).from_dict(logical_id, resource_dict, sam_plugins=sam_plugins) kwargs = macro.resources_to_link(sam_template["Resources"]) kwargs["managed_policy_map"] = self.managed_policy_map kwargs["intrinsics_resolver"] = intrinsics_resolver kwargs["mappings_resolver"] = mappings_resolver kwargs[ "deployment_preference_collection"] = deployment_preference_collection kwargs["conditions"] = template.get("Conditions") # add the value of FunctionName property if the function is referenced with the api resource self.redeploy_restapi_parameters[ "function_names"] = self._get_function_names( resource_dict, intrinsics_resolver) kwargs[ "redeploy_restapi_parameters"] = self.redeploy_restapi_parameters kwargs["shared_api_usage_plan"] = shared_api_usage_plan translated = macro.to_cloudformation(**kwargs) supported_resource_refs = macro.get_resource_references( translated, supported_resource_refs) # Some resources mutate their logical ids. Track those to change all references to them: if logical_id != macro.logical_id: changed_logical_ids[logical_id] = macro.logical_id del template["Resources"][logical_id] for resource in translated: if verify_unique_logical_id(resource, sam_template["Resources"]): template["Resources"].update(resource.to_dict()) else: document_errors.append( DuplicateLogicalIdException( logical_id, resource.logical_id, resource.resource_type)) except (InvalidResourceException, InvalidEventException) as e: document_errors.append(e) if deployment_preference_collection.any_enabled(): template["Resources"].update(deployment_preference_collection. codedeploy_application.to_dict()) if not deployment_preference_collection.can_skip_service_role(): template["Resources"].update(deployment_preference_collection. codedeploy_iam_role.to_dict()) for logical_id in deployment_preference_collection.enabled_logical_ids( ): try: template["Resources"].update( deployment_preference_collection.deployment_group( logical_id).to_dict()) except InvalidResourceException as e: document_errors.append(e) # Run the after-transform plugin target try: sam_plugins.act(LifeCycleEvents.after_transform_template, template) except (InvalidDocumentException, InvalidResourceException) as e: document_errors.append(e) # Cleanup if "Transform" in template: del template["Transform"] if len(document_errors) == 0: template = intrinsics_resolver.resolve_sam_resource_id_refs( template, changed_logical_ids) template = intrinsics_resolver.resolve_sam_resource_refs( template, supported_resource_refs) return template else: raise InvalidDocumentException(document_errors)
def translate(self, sam_template, parameter_values): """Loads the SAM resources from the given SAM manifest, replaces them with their corresponding CloudFormation resources, and returns the resulting CloudFormation template. :param dict sam_template: the SAM manifest, as loaded by json.load() or yaml.load(), or as provided by \ CloudFormation transforms. :param dict parameter_values: Map of template parameter names to their values. It is a required parameter that should at least be an empty map. By providing an empty map, the caller explicitly opts-into the idea that some functionality that relies on resolving parameter references might not work as expected (ex: auto-creating new Lambda Version when CodeUri contains reference to template parameter). This is why this parameter is required :returns: a copy of the template with SAM resources replaced with the corresponding CloudFormation, which may \ be dumped into a valid CloudFormation JSON or YAML template """ sam_parameter_values = SamParameterValues(parameter_values) sam_parameter_values.add_default_parameter_values(sam_template) sam_parameter_values.add_pseudo_parameter_values() parameter_values = sam_parameter_values.parameter_values # Create & Install plugins sam_plugins = prepare_plugins(self.plugins, parameter_values) self.sam_parser.parse( sam_template=sam_template, parameter_values=parameter_values, sam_plugins=sam_plugins ) template = copy.deepcopy(sam_template) macro_resolver = ResourceTypeResolver(sam_resources) intrinsics_resolver = IntrinsicsResolver(parameter_values) deployment_preference_collection = DeploymentPreferenceCollection() supported_resource_refs = SupportedResourceReferences() document_errors = [] changed_logical_ids = {} for logical_id, resource_dict in self._get_resources_to_iterate(sam_template, macro_resolver): try: macro = macro_resolver\ .resolve_resource_type(resource_dict)\ .from_dict(logical_id, resource_dict, sam_plugins=sam_plugins) kwargs = macro.resources_to_link(sam_template['Resources']) kwargs['managed_policy_map'] = self.managed_policy_map kwargs['intrinsics_resolver'] = intrinsics_resolver kwargs['deployment_preference_collection'] = deployment_preference_collection translated = macro.to_cloudformation(**kwargs) supported_resource_refs = macro.get_resource_references(translated, supported_resource_refs) # Some resources mutate their logical ids. Track those to change all references to them: if logical_id != macro.logical_id: changed_logical_ids[logical_id] = macro.logical_id del template['Resources'][logical_id] for resource in translated: if verify_unique_logical_id(resource, sam_template['Resources']): template['Resources'].update(resource.to_dict()) else: document_errors.append(DuplicateLogicalIdException( logical_id, resource.logical_id, resource.resource_type)) except (InvalidResourceException, InvalidEventException) as e: document_errors.append(e) if deployment_preference_collection.any_enabled(): template['Resources'].update(deployment_preference_collection.codedeploy_application.to_dict()) if not deployment_preference_collection.can_skip_service_role(): template['Resources'].update(deployment_preference_collection.codedeploy_iam_role.to_dict()) for logical_id in deployment_preference_collection.enabled_logical_ids(): template['Resources'].update(deployment_preference_collection.deployment_group(logical_id).to_dict()) # Run the after-transform plugin target try: sam_plugins.act(LifeCycleEvents.after_transform_template, template) except (InvalidDocumentException, InvalidResourceException) as e: document_errors.append(e) # Cleanup if 'Transform' in template: del template['Transform'] if len(document_errors) == 0: template = intrinsics_resolver.resolve_sam_resource_id_refs(template, changed_logical_ids) template = intrinsics_resolver.resolve_sam_resource_refs(template, supported_resource_refs) return template else: raise InvalidDocumentException(document_errors)
def translate(self, sam_template, parameter_values): """Loads the SAM resources from the given SAM manifest, replaces them with their corresponding CloudFormation resources, and returns the resulting CloudFormation template. :param dict sam_template: the SAM manifest, as loaded by json.load() or yaml.load(), or as provided by \ CloudFormation transforms. :param dict parameter_values: Map of template parameter names to their values. It is a required parameter that should at least be an empty map. By providing an empty map, the caller explicitly opts-into the idea that some functionality that relies on resolving parameter references might not work as expected (ex: auto-creating new Lambda Version when CodeUri contains reference to template parameter). This is why this parameter is required :returns: a copy of the template with SAM resources replaced with the corresponding CloudFormation, which may \ be dumped into a valid CloudFormation JSON or YAML template """ # Create & Install plugins sam_plugins = prepare_plugins(self.plugins) parameter_values = self._add_default_parameter_values( sam_template, parameter_values) self.sam_parser.parse(sam_template=sam_template, parameter_values=parameter_values, sam_plugins=sam_plugins) template = copy.deepcopy(sam_template) macro_resolver = ResourceTypeResolver(sam_resources) intrinsics_resolver = IntrinsicsResolver(parameter_values) deployment_preference_collection = DeploymentPreferenceCollection() supported_resource_refs = SupportedResourceReferences() document_errors = [] changed_logical_ids = {} for logical_id, resource_dict in self._get_resources_to_iterate( sam_template, macro_resolver): try: macro = macro_resolver\ .resolve_resource_type(resource_dict)\ .from_dict(logical_id, resource_dict, sam_plugins=sam_plugins) kwargs = macro.resources_to_link(sam_template['Resources']) kwargs['managed_policy_map'] = self.managed_policy_map kwargs['intrinsics_resolver'] = intrinsics_resolver kwargs[ 'deployment_preference_collection'] = deployment_preference_collection translated = macro.to_cloudformation(**kwargs) supported_resource_refs = macro.get_resource_references( translated, supported_resource_refs) # Some resources mutate their logical ids. Track those to change all references to them: if logical_id != macro.logical_id: changed_logical_ids[logical_id] = macro.logical_id del template['Resources'][logical_id] for resource in translated: if verify_unique_logical_id(resource, sam_template['Resources']): template['Resources'].update(resource.to_dict()) else: document_errors.append( DuplicateLogicalIdException( logical_id, resource.logical_id, resource.resource_type)) except (InvalidResourceException, InvalidEventException) as e: document_errors.append(e) if deployment_preference_collection.any_enabled(): template['Resources'].update(deployment_preference_collection. codedeploy_application.to_dict()) if not deployment_preference_collection.can_skip_service_role(): template['Resources'].update(deployment_preference_collection. codedeploy_iam_role.to_dict()) for logical_id in deployment_preference_collection.enabled_logical_ids( ): template['Resources'].update( deployment_preference_collection.deployment_group( logical_id).to_dict()) # Run the after-transform plugin target try: sam_plugins.act(LifeCycleEvents.after_transform_template, template) except (InvalidDocumentException, InvalidResourceException) as e: document_errors.append(e) # Cleanup if 'Transform' in template: del template['Transform'] if len(document_errors) is 0: template = intrinsics_resolver.resolve_sam_resource_id_refs( template, changed_logical_ids) template = intrinsics_resolver.resolve_sam_resource_refs( template, supported_resource_refs) return template else: raise InvalidDocumentException(document_errors)