예제 #1
0
 def _run(self, args: argparse.Namespace) -> int:
     """Execute and process the args."""
     try:
         log.set_log_level_from_args(args)
         return self.replicate_object(args.model, args)
     except Exception as e:  # pragma: no cover
         return handle_generic_command_exception(e, logger, 'Error while replicating model')
예제 #2
0
    def assemble_model(cls, model_alias: str, args: argparse.Namespace) -> int:
        """Assemble a top level OSCAL model within the trestle dist directory."""
        log.set_log_level_from_args(args)
        logger.info(f'Assembling models of type {model_alias}.')

        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(
                args.trestle_root):
            raise TrestleRootError(
                f'Given directory {trestle_root} is not a trestle project.')

        model_names = []
        if args.name:
            model_names = [args.name]
            logger.info(
                f'Assembling single model of type {model_alias}: {args.name}.')
        else:
            model_names = ModelUtils.get_models_of_type(
                model_alias, trestle_root)
            nmodels = len(model_names)
            logger.info(
                f'Assembling {nmodels} found models of type {model_alias}.')
        if len(model_names) == 0:
            logger.info(f'No models found to assemble of type {model_alias}.')
            return CmdReturnCodes.SUCCESS.value

        for model_name in model_names:
            # contruct path to the model file name
            root_model_dir = trestle_root / ModelUtils.model_type_to_model_dir(
                model_alias)

            model_file_type = file_utils.get_contextual_file_type(
                root_model_dir / model_name)

            model_file_name = f'{model_alias}{FileContentType.to_file_extension(model_file_type)}'
            root_model_filepath = root_model_dir / model_name / model_file_name

            if not root_model_filepath.exists():
                raise TrestleError(
                    f'No top level model file at {root_model_dir}')

            # distributed load
            _, _, assembled_model = ModelUtils.load_distributed(
                root_model_filepath, args.trestle_root)
            plural_alias = ModelUtils.model_type_to_model_dir(model_alias)

            assembled_model_dir = trestle_root / const.TRESTLE_DIST_DIR / plural_alias

            assembled_model_filepath = assembled_model_dir / f'{model_name}.{args.extension}'

            plan = Plan()
            plan.add_action(CreatePathAction(assembled_model_filepath, True))
            plan.add_action(
                WriteFileAction(
                    assembled_model_filepath, Element(assembled_model),
                    FileContentType.to_content_type(f'.{args.extension}')))

            plan.execute()

        return CmdReturnCodes.SUCCESS.value
예제 #3
0
    def _run(self, args: argparse.Namespace) -> int:
        try:
            log.set_log_level_from_args(args)

            mode_args = argparse.Namespace(mode=VAL_MODE_ALL)
            validator = vfact.validator_factory.get(mode_args)

            return validator.validate(args)
        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(e, logger, 'Error while validating contents of a trestle model')
예제 #4
0
    def _run(self, args: argparse.Namespace) -> int:
        try:
            log.set_log_level_from_args(args)
            trestle_root = pathlib.Path(args.trestle_root)

            return self.filter_ssp(trestle_root, args.name, args.profile,
                                   args.output, args.regenerate, args.version)
        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(
                e, logger, 'Error generating the filtered ssp')
    def _run(self, args: argparse.Namespace) -> int:
        try:
            log.set_log_level_from_args(args)
            file_path: pathlib.Path = args.file.resolve()
            if not file_path.exists() or not file_path.is_file():
                raise TrestleError(
                    'File path provided does not exist or is a directory')

            element_str: str = args.element
            if ',' in element_str:
                logger.warning('Only a single element path is allowed.')

            return self.partial_object_validate(file_path, element_str)
        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(
                e, logger, 'Error while validating OSCAL file')
예제 #6
0
    def _run(self, args: argparse.Namespace) -> int:
        """Remove an OSCAL component/subcomponent to the specified component.

        This method takes input a filename and a list of comma-seperated element path. Element paths are field aliases.
        The method first finds the parent model from the file and loads the file into the model.
        Then the method executes 'remove' for each of the element paths specified.
        """
        try:
            log.set_log_level_from_args(args)
            args_dict = args.__dict__

            file_path = pathlib.Path(args_dict[const.ARG_FILE]).resolve()
            relative_path = file_path.relative_to(args.trestle_root)
            # Get parent model and then load json into parent model
            parent_model, parent_alias = ModelUtils.get_relative_model_type(
                relative_path)

            parent_object = parent_model.oscal_read(file_path)
            parent_element = Element(parent_object, parent_alias)

            add_plan = Plan()

            # Do _remove for each element_path specified in args
            element_paths: List[str] = str(
                args_dict[const.ARG_ELEMENT]).split(',')
            for elm_path_str in element_paths:
                element_path = ElementPath(elm_path_str)
                remove_action, parent_element = self.remove(
                    element_path, parent_element)
                add_plan.add_action(remove_action)

            create_action = CreatePathAction(file_path, True)
            write_action = WriteFileAction(
                file_path, parent_element,
                FileContentType.to_content_type(file_path.suffix))
            add_plan.add_action(remove_action)
            add_plan.add_action(create_action)
            add_plan.add_action(write_action)

            add_plan.execute()

            return CmdReturnCodes.SUCCESS.value

        except Exception as e:
            return err.handle_generic_command_exception(
                e, logger, 'Error while removing OSCAL component')
예제 #7
0
    def _run(self, args: argparse.Namespace) -> int:
        try:
            logger.debug('Entering trestle href.')

            log.set_log_level_from_args(args)

            profile_name: str = args.name

            new_href: str = args.href.strip("'")

            item_num = args.item

            return self.change_import_href(args.trestle_root, profile_name,
                                           new_href, item_num)
        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(
                e, logger,
                f'Error while changing href or import in profile: {e}')
예제 #8
0
    def _run(self, args: argparse.Namespace) -> int:
        try:
            logger.debug('Entering trestle describe.')

            log.set_log_level_from_args(args)

            if args.file:
                model_file = pathlib.Path(args.file)

                element = '' if not args.element else args.element.strip("'")
                results = self.describe(model_file.resolve(), element, args.trestle_root)

                return CmdReturnCodes.SUCCESS.value if len(results) > 0 else CmdReturnCodes.COMMAND_ERROR.value
            else:
                raise TrestleIncorrectArgsError('No file specified for command describe.')

        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(e, logger, 'Error while describing contents of a model')
예제 #9
0
    def create_object(cls, model_alias: str,
                      object_type: Type[TopLevelOscalModel],
                      args: argparse.Namespace) -> int:
        """Create a top level OSCAL object within the trestle directory, leveraging functionality in add."""
        log.set_log_level_from_args(args)
        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(
                args.trestle_root):
            raise err.TrestleRootError(
                f'Given directory {trestle_root} is not a trestle project.')

        plural_path = ModelUtils.model_type_to_model_dir(model_alias)

        desired_model_dir = trestle_root / plural_path / args.output

        desired_model_path = desired_model_dir / (model_alias + '.' +
                                                  args.extension)

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

        # Create sample model.
        sample_model = generators.generate_sample_model(
            object_type, include_optional=args.include_optional_fields)
        # Presuming top level level model not sure how to do the typing for this.
        sample_model.metadata.title = f'Generic {model_alias} created by trestle named {args.output}.'  # type: ignore
        sample_model.metadata.last_modified = datetime.now().astimezone()
        sample_model.metadata.oscal_version = trestle.oscal.OSCAL_VERSION
        sample_model.metadata.version = '0.0.0'

        top_element = Element(sample_model, model_alias)

        create_action = CreatePathAction(desired_model_path.resolve(), True)
        write_action = WriteFileAction(
            desired_model_path.resolve(), top_element,
            FileContentType.to_content_type(desired_model_path.suffix))

        # create a plan to write the directory and file.
        create_plan = Plan()
        create_plan.add_action(create_action)
        create_plan.add_action(write_action)
        create_plan.execute()
        return CmdReturnCodes.SUCCESS.value
예제 #10
0
 def _run(self, args: argparse.Namespace) -> int:
     try:
         log.set_log_level_from_args(args)
         trestle_root = pathlib.Path(args.trestle_root)
         # the original profile model name defaults to being the same as the new one
         prof_name = args.output if not args.name else args.name
         return self.assemble_profile(
             trestle_root=trestle_root,
             orig_profile_name=prof_name,
             md_name=args.markdown,
             new_profile_name=args.output,
             set_parameters=args.set_parameters,
             regenerate=args.regenerate,
             version=args.version,
             required_sections=args.required_sections,
             allowed_sections=args.allowed_sections)
     except Exception as e:  # pragma: no cover
         return handle_generic_command_exception(
             e, logger, 'Assembly of markdown to profile failed')
예제 #11
0
    def _run(self, args: argparse.Namespace) -> int:
        """Split an OSCAL file into elements."""
        try:
            log.set_log_level_from_args(args)
            trace.log('Entering trestle split.')
            # get the Model
            args_raw: Dict[str, str] = args.__dict__

            # remove any quotes passed in as on windows platforms
            elements_clean: str = args_raw[const.ARG_ELEMENT].strip("'")

            file_name = ''
            file_name = '' if not args_raw[const.ARG_FILE] else args_raw[const.ARG_FILE]
            # cwd must be in the model directory if file to split is not specified
            effective_cwd = pathlib.Path.cwd()

            return self.perform_split(effective_cwd, file_name, elements_clean, args.trestle_root)
        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(e, logger, 'Error while performing a split operation')
예제 #12
0
    def _initialize(self, args: argparse.Namespace) -> int:
        log.set_log_level_from_args(args)
        # Externalize
        self.trestle_root = args.trestle_root
        self.task_name = args.task_name

        try:
            self.global_ = args.__getattribute__('global')
        except AttributeError:
            self.global_ = None

        if self.task_name:
            self.task_path = self.trestle_root / self.task_name
            if not file_utils.is_directory_name_allowed(self.task_name):
                logger.error(
                    f'Task name {self.task_name} is invalid as it interferes with OSCAL and trestle reserved names.'
                )
                return CmdReturnCodes.COMMAND_ERROR.value

        rc = self._setup_template_dir(args)

        return rc
예제 #13
0
    def _run(self, args: argparse.Namespace) -> int:
        try:
            log.set_log_level_from_args(args)
            trestle_root: pathlib.Path = args.trestle_root
            if not file_utils.is_directory_name_allowed(args.output):
                raise TrestleError(
                    f'{args.output} is not an allowed directory name')

            yaml_header: dict = {}
            if args.yaml_header:
                try:
                    logging.debug(
                        f'Loading yaml header file {args.yaml_header}')
                    yaml = YAML()
                    yaml_header = yaml.load(
                        pathlib.Path(args.yaml_header).open('r'))
                except YAMLError as e:
                    raise TrestleError(
                        f'YAML error loading yaml header for ssp generation: {e}'
                    )

            # combine command line sections with any in the yaml header, with priority to command line
            sections_dict: Optional[Dict[str, str]] = None
            if args.sections:
                sections_dict = sections_to_dict(args.sections)

            profile_path = trestle_root / f'profiles/{args.name}/profile.json'

            markdown_path = trestle_root / args.output

            return self.generate_markdown(trestle_root, profile_path,
                                          markdown_path, yaml_header,
                                          args.overwrite_header_values,
                                          sections_dict,
                                          args.required_sections)
        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(
                e, logger, 'Generation of the profile markdown failed')
예제 #14
0
    def _run(self, args: argparse.Namespace) -> int:
        """Create a trestle project in the current directory."""
        try:
            log.set_log_level_from_args(args)
            dir_path: pathlib.Path = args.trestle_root
            if not dir_path.exists() or not dir_path.is_dir():
                raise TrestleRootError(
                    f'Initialization failed. Given directory {dir_path} does not exist or is not a directory.'
                )

            # Create directories
            self._create_directories(dir_path)

            # Create config file
            self._copy_config_file(dir_path)

            logger.info(
                f'Initialized trestle project successfully in {dir_path}')

            return CmdReturnCodes.SUCCESS.value

        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(
                e, logger, 'Failed to initialize Trestle working directory.')
예제 #15
0
    def _run(self, args: argparse.Namespace):
        try:
            log.set_log_level_from_args(args)
            logger.debug(f'Starting {self.name} command')
            input_path = pathlib.Path(args.input)
            output_path = pathlib.Path(args.output)

            if args.look_up_table:
                lut_table = pathlib.Path(args.look_up_table)
                lookup_table_path = pathlib.Path.cwd() / lut_table
                lut = JinjaCmd.load_LUT(lookup_table_path, args.external_lut_prefix)
                status = JinjaCmd.jinja_ify(
                    pathlib.Path(args.trestle_root),
                    input_path,
                    output_path,
                    args.system_security_plan,
                    args.profile,
                    lut,
                    number_captions=args.number_captions,
                    parameters_formatting=args.param_formatting
                )
            else:
                status = JinjaCmd.jinja_ify(
                    pathlib.Path(args.trestle_root),
                    input_path,
                    output_path,
                    args.system_security_plan,
                    args.profile,
                    number_captions=args.number_captions,
                    parameters_formatting=args.param_formatting
                )
            logger.debug(f'Done {self.name} command')
            return status

        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(e, logger, 'Error while generating markdown via Jinja template')
예제 #16
0
    def _run(self, args: argparse.Namespace) -> int:
        try:
            logger.debug('Entering trestle task.')
            log.set_log_level_from_args(args)
            # Initial logic for conflicting args
            if args.task and args.list:
                raise TrestleIncorrectArgsError(
                    'Task name or -l can be provided not both.')

            if not args.task and not args.list:
                raise TrestleIncorrectArgsError(
                    'Either a trestle task or "-l/--list" shoudl be passed as input arguments.'
                )

            # Ensure trestle directory (must be true)
            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(
                    args.trestle_root):
                raise TrestleError(
                    f'Given directory: {trestle_root} is not a trestle project.'
                )

            config_path = trestle_root / const.TRESTLE_CONFIG_DIR / const.TRESTLE_CONFIG_FILE

            if args.config:
                config_path = pathlib.Path(args.config)
            if not config_path.exists():
                raise TrestleError(
                    f'Config file at {config_path} does not exist.')

            # permit ${name} in config definitions
            global_config = configparser.ConfigParser(
                interpolation=configparser.ExtendedInterpolation())
            global_config.read_file(
                config_path.open('r', encoding=const.FILE_ENCODING))
            # run setup
            task_index = self._build_task_index()

            # Clean to run
            if args.list:
                self._list_tasks(task_index)
                return CmdReturnCodes.SUCCESS.value
            # run the task
            if args.task not in task_index.keys():
                raise TrestleIncorrectArgsError(
                    f'Unknown trestle task: {args.task}')

            logger.debug(f'Loading task: {args.task}')
            section_label = 'task.' + args.task
            config_section: Optional[configparser.SectionProxy] = None
            if section_label in global_config.sections():
                config_section = global_config[section_label]
            else:
                logger.warning(
                    f'Config file was not configured with the appropriate section for the task: "[{section_label}]"'
                )

            task = task_index[args.task](config_section)
            if args.info:
                task.print_info()
                return CmdReturnCodes.SUCCESS.value

            simulate_result = task.simulate()
            if not (simulate_result == TaskOutcome.SIM_SUCCESS):
                raise TrestleError(
                    f'Task {args.task} reported a {simulate_result}')

            actual_result = task.execute()
            if not (actual_result == TaskOutcome.SUCCESS):
                raise TrestleError(
                    f'Task {args.task} reported a {actual_result}')

            logger.info(f'Task: {args.task} executed successfully.')
            return CmdReturnCodes.SUCCESS.value

        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(
                e, logger, 'Error while executing Trestle task')
예제 #17
0
    def _run(self, args: argparse.Namespace) -> int:
        try:
            log.set_log_level_from_args(args)
            trestle_root = args.trestle_root
            if not file_utils.is_directory_name_allowed(args.output):
                raise TrestleError(
                    f'{args.output} is not an allowed directory name')

            profile_path = trestle_root / f'profiles/{args.profile}/profile.json'

            yaml_header: dict = {}
            if args.yaml_header:
                try:
                    logging.debug(
                        f'Loading yaml header file {args.yaml_header}')
                    yaml = YAML()
                    yaml_header = yaml.load(
                        pathlib.Path(args.yaml_header).open('r'))
                except YAMLError as e:
                    raise TrestleError(
                        f'YAML error loading yaml header for ssp generation: {e}'
                    )

            markdown_path = trestle_root / args.output

            profile_resolver = ProfileResolver()

            resolved_catalog = profile_resolver.get_resolved_profile_catalog(
                trestle_root, profile_path)
            catalog_interface = CatalogInterface(resolved_catalog)

            sections_dict: Dict[str, str] = {}
            if args.sections:
                sections_dict = sections_to_dict(args.sections)
                if 'statement' in sections_dict:
                    raise TrestleError(
                        'Statement is not allowed as a section name.')
                # add any existing sections from the controls but only have short names
                control_section_short_names = catalog_interface.get_sections()
                for short_name in control_section_short_names:
                    if short_name not in sections_dict:
                        sections_dict[short_name] = short_name
                logger.debug(f'ssp sections dict: {sections_dict}')

            catalog_interface.write_catalog_as_markdown(
                md_path=markdown_path,
                yaml_header=yaml_header,
                sections_dict=sections_dict,
                prompt_responses=True,
                additional_content=False,
                profile=None,
                overwrite_header_values=args.overwrite_header_values,
                set_parameters=False,
                required_sections=None,
                allowed_sections=args.allowed_sections)

            return CmdReturnCodes.SUCCESS.value

        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(
                e, logger, 'Error while writing markdown from catalog')
예제 #18
0
    def _run(self, args: argparse.Namespace) -> int:
        try:
            log.set_log_level_from_args(args)
            trestle_root = pathlib.Path(args.trestle_root)

            md_path = trestle_root / args.markdown

            # the original, reference ssp name defaults to same as output if name not specified
            # thus in cyclic editing you are reading and writing same json ssp
            orig_ssp_name = args.output
            if args.name:
                orig_ssp_name = args.name
            new_ssp_name = args.output
            # if orig ssp exists - need to load it rather than instantiate new one
            orig_ssp_path = ModelUtils.path_for_top_level_model(
                trestle_root, orig_ssp_name, ossp.SystemSecurityPlan,
                FileContentType.JSON)

            # if output ssp already exists, load it to see if new one is different
            existing_ssp: Optional[ossp.SystemSecurityPlan] = None
            new_ssp_path = ModelUtils.path_for_top_level_model(
                trestle_root, new_ssp_name, ossp.SystemSecurityPlan,
                FileContentType.JSON)
            if new_ssp_path.exists():
                _, _, existing_ssp = ModelUtils.load_distributed(
                    new_ssp_path, trestle_root)

            ssp: ossp.SystemSecurityPlan
            comp_dict: Dict[str, ossp.SystemComponent] = {}

            # need to load imp_reqs from markdown but need component first
            if orig_ssp_path.exists():
                # load the existing json ssp
                _, _, ssp = ModelUtils.load_distributed(
                    orig_ssp_path, trestle_root)
                for component in ssp.system_implementation.components:
                    comp_dict[component.title] = component
                # read the new imp reqs from markdown and have them reference existing components
                imp_reqs = CatalogInterface.read_catalog_imp_reqs(
                    md_path, comp_dict)
                self._merge_imp_reqs(ssp, imp_reqs)
            else:
                # create a sample ssp to hold all the parts
                ssp = gens.generate_sample_model(ossp.SystemSecurityPlan)
                # load the imp_reqs from markdown and create components as needed, referenced by ### headers
                imp_reqs = CatalogInterface.read_catalog_imp_reqs(
                    md_path, comp_dict)

                # create system implementation
                system_imp: ossp.SystemImplementation = gens.generate_sample_model(
                    ossp.SystemImplementation)
                ssp.system_implementation = system_imp

                # create a control implementation to hold the implementated requirements
                control_imp: ossp.ControlImplementation = gens.generate_sample_model(
                    ossp.ControlImplementation)
                control_imp.implemented_requirements = imp_reqs
                control_imp.description = const.SSP_SYSTEM_CONTROL_IMPLEMENTATION_TEXT

                # insert the parts into the ssp
                ssp.control_implementation = control_imp
                ssp.system_implementation = system_imp

                # we don't have access to the original profile so we don't know the href
                import_profile: ossp.ImportProfile = gens.generate_sample_model(
                    ossp.ImportProfile)
                import_profile.href = 'REPLACE_ME'
                ssp.import_profile = import_profile

            # now that we know the complete list of needed components, add them to the sys_imp
            # TODO if the ssp already existed then components may need to be removed if not ref'd by imp_reqs
            component_list: List[ossp.SystemComponent] = []
            for comp in comp_dict.values():
                component_list.append(comp)
            if ssp.system_implementation.components:
                # reconstruct list with same order as existing, but add/remove components as needed
                new_list: List[ossp.SystemComponent] = []
                for comp in ssp.system_implementation.components:
                    if comp in component_list:
                        new_list.append(comp)
                for comp in component_list:
                    if comp not in new_list:
                        new_list.append(comp)
                ssp.system_implementation.components = new_list
            elif component_list:
                ssp.system_implementation.components = component_list
            self._generate_roles_in_metadata(ssp)

            if args.version:
                ssp.metadata.version = com.Version(__root__=args.version)

            if existing_ssp == ssp:
                logger.info(
                    'No changes to assembled ssp so ssp not written out.')
                return CmdReturnCodes.SUCCESS.value

            if args.regenerate:
                ssp, _, _ = ModelUtils.regenerate_uuids(ssp)
            ModelUtils.update_last_modified(ssp)

            # write out the ssp as json
            ModelUtils.save_top_level_model(ssp, trestle_root, new_ssp_name,
                                            FileContentType.JSON)

            return CmdReturnCodes.SUCCESS.value

        except Exception as e:  # pragma: no cover
            return handle_generic_command_exception(
                e, logger, 'Error while assembling SSP')
예제 #19
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')