Example #1
0
    def import_model(self, model: OscalBaseModel, name: str, content_type='json') -> ManagedOSCAL:
        """Import OSCAL object into trestle repository."""
        logger.debug(f'Importing model {name} of type {model.__class__.__name__}.')
        model_alias = classname_to_alias(model.__class__.__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.')

        # Work out output directory and file
        plural_path = ModelUtils.model_type_to_model_dir(model_alias)

        desired_model_dir = self._root_dir / plural_path
        desired_model_path = desired_model_dir / name / (model_alias + '.' + content_type)
        desired_model_path = desired_model_path.resolve()

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

        content_type = FileContentType.to_content_type(pathlib.Path(desired_model_path).suffix)

        # Prepare actions
        top_element = Element(model)
        create_action = CreatePathAction(desired_model_path, True)
        write_action = WriteFileAction(desired_model_path, top_element, content_type)

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

        # Validate the imported file, rollback if unsuccessful
        success = False
        errmsg = ''
        try:
            success = self.validate_model(model.__class__, name)
            if not success:
                errmsg = f'Validation of model {name} did not pass'
                logger.error(errmsg)
        except Exception as err:
            logger.error(errmsg)
            errmsg = f'Import of model {name} failed. Validation failed with error: {err}'

        if not success:
            # rollback in case of validation error or failure
            logger.debug(f'Rolling back import of model {name} to {desired_model_path}')
            try:
                import_plan.rollback()
            except TrestleError as err:
                logger.error(f'Failed to rollback: {err}. Remove {desired_model_path} to resolve state.')
            else:
                logger.debug(f'Successful rollback of import to {desired_model_path}')

            # raise trestle error
            raise TrestleError(errmsg)

        # all well; model was imported and validated successfully
        logger.debug(f'Model {name} of type {model.__class__.__name__} imported successfully.')
        return ManagedOSCAL(self._root_dir, model.__class__, name)
Example #2
0
def test_plan_execution(tmp_path, sample_nist_component_def: component.ComponentDefinition):
    """Test successful execution of a valid plan."""
    content_type = FileContentType.YAML

    base_dir: pathlib.Path = pathlib.Path.joinpath(tmp_path, 'mycomponent')
    targets_dir: pathlib.Path = pathlib.Path.joinpath(base_dir, 'components')
    metadata_yaml: pathlib.Path = pathlib.Path.joinpath(base_dir, 'metadata.yaml')

    test_utils.ensure_trestle_config_dir(base_dir)

    # hand craft a split plan
    split_plan = Plan()
    split_plan.add_action(CreatePathAction(metadata_yaml))
    split_plan.add_action(
        WriteFileAction(
            metadata_yaml, Element(sample_nist_component_def.metadata, 'component-definition'), content_type
        )
    )
    # Test stringing a plan
    stringed = str(split_plan)
    assert len(stringed) > 0

    target_files: List[pathlib.Path] = []
    for index in range(len(sample_nist_component_def.components)):

        target_file: pathlib.Path = pathlib.Path.joinpath(targets_dir, f'component_{index}.yaml')
        target_files.append(target_file)
        split_plan.add_action(CreatePathAction(target_file))
        split_plan.add_action(
            WriteFileAction(target_file, Element(sample_nist_component_def.components[index], 'target'), content_type)
        )

    # execute the plan
    split_plan.execute()
    assert base_dir.exists()
    assert targets_dir.exists()
    assert metadata_yaml.exists()
    for target_file in target_files:
        assert target_file.exists()

    split_plan.rollback()
    assert base_dir.exists() is True
    assert targets_dir.exists() is False
    assert metadata_yaml.exists() is False
    for target_file in target_files:
        target_file.exists()
Example #3
0
def test_plan_execution(tmp_dir, sample_target_def: target.TargetDefinition):
    """Test successful execution of a valid plan."""
    content_type = FileContentType.YAML

    base_dir: pathlib.Path = pathlib.Path.joinpath(tmp_dir, 'mytarget')
    targets_dir: pathlib.Path = pathlib.Path.joinpath(base_dir, 'targets')
    metadata_yaml: pathlib.Path = pathlib.Path.joinpath(
        base_dir, 'metadata.yaml')

    test_utils.ensure_trestle_config_dir(base_dir)

    # hand craft a split plan
    split_plan = Plan()
    split_plan.add_action(CreatePathAction(metadata_yaml))
    split_plan.add_action(
        WriteFileAction(
            metadata_yaml,
            Element(sample_target_def.metadata, 'target-definition'),
            content_type))

    target_files: List[pathlib.Path] = []
    for tid, t in sample_target_def.targets.items():
        target_file: pathlib.Path = pathlib.Path.joinpath(
            targets_dir, tid + '.yaml')
        target_files.append(target_file)
        split_plan.add_action(CreatePathAction(target_file))
        split_plan.add_action(
            WriteFileAction(target_file, Element(t, 'target'), content_type))

    # execute the plan
    split_plan.execute()
    assert base_dir.exists()
    assert targets_dir.exists()
    assert metadata_yaml.exists()
    for target_file in target_files:
        assert target_file.exists()

    split_plan.rollback()
    assert base_dir.exists() is True
    assert targets_dir.exists() is False
    assert metadata_yaml.exists() is False
    for target_file in target_files:
        target_file.exists() is False
Example #4
0
    def _run(self, args: argparse.Namespace) -> int:
        """Top level import run command."""
        try:
            log.set_log_level_from_args(args)
            trestle_root = args.trestle_root
            if not file_utils.is_valid_project_root(trestle_root):
                raise TrestleRootError(
                    f'Attempt to import from non-valid trestle project root {trestle_root}'
                )

            input_uri = args.file
            if cache.FetcherFactory.in_trestle_directory(
                    trestle_root, input_uri):
                raise TrestleError(
                    f'Imported file {input_uri} cannot be from current trestle project. Use duplicate instead.'
                )

            content_type = FileContentType.to_content_type(
                '.' + input_uri.split('.')[-1])

            fetcher = cache.FetcherFactory.get_fetcher(trestle_root,
                                                       str(input_uri))

            model_read, parent_alias = fetcher.get_oscal(True)

            plural_path = ModelUtils.model_type_to_model_dir(parent_alias)

            output_name = args.output

            desired_model_dir = trestle_root / plural_path
            desired_model_path: pathlib.Path = desired_model_dir / output_name / parent_alias
            desired_model_path = desired_model_path.with_suffix(
                FileContentType.to_file_extension(content_type)).resolve()

            if desired_model_path.exists():
                raise TrestleError(
                    f'Cannot import because file to be imported here: {desired_model_path} already exists.'
                )

            if args.regenerate:
                logger.debug(
                    f'regenerating uuids in imported file {input_uri}')
                model_read, lut, nchanged = ModelUtils.regenerate_uuids(
                    model_read)
                logger.debug(
                    f'uuid lut has {len(lut.items())} entries and {nchanged} refs were updated'
                )

            top_element = Element(model_read)
            create_action = CreatePathAction(desired_model_path, True)
            write_action = WriteFileAction(desired_model_path, top_element,
                                           content_type)

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

            import_plan.execute()

            args = argparse.Namespace(file=desired_model_path,
                                      verbose=args.verbose,
                                      trestle_root=args.trestle_root,
                                      type=None,
                                      all=None)
            rollback = False
            try:
                rc = validatecmd.ValidateCmd()._run(args)
                if rc > 0:
                    logger.warning(
                        f'Validation of imported file {desired_model_path} did not pass'
                    )
                    rollback = True
            except TrestleError as err:
                logger.warning(
                    f'Import of {str(input_uri)} failed with validation error: {err}'
                )
                rollback = True

            if rollback:
                logger.debug(
                    f'Rolling back import of {str(input_uri)} to {desired_model_path}'
                )
                try:
                    import_plan.rollback()
                except TrestleError as err:
                    raise TrestleError(
                        f'Import failed in plan rollback: {err}. Manually remove {desired_model_path} to recover.'
                    )
                logger.debug(
                    f'Successful rollback of import to {desired_model_path}')
                return CmdReturnCodes.COMMAND_ERROR.value

            return CmdReturnCodes.SUCCESS.value

        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(
                e, logger, 'Error while importing OSCAL file')