From 09ff44a3bfd412ee97a711483a5f64c8815436de Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Thu, 30 Nov 2023 19:05:41 -0500 Subject: [PATCH] feat: updates create_new_with_filter with more filter types and management operations The create_new_with_filter does not support all of the filter types trestle does. This adds the additional supported types. It also adds optional markdown creation for continued editing. Signed-off-by: Jennifer Power --- tests/data/json/test_comp.json | 2 +- tests/trestlebot/tasks/authored/test_ssp.py | 40 ++++++++++-- trestlebot/tasks/authored/ssp.py | 71 ++++++++++++++------- 3 files changed, 84 insertions(+), 29 deletions(-) diff --git a/tests/data/json/test_comp.json b/tests/data/json/test_comp.json index ec6ee89a..77075f56 100644 --- a/tests/data/json/test_comp.json +++ b/tests/data/json/test_comp.json @@ -64,7 +64,7 @@ }, "components": [ { - "uuid": "8220b305-0271-45f9-8a21-40ab6f197f70", + "uuid": "8220b305-0271-45f9-8a21-40ab6f197f78", "type": "Service", "title": "test_comp", "description": "test comp", diff --git a/tests/trestlebot/tasks/authored/test_ssp.py b/tests/trestlebot/tasks/authored/test_ssp.py index 50107fba..6db2fa43 100644 --- a/tests/trestlebot/tasks/authored/test_ssp.py +++ b/tests/trestlebot/tasks/authored/test_ssp.py @@ -246,28 +246,56 @@ def test_create_new_with_filter(tmp_trestle_dir: str) -> None: _ = testutils.setup_for_profile(trestle_root, test_prof_filter, test_prof_filter) ssp_name = "new_ssp" + new_md_path = f"{markdown_dir}/{ssp_name}" input_ssp = test_ssp_output # Call create_new_with_filter with new profile - authored_ssp.create_new_with_filter(ssp_name, input_ssp, test_prof_filter, []) + authored_ssp.create_new_with_filter( + ssp_name, input_ssp, markdown_path=new_md_path, profile_name=test_prof_filter + ) + + ssp_index.reload() assert ssp_index.get_profile_by_ssp(ssp_name) == test_prof_filter assert test_comp in ssp_index.get_comps_by_ssp(ssp_name) - _, mpath = load_validate_model_name( + model_path = ModelUtils.get_model_path_for_name_and_class( trestle_root, ssp_name, ossp.SystemSecurityPlan, FileContentType.JSON ) - assert mpath.exists() + assert model_path.exists() ssp_name = "new_ssp_2" + new_md_path = f"{markdown_dir}/{ssp_name}" # Call create_new_with_filter with a single compdef - authored_ssp.create_new_with_filter(ssp_name, input_ssp, "", [test_comp_2]) + authored_ssp.create_new_with_filter( + ssp_name, input_ssp, markdown_path=new_md_path, compdefs=[test_comp_2] + ) + ssp_index.reload() assert ssp_index.get_profile_by_ssp(ssp_name) == test_prof assert test_comp not in ssp_index.get_comps_by_ssp(ssp_name) assert test_comp_2 in ssp_index.get_comps_by_ssp(ssp_name) - _, mpath = load_validate_model_name( + ssp, model_path = load_validate_model_name( trestle_root, ssp_name, ossp.SystemSecurityPlan, FileContentType.JSON ) - assert mpath.exists() + assert model_path.exists() + + assert len(ssp.system_implementation.components) == 1 + + component_names = [ + component.title for component in ssp.system_implementation.components + ] + assert test_comp_2 in component_names + assert test_comp not in component_names + + # Check that without model path the ssp_index is not updated + ssp_name = "new_ssp_3" + authored_ssp.create_new_with_filter( + ssp_name, input_ssp, implementation_status=["implemented"] + ) + ssp_index.reload() + with pytest.raises( + AuthoredObjectException, match="SSP new_ssp_3 does not exists in the index" + ): + ssp_index.get_profile_by_ssp(ssp_name) diff --git a/trestlebot/tasks/authored/ssp.py b/trestlebot/tasks/authored/ssp.py index ece051f2..65cc6eaa 100644 --- a/trestlebot/tasks/authored/ssp.py +++ b/trestlebot/tasks/authored/ssp.py @@ -23,9 +23,11 @@ from typing import Any, Dict, List, Optional from trestle.common.err import TrestleError +from trestle.common.model_utils import ModelUtils from trestle.core.commands.author.ssp import SSPFilter from trestle.core.commands.common.return_codes import CmdReturnCodes from trestle.core.repository import AgileAuthoring +from trestle.oscal.component import ComponentDefinition from trestlebot.const import COMPDEF_KEY_NAME, LEVERAGED_SSP_KEY_NAME, PROFILE_KEY_NAME from trestlebot.tasks.authored.base_authored import ( @@ -251,20 +253,29 @@ def create_new_with_filter( self, ssp_name: str, input_ssp: str, - profile_name: str, - compdefs: List[str], + version: str = "", + markdown_path: str = "", + profile_name: str = "", + compdefs: Optional[List[str]] = None, + implementation_status: Optional[List[str]] = None, + control_origination: Optional[List[str]] = None, ) -> None: """ - Create new ssp from an ssp with filtering by profile and component definitions + Create new ssp from an ssp with filters. Args: ssp_name: Output name for ssp input_ssp: Input ssp to filter - profile_name: Profile to filter by - compdefs: List of component definitions to filter by + profile_name: Optional profile to filter by + compdefs: Optional list of component definitions to filter by + implementation_status: Optional implementation status to filter by + control_origination: Optional control origination to filter by + markdown_path: Optional top-level markdown path to write to for continued editing. Notes: - This will generate SSP markdown and an index entry for a new managed SSP. + This will transform the SSP with filters. If markdown_path is provided, it will + also generate SSP markdown and an index entry for a new managed SSP for continued + management in the workspace. """ # Create new ssp by filtering input ssp @@ -272,17 +283,28 @@ def create_new_with_filter( trestle_path = pathlib.Path(trestle_root) ssp_filter: SSPFilter = SSPFilter() + components_title: Optional[List[str]] = None + if compdefs: + components_title = [] + for comp_def_name in compdefs: + comp_def, _ = ModelUtils.load_model_for_class( + trestle_path, comp_def_name, ComponentDefinition + ) + components_title.extend( + [component.title for component in comp_def.components] + ) + try: exit_code = ssp_filter.filter_ssp( trestle_root=trestle_path, ssp_name=input_ssp, profile_name=profile_name, out_name=ssp_name, - regenerate=False, - components=compdefs, - version="", - implementation_status=None, - control_origination=None, + regenerate=True, + components=components_title, + version=version, + implementation_status=implementation_status, + control_origination=control_origination, ) if exit_code != CmdReturnCodes.SUCCESS.value: @@ -294,17 +316,22 @@ def create_new_with_filter( f"Trestle filtering failed for {input_ssp}: {e}" ) - # Retrieve index information from existing ssp - if not profile_name: - profile_name = self.ssp_index.get_profile_by_ssp(input_ssp) - - if not compdefs: - compdefs = self.ssp_index.get_comps_by_ssp(input_ssp) + # If markdown_path is provided, create a new managed ssp. + # this will eventually need to have a JSON to MD recovery to + # reduce manual editing. + if markdown_path: + if not profile_name: + profile_name = self.ssp_index.get_profile_by_ssp(input_ssp) - leveraged_ssp = self.ssp_index.get_leveraged_by_ssp(input_ssp) + if not compdefs: + compdefs = self.ssp_index.get_comps_by_ssp(input_ssp) - # Add new information to index - self.ssp_index.add_new_ssp(ssp_name, profile_name, compdefs, leveraged_ssp) + leveraged_ssp = self.ssp_index.get_leveraged_by_ssp(input_ssp) - # Write out index - self.ssp_index.write_out() + self.create_new_default( + ssp_name, + profile_name, + compdefs, + markdown_path, + leveraged_ssp, + )