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
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
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
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
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')
def __init__(self, root_dir: pathlib.Path) -> None: """Initialize trestle repository object.""" if not file_utils.is_valid_project_root(root_dir): raise TrestleError(f'Provided root directory {root_dir} is not a valid Trestle root directory.') self._root_dir = root_dir
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')
def test_is_valid_project_root(tmp_path: pathlib.Path) -> None: """Test is_valid_project_root method.""" assert file_utils.is_valid_project_root(tmp_path) is False test_utils.ensure_trestle_config_dir(tmp_path) assert file_utils.is_valid_project_root(tmp_path) is True