コード例 #1
0
def test_path_to_file_content_type(tmp_trestle_dir: Path) -> None:
    """Test path_to_file_content_type method."""
    tmp_stem = tmp_trestle_dir / 'content_type_test'

    tmp_file = tmp_stem.with_suffix('.json')
    tmp_file.touch()
    assert FileContentType.JSON == FileContentType.path_to_content_type(
        tmp_file)
    assert FileContentType.path_to_file_extension(tmp_file) == '.json'
    tmp_file.unlink()

    tmp_file = tmp_stem.with_suffix('.yaml')
    tmp_file.touch()
    assert FileContentType.YAML == FileContentType.path_to_content_type(
        tmp_file)
    assert FileContentType.path_to_file_extension(tmp_file) == '.yaml'
    tmp_file.unlink()

    tmp_file = tmp_stem.with_suffix('.yml')
    tmp_file.touch()
    assert FileContentType.YAML == FileContentType.path_to_content_type(
        tmp_file)
    assert FileContentType.path_to_file_extension(tmp_file) == '.yml'
    tmp_file.unlink()

    assert FileContentType.UNKNOWN == FileContentType.path_to_content_type(
        tmp_file)
    assert FileContentType.path_to_file_extension(tmp_file) == ''
コード例 #2
0
ファイル: model_utils.py プロジェクト: IBM/compliance-trestle
    def load_top_level_model(
        trestle_root: pathlib.Path,
        model_name: str,
        model_class: Type[TopLevelOscalModel],
        file_content_type: Optional[FileContentType] = None
    ) -> Tuple[Union[OscalBaseModel, List[OscalBaseModel], Dict[
            str, OscalBaseModel]], pathlib.Path]:
        """Load a model by name and model class and infer file content type if not specified.

        If you need to load an existing model but its content type may not be known, use this method.
        But the file content type should be specified if it is somehow known.
        """
        root_model_path = ModelUtils._root_path_for_top_level_model(
            trestle_root, model_name, model_class)
        if file_content_type is None:
            file_content_type = FileContentType.path_to_content_type(
                root_model_path)
        if not FileContentType.is_readable_file(file_content_type):
            raise TrestleError(
                f'Unable to load model {model_name} without specifying json or yaml.'
            )
        full_model_path = root_model_path.with_suffix(
            FileContentType.to_file_extension(file_content_type))
        _, _, model = ModelUtils.load_distributed(full_model_path,
                                                  trestle_root)
        return model, full_model_path
コード例 #3
0
ファイル: repository.py プロジェクト: IBM/compliance-trestle
    def __init__(self, root_dir: pathlib.Path, model_type: Type[OscalBaseModel], name: str) -> None:
        """Initialize repository OSCAL model object."""
        if not file_utils.is_valid_project_root(root_dir):
            raise TrestleError(f'Provided root directory {str(root_dir)} is not a valid Trestle root directory.')
        self._root_dir = root_dir
        self._model_type = model_type
        self._model_name = name

        # set model alais and dir
        self.model_alias = classname_to_alias(self._model_type.__name__, AliasMode.JSON)
        if parser.to_full_model_name(self.model_alias) is None:
            raise TrestleError(f'Given model {self.model_alias} is not a top level model.')

        plural_path = ModelUtils.model_type_to_model_dir(self.model_alias)
        self.model_dir = self._root_dir / plural_path / self._model_name

        if not self.model_dir.exists() or not self.model_dir.is_dir():
            raise TrestleError(f'Model dir {self._model_name} does not exist.')

        file_content_type = FileContentType.path_to_content_type(self.model_dir / self.model_alias)
        if file_content_type == FileContentType.UNKNOWN:
            raise TrestleError(f'Model file for model {self._model_name} does not exist.')
        self.file_content_type = file_content_type

        filepath = pathlib.Path(
            self.model_dir,
            self.model_alias + FileContentType.path_to_file_extension(self.model_dir / self.model_alias)
        )

        self.filepath = filepath
コード例 #4
0
ファイル: repository.py プロジェクト: IBM/compliance-trestle
    def delete_model(self, model_type: Type[OscalBaseModel], name: str) -> bool:
        """Delete an OSCAL model from repository."""
        logger.debug(f'Deleting model {name} of type {model_type.__name__}.')
        model_alias = classname_to_alias(model_type.__name__, AliasMode.JSON)
        if parser.to_full_model_name(model_alias) is None:
            raise TrestleError(f'Given model {model_alias} is not a top level model.')
        plural_path = ModelUtils.model_type_to_model_dir(model_alias)
        desired_model_dir = self._root_dir / plural_path / name

        if not desired_model_dir.exists() or not desired_model_dir.is_dir():
            raise TrestleError(f'Model {name} does not exist.')
        shutil.rmtree(desired_model_dir)

        # remove model from dist directory if it exists
        dist_model_dir = self._root_dir / const.TRESTLE_DIST_DIR / plural_path
        file_content_type = FileContentType.path_to_content_type(dist_model_dir / name)
        if file_content_type != FileContentType.UNKNOWN:
            file_path = pathlib.Path(
                dist_model_dir, name + FileContentType.path_to_file_extension(dist_model_dir / name)
            )
            logger.debug(f'Deleting model {name} from dist directory.')
            os.remove(file_path)

        logger.debug(f'Model {name} deleted successfully.')
        return True
コード例 #5
0
ファイル: replicate.py プロジェクト: IBM/compliance-trestle
    def replicate_object(cls, model_alias: str, args: argparse.Namespace) -> int:
        """
        Core replicate routine invoked by subcommands.

        Args:
            model_alias: Name of the top level model in the trestle directory.
            args: CLI arguments
        Returns:
            A return code that can be used as standard posix codes. 0 is success.
        """
        logger.debug('Entering replicate_object.')

        # 1 Bad working directory if not running from current working directory
        trestle_root = args.trestle_root  # trestle root is set via command line in args. Default is cwd.
        if not trestle_root or not file_utils.is_valid_project_root(trestle_root):
            raise TrestleError(f'Given directory: {trestle_root} is not a trestle project.')

        plural_path = ModelUtils.model_type_to_model_dir(model_alias)

        # 2 Check that input file given exists.

        input_file_stem = trestle_root / plural_path / args.name / model_alias
        content_type = FileContentType.path_to_content_type(input_file_stem)
        if content_type == FileContentType.UNKNOWN:
            raise TrestleError(
                f'Input file {args.name} has no json or yaml file at expected location {input_file_stem}.'
            )

        input_file = input_file_stem.with_suffix(FileContentType.to_file_extension(content_type))

        # 3 Distributed load from file
        _, model_alias, model_instance = ModelUtils.load_distributed(input_file, trestle_root)

        rep_model_path = trestle_root / plural_path / args.output / (
            model_alias + FileContentType.to_file_extension(content_type)
        )

        if rep_model_path.exists():
            raise TrestleError(f'OSCAL file to be replicated here: {rep_model_path} exists.')

        if args.regenerate:
            logger.debug(f'regenerating uuids for model {input_file}')
            model_instance, uuid_lut, n_refs_updated = ModelUtils.regenerate_uuids(model_instance)
            logger.debug(f'{len(uuid_lut)} uuids generated and {n_refs_updated} references updated')

        # 4 Prepare actions and plan
        top_element = Element(model_instance)
        create_action = CreatePathAction(rep_model_path, True)
        write_action = WriteFileAction(rep_model_path, top_element, content_type)

        # create a plan to create the directory and imported file.
        replicate_plan = Plan()
        replicate_plan.add_action(create_action)
        replicate_plan.add_action(write_action)

        replicate_plan.execute()

        return CmdReturnCodes.SUCCESS.value
コード例 #6
0
ファイル: model_utils.py プロジェクト: IBM/compliance-trestle
    def full_path_for_top_level_model(
        trestle_root: pathlib.Path,
        model_name: str,
        model_class: Type[TopLevelOscalModel],
    ) -> pathlib.Path:
        """
        Find the full path of an existing model given its name and model type but no file content type.

        Use this method when you need the path of a model but you don't know the file content type.
        This method should only be called if the model needs to exist already in the trestle directory.
        If you do know the file content type, use path_for_top_level_model instead.
        """
        root_model_path = ModelUtils._root_path_for_top_level_model(
            trestle_root, model_name, model_class)
        file_content_type = FileContentType.path_to_content_type(
            root_model_path)
        if not FileContentType.is_readable_file(file_content_type):
            raise TrestleError(
                f'Unable to load model {model_name} as json or yaml.')
        return root_model_path.with_suffix(
            FileContentType.to_file_extension(file_content_type))
コード例 #7
0
ファイル: model_utils.py プロジェクト: IBM/compliance-trestle
    def load_distributed(
        abs_path: Path,
        abs_trestle_root: Path,
        collection_type: Optional[Type[Any]] = None
    ) -> Tuple[Type[OscalBaseModel], str, Union[
            OscalBaseModel, List[OscalBaseModel], Dict[str, OscalBaseModel]]]:
        """
        Given path to a model, load the model.

        If the model is decomposed/split/distributed,the decomposed models are loaded recursively.

        Args:
            abs_path: The path to the file/directory to be loaded.
            abs_trestle_root: The trestle project root directory.
            collection_type: The type of collection model, if it is a collection model.
                typing.List is the only collection type handled or expected.
                Defaults to None.

        Returns:
            Return a tuple of Model Type (e.g. class 'trestle.oscal.catalog.Catalog'),
            Model Alias (e.g. 'catalog.metadata') and Instance of the Model.
            If the model is decomposed/split/distributed, the instance of the model contains
            the decomposed models loaded recursively.
        """
        # if trying to load file that does not exist, load path instead
        if not abs_path.exists():
            abs_path = abs_path.with_name(abs_path.stem)

        if not abs_path.exists():
            raise TrestleNotFoundError(f'File {abs_path} not found for load.')

        if collection_type:
            # If the path contains a list type model
            if collection_type is list:
                return ModelUtils._load_list(abs_path, abs_trestle_root)
            # the only other collection type in OSCAL is dict, and it only applies to include_all,
            # which is too granular ever to be loaded by this routine
            else:
                raise TrestleError(
                    f'Collection type {collection_type} not recognized for distributed load.'
                )

        # Get current model
        primary_model_type, primary_model_alias = ModelUtils.get_stripped_model_type(
            abs_path, abs_trestle_root)
        primary_model_instance: Optional[OscalBaseModel] = None

        # is this an attempt to load an actual json or yaml file?
        content_type = FileContentType.path_to_content_type(abs_path)
        # if file is sought but it doesn't exist, ignore and load as decomposed model
        if FileContentType.is_readable_file(
                content_type) and abs_path.exists():
            primary_model_instance = primary_model_type.oscal_read(abs_path)
        # Is model decomposed?
        decomposed_dir = abs_path.with_name(abs_path.stem)

        if decomposed_dir.exists():
            aliases_not_to_be_stripped = []
            instances_to_be_merged: List[OscalBaseModel] = []

            for local_path in sorted(
                    trestle.common.file_utils.iterdir_without_hidden_files(
                        decomposed_dir)):
                if local_path.is_file():
                    model_type, model_alias, model_instance = ModelUtils.load_distributed(
                        local_path, abs_trestle_root)
                    aliases_not_to_be_stripped.append(
                        model_alias.split('.')[-1])
                    instances_to_be_merged.append(model_instance)

                elif local_path.is_dir():
                    model_type, model_alias = ModelUtils.get_stripped_model_type(
                        local_path, abs_trestle_root)
                    # Only load the directory if it is a collection model. Otherwise do nothing - it gets loaded when
                    # iterating over the model file

                    # If a model is just a container for a list e.g.
                    # class Foo(OscalBaseModel):  noqa: E800
                    #      __root__: List[Bar]    noqa: E800
                    # You need to test whether first a root key exists
                    # then whether the outer_type of root is a collection.
                    # Alternative is to do a try except to avoid the error for an unknown key.

                    if model_type.is_collection_container():
                        # This directory is a decomposed List or Dict
                        collection_type = model_type.get_collection_type()
                        model_type, model_alias, model_instance = ModelUtils.load_distributed(
                            local_path, abs_trestle_root, collection_type)
                        aliases_not_to_be_stripped.append(
                            model_alias.split('.')[-1])
                        instances_to_be_merged.append(model_instance)
            primary_model_dict = {}
            if primary_model_instance is not None:
                primary_model_dict = primary_model_instance.__dict__

            merged_model_type, merged_model_alias = ModelUtils.get_stripped_model_type(
                abs_path, abs_trestle_root, aliases_not_to_be_stripped)

            # The following use of top_level is to allow loading of a top level model by name only, e.g. MyCatalog
            # There may be a better overall way to approach this.
            top_level = len(merged_model_alias.split('.')) == 1

            for i in range(len(aliases_not_to_be_stripped)):
                alias = aliases_not_to_be_stripped[i]
                instance = instances_to_be_merged[i]
                if hasattr(
                        instance, '__dict__'
                ) and '__root__' in instance.__dict__ and isinstance(
                        instance, OscalBaseModel):
                    instance = instance.__dict__['__root__']
                if top_level and not primary_model_dict:
                    primary_model_dict = instance.__dict__
                else:
                    primary_model_dict[alias] = instance

            merged_model_instance = merged_model_type(
                **primary_model_dict)  # type: ignore
            return merged_model_type, merged_model_alias, merged_model_instance
        return primary_model_type, primary_model_alias, primary_model_instance