1111from typing import Any , Dict
1212from uuid import uuid4
1313
14+ import jsonpatch
1415import yaml
1516from botocore .exceptions import ClientError , WaiterError
1617from jinja2 import Environment , PackageLoader , select_autoescape
6364TARGET_CANARY_FOLDER = "canary-bundle/canary"
6465RPDK_CONFIG_FILE = ".rpdk-config"
6566CANARY_FILE_PREFIX = "canary"
67+ CANARY_FILE_CREATE_SUFFIX = "001"
68+ CANARY_FILE_UPDATE_SUFFIX = "002"
69+ CANARY_SUPPORTED_PATCH_INPUT_OPERATIONS = {"replace" , "remove" , "add" }
70+ CREATE_INPUTS_KEY = "CreateInputs"
71+ PATCH_INPUTS_KEY = "PatchInputs"
72+ PATCH_VALUE_KEY = "value"
73+ PATCH_OPERATION_KEY = "op"
6674CONTRACT_TEST_DEPENDENCY_FILE_NAME = "dependencies.yml"
6775CANARY_DEPENDENCY_FILE_NAME = "bootstrap.yaml"
6876CANARY_SETTINGS = "canarySettings"
7684FILE_GENERATION_ENABLED = "file_generation_enabled"
7785TYPE_NAME = "typeName"
7886CONTRACT_TEST_FILE_NAMES = "contract_test_file_names"
79- INPUT1_FILE_NAME = "inputs_1.json"
8087FN_SUB = "Fn::Sub"
8188FN_IMPORT_VALUE = "Fn::ImportValue"
8289UUID = "uuid"
@@ -1345,21 +1352,68 @@ def _generate_stack_template_files(self) -> None:
13451352 with ct_file .open ("r" ) as f :
13461353 json_data = json .load (f )
13471354 resource_name = self .type_info [2 ]
1348- stack_template_data = {
1349- "Description" : f"Template for { self .type_name } " ,
1350- "Resources" : {
1351- f"{ resource_name } " : {
1352- "Type" : self .type_name ,
1353- "Properties" : self ._replace_dynamic_values (
1354- json_data ["CreateInputs" ]
1355- ),
1356- }
1357- },
1358- }
1359- stack_template_file_name = f"{ CANARY_FILE_PREFIX } { count } _001.yaml"
1360- stack_template_file_path = stack_template_folder / stack_template_file_name
1361- with stack_template_file_path .open ("w" ) as stack_template_file :
1362- yaml .dump (stack_template_data , stack_template_file , indent = 2 )
1355+
1356+ self ._save_stack_template_data (
1357+ resource_name ,
1358+ count ,
1359+ stack_template_folder ,
1360+ self ._replace_dynamic_values (
1361+ json_data [CREATE_INPUTS_KEY ],
1362+ ),
1363+ CANARY_FILE_CREATE_SUFFIX ,
1364+ )
1365+ if PATCH_INPUTS_KEY in json_data :
1366+ supported_patch_inputs = self ._translate_supported_patch_inputs (
1367+ json_data [PATCH_INPUTS_KEY ]
1368+ )
1369+ patch_data = jsonpatch .apply_patch (
1370+ json_data [CREATE_INPUTS_KEY ], supported_patch_inputs , in_place = False
1371+ )
1372+ self ._save_stack_template_data (
1373+ resource_name ,
1374+ count ,
1375+ stack_template_folder ,
1376+ patch_data ,
1377+ CANARY_FILE_UPDATE_SUFFIX ,
1378+ )
1379+
1380+ def _save_stack_template_data (
1381+ self ,
1382+ resource_name ,
1383+ contract_test_input_count ,
1384+ stack_template_folder ,
1385+ properties_data ,
1386+ suffix ,
1387+ ):
1388+ stack_template_data = {
1389+ "Description" : f"Template for { self .type_name } " ,
1390+ "Resources" : {
1391+ f"{ resource_name } " : {
1392+ "Type" : self .type_name ,
1393+ "Properties" : properties_data ,
1394+ }
1395+ },
1396+ }
1397+ stack_template_file_name = (
1398+ f"{ CANARY_FILE_PREFIX } { contract_test_input_count } _{ suffix } .yaml"
1399+ )
1400+ stack_template_file_path = stack_template_folder / stack_template_file_name
1401+ with stack_template_file_path .open ("w" ) as stack_template_file :
1402+ yaml .dump (stack_template_data , stack_template_file , indent = 2 )
1403+
1404+ def _translate_supported_patch_inputs (self , patch_inputs : Any ) -> Any :
1405+ output = []
1406+ for patch_input in patch_inputs :
1407+ if (
1408+ patch_input .get (PATCH_OPERATION_KEY )
1409+ in CANARY_SUPPORTED_PATCH_INPUT_OPERATIONS
1410+ ):
1411+ if PATCH_VALUE_KEY in patch_input :
1412+ self ._replace_dynamic_values_with_root_key (
1413+ patch_input , PATCH_VALUE_KEY
1414+ )
1415+ output .append (patch_input )
1416+ return output
13631417
13641418 def _replace_dynamic_values (self , properties : Dict [str , Any ]) -> Dict [str , Any ]:
13651419 for key , value in properties .items ():
@@ -1372,6 +1426,19 @@ def _replace_dynamic_values(self, properties: Dict[str, Any]) -> Dict[str, Any]:
13721426 properties [key ] = return_value
13731427 return properties
13741428
1429+ def _replace_dynamic_values_with_root_key (
1430+ self , properties : Dict [str , Any ], root_key = None
1431+ ) -> Dict [str , Any ]:
1432+ value = properties [root_key ]
1433+ if isinstance (value , dict ):
1434+ properties [root_key ] = self ._replace_dynamic_values (value )
1435+ elif isinstance (value , list ):
1436+ properties [root_key ] = [self ._replace_dynamic_value (item ) for item in value ]
1437+ else :
1438+ return_value = self ._replace_dynamic_value (value )
1439+ properties [root_key ] = return_value
1440+ return properties
1441+
13751442 def _replace_dynamic_value (self , original_value : Any ) -> Any :
13761443 pattern = r"\{\{(.*?)\}\}"
13771444
0 commit comments