Skip to content

Commit d17ac7d

Browse files
authored
Implement canary file generation functionality from contract test PatchInputs (#1074)
* Implement canary file generation functionality from contract test input for Patch Canaries * Add functionality for add/remove operations. Fix dynamic variables * Refactor to use jsonpatch and improve naming.
1 parent d030eea commit d17ac7d

File tree

4 files changed

+894
-17
lines changed

4 files changed

+894
-17
lines changed

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ include_trailing_comma = true
2929
combine_as_imports = True
3030
force_grid_wrap = 0
3131
known_first_party = rpdk
32-
known_third_party = boto3,botocore,cfn_tools,cfnlint,colorama,docker,hypothesis,jinja2,jsonschema,nested_lookup,ordered_set,pkg_resources,pytest,pytest_localserver,requests,setuptools,yaml
32+
known_third_party = boto3,botocore,cfn_tools,cfnlint,colorama,docker,hypothesis,jinja2,jsonpatch,jsonschema,nested_lookup,ordered_set,pkg_resources,pytest,pytest_localserver,requests,setuptools,yaml
3333

3434
[tool:pytest]
3535
# can't do anything about 3rd part modules, so don't spam us

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def find_version(*file_paths):
4141
"boto3>=1.10.20",
4242
"Jinja2>=3.1.2",
4343
"markupsafe>=2.1.0",
44+
"jsonpatch",
4445
"jsonschema>=3.0.0,<=4.17.3",
4546
"pytest>=4.5.0",
4647
"pytest-random-order>=1.0.4",

src/rpdk/core/project.py

Lines changed: 83 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from typing import Any, Dict
1212
from uuid import uuid4
1313

14+
import jsonpatch
1415
import yaml
1516
from botocore.exceptions import ClientError, WaiterError
1617
from jinja2 import Environment, PackageLoader, select_autoescape
@@ -63,6 +64,13 @@
6364
TARGET_CANARY_FOLDER = "canary-bundle/canary"
6465
RPDK_CONFIG_FILE = ".rpdk-config"
6566
CANARY_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"
6674
CONTRACT_TEST_DEPENDENCY_FILE_NAME = "dependencies.yml"
6775
CANARY_DEPENDENCY_FILE_NAME = "bootstrap.yaml"
6876
CANARY_SETTINGS = "canarySettings"
@@ -76,7 +84,6 @@
7684
FILE_GENERATION_ENABLED = "file_generation_enabled"
7785
TYPE_NAME = "typeName"
7886
CONTRACT_TEST_FILE_NAMES = "contract_test_file_names"
79-
INPUT1_FILE_NAME = "inputs_1.json"
8087
FN_SUB = "Fn::Sub"
8188
FN_IMPORT_VALUE = "Fn::ImportValue"
8289
UUID = "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

Comments
 (0)