Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: updates create_new_with_filter with more filter types and management operations #88

Merged
merged 3 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tests/data/json/test_comp.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
46 changes: 37 additions & 9 deletions tests/trestlebot/tasks/authored/test_ssp.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def test_assemble(tmp_trestle_dir: str) -> None:
"""Test to test assemble functionality for SSPs"""
# Prepare the workspace and generate the markdown
trestle_root = pathlib.Path(tmp_trestle_dir)
md_path = f"{markdown_dir}/{test_ssp_output}"
md_path = os.path.join(markdown_dir, test_ssp_output)
args = testutils.setup_for_ssp(trestle_root, test_prof, [test_comp], md_path)
ssp_generate = SSPGenerate()
assert ssp_generate._run(args) == 0
Expand All @@ -72,7 +72,7 @@ def test_assemble_no_ssp_entry(tmp_trestle_dir: str) -> None:
"""Test to trigger failure for missing SSP index"""
# Prepare the workspace and generate the markdown
trestle_root = pathlib.Path(tmp_trestle_dir)
md_path = f"{markdown_dir}/{test_ssp_output}"
md_path = os.path.join(markdown_dir, test_ssp_output)
args = testutils.setup_for_ssp(trestle_root, test_prof, [test_comp], md_path)
ssp_generate = SSPGenerate()
assert ssp_generate._run(args) == 0
Expand Down Expand Up @@ -225,7 +225,7 @@ def test_create_new_with_filter(tmp_trestle_dir: str) -> None:
"""Test to create new SSP with filtering by profile"""
# Prepare the workspace and input ssp
trestle_root = pathlib.Path(tmp_trestle_dir)
md_path = f"{markdown_dir}/{test_ssp_output}"
md_path = os.path.join(markdown_dir, test_ssp_output)
args = testutils.setup_for_ssp(
trestle_root, test_prof, [test_comp, test_comp_2], md_path
)
Expand All @@ -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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using os.path.join a comment explaining new directory to be created with ssp name

new_md_path = os.path.join(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 = os.path.join(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 markdown 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)
71 changes: 49 additions & 22 deletions trestlebot/tasks/authored/ssp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -251,38 +253,58 @@ 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
trestle_root = self.get_trestle_root()
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:
Expand All @@ -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,
)