from unittest import TestCase from unittest.mock import patch from samtranslator.model.codedeploy import CodeDeployApplication, CodeDeployDeploymentGroup from samtranslator.model.exceptions import InvalidResourceException from samtranslator.model.iam import IAMRole from samtranslator.model.preferences.deployment_preference import DeploymentPreference from samtranslator.model.preferences.deployment_preference_collection import ( CODE_DEPLOY_SERVICE_ROLE_LOGICAL_ID, CODEDEPLOY_APPLICATION_LOGICAL_ID, DeploymentPreferenceCollection, ) class TestDeploymentPreferenceCollection(TestCase): def setup_method(self, method): self.deployment_type_global = "AllAtOnce" self.alarms_global = [{"Ref": "alarm1"}, {"Ref": "alarm2"}] self.post_traffic_host_global = "post_traffic_function_ref" self.pre_traffic_hook_global = "pre_traffic_function_ref" self.function_logical_id = "FunctionLogicalId" self.condition = "CodeDeployCondition" @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_when_no_global_dict_each_local_deployment_preference_requires_parameters(self): with self.assertRaises(InvalidResourceException): DeploymentPreferenceCollection().add("", dict()) @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_add_when_logical_id_previously_added_raises_value_error(self): with self.assertRaises(ValueError): deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add("1", {"Type": "Canary"}) deployment_preference_collection.add("1", {"Type": "Linear"}) @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_codedeploy_application(self): expected_codedeploy_application_resource = CodeDeployApplication(CODEDEPLOY_APPLICATION_LOGICAL_ID) expected_codedeploy_application_resource.ComputePlatform = "Lambda" self.assertEqual( DeploymentPreferenceCollection().get_codedeploy_application().to_dict(), expected_codedeploy_application_resource.to_dict(), ) @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_codedeploy_iam_role(self): expected_codedeploy_iam_role = IAMRole("CodeDeployServiceRole") expected_codedeploy_iam_role.AssumeRolePolicyDocument = { "Version": "2012-10-17", "Statement": [ { "Action": ["sts:AssumeRole"], "Effect": "Allow", "Principal": {"Service": ["codedeploy.amazonaws.com"]}, } ], } expected_codedeploy_iam_role.ManagedPolicyArns = [ "arn:aws:iam::aws:policy/service-role/AWSCodeDeployRoleForLambda" ] self.assertEqual( DeploymentPreferenceCollection().get_codedeploy_iam_role().to_dict(), expected_codedeploy_iam_role.to_dict() ) @patch("boto3.session.Session.region_name", "ap-southeast-1") 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()) @patch("boto3.session.Session.region_name", "ap-southeast-1") 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) @patch("boto3.session.Session.region_name", "ap-southeast-1") 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) @patch("boto3.session.Session.region_name", "ap-southeast-1") 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) @patch("boto3.session.Session.region_name", "ap-southeast-1") 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()) @patch("boto3.session.Session.region_name", "ap-southeast-1") 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) @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_deployment_preference_with_alarms_intrinsic_if(self): deployment_preference = { "Type": "TestDeploymentConfiguration", "Alarms": {"Fn::If": ["MyCondition", ["Alarm1", "Alarm2"], ["Alarm3"]]}, } expected_alarm_configuration = { "Fn::If": [ "MyCondition", {"Enabled": True, "Alarms": [{"Name": "Alarm1"}, {"Name": "Alarm2"}]}, {"Enabled": True, "Alarms": [{"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) @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_deployment_preference_with_alarms_intrinsic_if_empty_then(self): deployment_preference = { "Type": "TestDeploymentConfiguration", "Alarms": {"Fn::If": ["MyCondition", [], ["Alarm3"]]}, } expected_alarm_configuration = { "Fn::If": ["MyCondition", {}, {"Enabled": True, "Alarms": [{"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) @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_deployment_preference_with_alarms_intrinsic_if_noref_then(self): deployment_preference = { "Type": "TestDeploymentConfiguration", "Alarms": {"Fn::If": ["MyCondition", [{"Ref": "AWS::NoValue"}], ["Alarm3"]]}, } expected_alarm_configuration = { "Fn::If": ["MyCondition", {}, {"Enabled": True, "Alarms": [{"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) @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_deployment_preference_with_alarms_intrinsic_if_empty_else(self): deployment_preference = { "Type": "TestDeploymentConfiguration", "Alarms": {"Fn::If": ["MyCondition", ["Alarm1", "Alarm2"], []]}, } 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) @patch("boto3.session.Session.region_name", "ap-southeast-1") 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) @patch("boto3.session.Session.region_name", "ap-southeast-1") 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) @patch("boto3.session.Session.region_name", "ap-southeast-1") 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) @patch("boto3.session.Session.region_name", "ap-southeast-1") 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, f"Resource with id [{self.function_logical_id}] is invalid. Alarms must be a list", ) @patch("boto3.session.Session.region_name", "ap-southeast-1") 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, f"Resource with id [{self.function_logical_id}] is invalid. Fn::If requires 3 arguments", ) @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_deployment_preference_with_alarms_intrinsic_if_not_list(self): deployment_preference = { "Type": "TestDeploymentConfiguration", "Alarms": {"Fn::If": "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, f"Resource with id [{self.function_logical_id}] is invalid. Fn::If requires 3 arguments", ) @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_update_policy_with_minimal_parameters(self): expected_update_policy = { "CodeDeployLambdaAliasUpdate": { "ApplicationName": {"Ref": CODEDEPLOY_APPLICATION_LOGICAL_ID}, "DeploymentGroupName": {"Ref": self.function_logical_id + "DeploymentGroup"}, } } deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add(self.function_logical_id, {"Type": "CANARY"}) update_policy = deployment_preference_collection.update_policy(self.function_logical_id) self.assertEqual(expected_update_policy, update_policy.to_dict()) @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_update_policy_with_all_parameters(self): expected_update_polcy = { "CodeDeployLambdaAliasUpdate": { "ApplicationName": {"Ref": CODEDEPLOY_APPLICATION_LOGICAL_ID}, "DeploymentGroupName": {"Ref": self.function_logical_id + "DeploymentGroup"}, "BeforeAllowTrafficHook": self.pre_traffic_hook_global, "AfterAllowTrafficHook": self.post_traffic_host_global, } } deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add(self.function_logical_id, self.global_deployment_preference_yaml_dict()) update_policy = deployment_preference_collection.update_policy(self.function_logical_id) self.assertEqual(expected_update_polcy, update_policy.to_dict()) @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_any_enabled_true_if_one_of_three_enabled(self): deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add("1", {"Type": "LINEAR"}) deployment_preference_collection.add("2", {"Type": "LINEAR", "Enabled": False}) deployment_preference_collection.add("3", {"Type": "CANARY", "Enabled": False}) self.assertTrue(deployment_preference_collection.any_enabled()) @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_any_enabled_true_if_all_of_three_enabled(self): deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add("1", {"Type": "LINEAR"}) deployment_preference_collection.add("2", {"Type": "LINEAR", "Enabled": True}) deployment_preference_collection.add("3", {"Type": "CANARY", "Enabled": True}) self.assertTrue(deployment_preference_collection.any_enabled()) @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_any_enabled_false_if_all_of_three_disabled(self): deployment_preference_collection = DeploymentPreferenceCollection() deployment_preference_collection.add("1", {"Type": "Linear", "Enabled": False}) deployment_preference_collection.add("2", {"Type": "LINEAR", "Enabled": False}) deployment_preference_collection.add("3", {"Type": "CANARY", "Enabled": False}) self.assertFalse(deployment_preference_collection.any_enabled()) @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_enabled_logical_ids_returns_one_if_one_of_three_enabled(self): deployment_preference_collection = DeploymentPreferenceCollection() enabled_logical_id = "1" deployment_preference_collection.add(enabled_logical_id, {"Type": "LINEAR"}) deployment_preference_collection.add("2", {"Type": "LINEAR", "Enabled": False}) deployment_preference_collection.add("3", {"Type": "CANARY", "Enabled": False}) enabled_logical_ids = deployment_preference_collection.enabled_logical_ids() self.assertEqual(1, len(enabled_logical_ids)) self.assertEqual(enabled_logical_id, enabled_logical_ids[0]) def global_deployment_preference_yaml_dict(self): deployment_preference_yaml_dict = dict() deployment_preference_yaml_dict["Type"] = self.deployment_type_global deployment_preference_yaml_dict["Hooks"] = { "PreTraffic": self.pre_traffic_hook_global, "PostTraffic": self.post_traffic_host_global, } deployment_preference_yaml_dict["Alarms"] = self.alarms_global return deployment_preference_yaml_dict def global_deployment_preference(self): return DeploymentPreference( self.deployment_type_global, self.pre_traffic_hook_global, self.post_traffic_host_global, self.alarms_global, True, )