From 174497aacd09c9e7f961dd105fc5f4c3f0669307 Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Fri, 21 Jul 2023 14:36:01 -0400 Subject: [PATCH] feat: adds create default profile logic to AuthoredProfile type Refs: PSCE-173 Signed-off-by: Jennifer Power --- .../trestlebot/tasks/authored/test_profile.py | 81 +++++++++++++++++++ trestlebot/tasks/authored/compdef.py | 2 +- trestlebot/tasks/authored/profile.py | 78 +++++++++++++++++- 3 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 tests/trestlebot/tasks/authored/test_profile.py diff --git a/tests/trestlebot/tasks/authored/test_profile.py b/tests/trestlebot/tasks/authored/test_profile.py new file mode 100644 index 00000000..73521b94 --- /dev/null +++ b/tests/trestlebot/tasks/authored/test_profile.py @@ -0,0 +1,81 @@ +#!/usr/bin/python + +# Copyright 2023 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Test for Trestle Bot Authored Profile.""" + +import os +import pathlib + +from trestle.common.model_utils import ModelUtils +from trestle.core.models.file_content_type import FileContentType +from trestle.oscal.profile import Method, Profile + +from tests import testutils +from trestlebot.tasks.authored.profile import AuthoredProfile + + +test_prof = "simplified_nist_profile" +test_cat = "simplified_nist_catalog" + + +def test_create_new_default(tmp_trestle_dir: str) -> None: + """Test creating new default profile""" + # Prepare the workspace + trestle_root = pathlib.Path(tmp_trestle_dir) + _ = testutils.setup_for_catalog(trestle_root, test_cat, "") + + authored_prof = AuthoredProfile(tmp_trestle_dir) + + cat_path = os.path.join("catalogs", test_cat, "catalog.json") + + authored_prof.create_new_default(cat_path, test_prof) + + prof, _ = ModelUtils.load_model_for_class( + trestle_root, test_prof, Profile, FileContentType.JSON + ) + + assert prof.merge is not None + assert prof.merge.combine is not None + assert prof.merge.combine.method is Method.merge + + assert prof.imports is not None + assert prof.imports[0].include_all is not None + assert cat_path in prof.imports[0].href + + +def test_create_new_default_existing(tmp_trestle_dir: str) -> None: + """Test updating an existing profile""" + # Prepare the workspace + trestle_root = pathlib.Path(tmp_trestle_dir) + _ = testutils.setup_for_profile(trestle_root, test_prof, "") + + authored_prof = AuthoredProfile(tmp_trestle_dir) + + cat_path = os.path.join("catalogs", test_cat, "catalog.json") + + authored_prof.create_new_default(cat_path, test_prof) + + prof, _ = ModelUtils.load_model_for_class( + trestle_root, test_prof, Profile, FileContentType.JSON + ) + + assert prof.merge is not None + assert prof.merge.combine is not None + assert prof.merge.combine.method is Method.merge + + assert prof.imports is not None + assert prof.imports[0].include_all is not None + assert cat_path in prof.imports[0].href diff --git a/trestlebot/tasks/authored/compdef.py b/trestlebot/tasks/authored/compdef.py index 89c96b2a..28c04aaa 100644 --- a/trestlebot/tasks/authored/compdef.py +++ b/trestlebot/tasks/authored/compdef.py @@ -130,7 +130,7 @@ def create_new_default( compdef_name, comp.ComponentDefinition, FileContentType.JSON, - ) # type: ignore + ) existing_comp_data_path = pathlib.Path(comp_data_path) except TrestleNotFoundError: comp_data = gens.generate_sample_model(comp.ComponentDefinition) # type: ignore diff --git a/trestlebot/tasks/authored/profile.py b/trestlebot/tasks/authored/profile.py index 5603a691..0bda8e64 100644 --- a/trestlebot/tasks/authored/profile.py +++ b/trestlebot/tasks/authored/profile.py @@ -18,10 +18,19 @@ import os import pathlib +import shutil +from typing import Optional, Type -from trestle.common.err import TrestleError +import trestle.core.generators as gens +import trestle.oscal.profile as prof +from trestle.common import const +from trestle.common.err import TrestleError, TrestleNotFoundError +from trestle.common.load_validate import load_validate_model_name +from trestle.common.model_utils import ModelUtils from trestle.core.commands.author.profile import ProfileAssemble, ProfileGenerate from trestle.core.commands.common.return_codes import CmdReturnCodes +from trestle.core.models.file_content_type import FileContentType +from trestle.oscal.common import IncludeAll from trestlebot.tasks.authored.base_authored import ( AuthoredObjectException, @@ -87,3 +96,70 @@ def regenerate(self, model_path: str, markdown_path: str) -> None: ) except TrestleError as e: raise AuthoredObjectException(f"Trestle generate failed for {profile}: {e}") + + def create_new_default(self, import_path: str, profile_name: str) -> None: + """ + Create new profile with default info + + Args: + import_path: Reference to imported catalog or profile (ex. catalogs/example/catalog.json) + profile_name: Output profile name + + Notes: + This will attempt to read the output profile at the current name + and modify it. If one does not exist, a new profile will be created + with the specified name. The provided import will overwrite the first + import in the list. + + """ + trestle_root: pathlib.Path = pathlib.Path(self.get_trestle_root()) + + profile_data: Type[prof.Profile] + existing_prof_data_path: Optional[pathlib.Path] + + # Attempt to load the existing profile if not found create a new instance + try: + profile_data, prof_data_path = load_validate_model_name( + trestle_root, + profile_name, + prof.Profile, + FileContentType.JSON, + ) + existing_prof_data_path = pathlib.Path(prof_data_path) + except TrestleNotFoundError: + profile_data = gens.generate_sample_model(prof.Profile) # type: ignore + existing_prof_data_path = ModelUtils.get_model_path_for_name_and_class( + trestle_root, + profile_name, + prof.Profile, + FileContentType.JSON, + ) + if existing_prof_data_path is None: + raise AuthoredObjectException( + f"Error defining workspace name for profile {profile_name}" + ) + + # Update imports + profile_import: prof.Import = gens.generate_sample_model(prof.Import) + profile_import.href = const.TRESTLE_HREF_HEADING + import_path + profile_import.include_all = gens.generate_sample_model(IncludeAll) + + profile_data.imports[0] = profile_import + + # Set up default values for merge settings. + merge_object: prof.Merge = gens.generate_sample_model(prof.Merge) + combine_object: prof.Combine = gens.generate_sample_model(prof.Combine) + combine_object.method = prof.Method.merge + merge_object.combine = combine_object + merge_object.as_is = True + + profile_data.merge = merge_object + + profile_path = pathlib.Path(existing_prof_data_path) + if profile_path.parent.exists(): + shutil.rmtree(str(profile_path.parent)) + + ModelUtils.update_last_modified(profile_data) # type: ignore + + profile_path.parent.mkdir(parents=True, exist_ok=True) + profile_data.oscal_write(path=profile_path) # type: ignore