def rc_load_ini(fpath, emheading=None): """Load INI-formatted data from a (resource) file. :param fpath: pathlib.Path, path to a source file :param emheading: str, heading to be added to the exception's description :return: dict, configparser.ConfigParser.read_dict() compatible data. """ assert isinstance(fpath, Path) and fpath.is_absolute(), ( f'Argument: fpath: Absolute pathlib.Path is required: {fpath}') cfg_parser = cfp.ConfigParser() try: with fpath.open() as fp: cfg_parser.read_file(fp) except FileNotFoundError: err_msg = f'Load: {fpath}' err_msg = f'{emheading}: {err_msg}' if emheading else err_msg raise cr_exc.RCNotFoundError(err_msg) except (OSError, RuntimeError) as e: err_msg = f'Load: {fpath}: {type(e).__name__}: {e}' err_msg = f'{emheading}: {err_msg}' if emheading else err_msg raise cr_exc.RCError(err_msg) except cfp.Error as e: err_msg = f'Load: {fpath}: {type(e).__name__}: {e}' err_msg = f'{emheading}: {err_msg}' if emheading else err_msg raise cr_exc.RCInvalidError(err_msg) else: return {k: dict(v) for k, v in cfg_parser.items()}
def rc_load_json(fpath, emheading=None): """Load JSON-formatted data from a (resource) file. :param fpath: pathlib.Path, path to a source file. :param emheading: str, heading to be added to the exception's description :return: json obj, JSON-formatted data """ assert isinstance(fpath, Path) and fpath.is_absolute(), ( f'Argument: fpath: Absolute pathlib.Path is required: {fpath}') try: with fpath.open() as fp: j_body = json.load(fp, strict=False) except FileNotFoundError: err_msg = f'Load: {fpath}' err_msg = f'{emheading}: {err_msg}' if emheading else err_msg raise cr_exc.RCNotFoundError(err_msg) except (OSError, RuntimeError) as e: err_msg = f'Load: {fpath}: {type(e).__name__}: {e}' err_msg = f'{emheading}: {err_msg}' if emheading else err_msg raise cr_exc.RCError(err_msg) except cr_exc.JSON_ERRORS as e: err_msg = f'Load: {fpath}: {type(e).__name__}: {e}' err_msg = f'{emheading}: {err_msg}' if emheading else err_msg raise cr_exc.RCInvalidError(err_msg) else: return j_body
def load_dcos_conf_template(fpath: Path): """Load the DC/OS aggregated configuration template from disk. :param fpath: Path, path to template """ try: with fpath.open() as f: return template.parse_str(f.read()) except (OSError, RuntimeError) as e: raise cr_exc.RCError(f'DC/OS aggregated config: Template: Load:' f' {fpath}: {type(e).__name__}: {e}') from e
def save(self): """Save package manifest to a file within the active packages index.""" fpath = self.pkgactive_dpath.joinpath(f'{self.pkg_id}.json') try: with fpath.open(mode='w') as fp: json.dump(self.body, fp) except (OSError, RuntimeError) as e: err_msg = f'Package manifest: Save: {type(e).__name__}: {e}' raise cr_exc.RCError(err_msg) LOG.debug(f'Package manifest: Save: {fpath}')
def rc_load_ini(fpath, emheading=None, render=False, context=None): """Load INI-formatted data from a resource file. Content of a resource file can be pre-processed by Jinja2 rendering engine before being passed to INI-parser. :param fpath: pathlib.Path, path to a source file :param emheading: str, heading to be added to the exception's description :param render: bool, perform template rendering :param context: ResourceContext, rendering context data object :return: dict, configparser.ConfigParser.read_dict() compatible data. """ assert isinstance(fpath, Path) and fpath.is_absolute(), ( f'Argument: fpath: Absolute pathlib.Path is required: {fpath}' ) if context is None: context_items = {} else: assert isinstance(context, ResourceContext), ( f'Argument: context:' f' Got {type(context).__name__} instead of ResourceContext' ) context_items = context.get_items() cfg_parser = cfp.ConfigParser() try: if render is True: jj2_env = jj2.Environment( loader=jj2.FileSystemLoader(str(fpath.parent)) ) jj2_tmpl = jj2_env.get_template(str(fpath.name)) ini_str = jj2_tmpl.render(**context_items) LOG.debug(f'rc_load_ini(): ini_str: {ini_str}') cfg_parser.read_string(ini_str, source=str(fpath)) else: with fpath.open() as fp: cfg_parser.read_file(fp) except (FileNotFoundError, jj2.TemplateNotFound) as e: err_msg = f'Load: {fpath}' err_msg = f'{emheading}: {err_msg}' if emheading else err_msg raise cr_exc.RCNotFoundError(err_msg) from e except (OSError, RuntimeError) as e: err_msg = f'Load: {fpath}: {type(e).__name__}: {e}' err_msg = f'{emheading}: {err_msg}' if emheading else err_msg raise cr_exc.RCError(err_msg) from e except (jj2.TemplateError, cfp.Error) as e: err_msg = f'Load: {fpath}: {type(e).__name__}: {e}' err_msg = f'{emheading}: {err_msg}' if emheading else err_msg raise cr_exc.RCInvalidError(err_msg) from e else: return {k: dict(v) for k, v in cfg_parser.items()}
def _get_inst_state(self): """""" istate_fpath = self.inst_storage.state_dpath.joinpath( cm_const.DCOS_INST_STATE_FNAME_DFT) try: inst_state = InstallationState.load(istate_fpath) LOG.debug(f'{self.msg_src}: DC/OS installation state descriptor:' f' Load: {inst_state}') except cr_exc.RCNotFoundError: inst_state = None if inst_state is None: msg_base = (f'{self.msg_src}:' f' DC/OS installation state descriptor: Create') try: inst_state = InstallationState(self.inst_storage.istor_nodes) LOG.debug(f'{msg_base}: {inst_state}') except AssertionError as e: raise cr_exc.RCError(f'{msg_base}: {type(e).__name__}: {e}') except cr_exc.RCError as e: raise cr_exc.RCError(f'{msg_base}: {e}') return inst_state
def save(self): """Save package manifest to a file within the active packages index.""" fpath = getattr( self._istor_nodes, ISTOR_NODE.PKGACTIVE).joinpath(f'{self._pkg_id.pkg_id}.json') try: with fpath.open(mode='w') as fp: json.dump(self.body, fp) except (OSError, RuntimeError) as e: err_msg = f'Package manifest: Save: {type(e).__name__}: {e}' raise cr_exc.RCError(err_msg) from e LOG.debug(f'Package manifest: Save: {fpath}')
def rc_load_json(fpath, emheading=None, render=False, context=None): """Load JSON-formatted data from a resource file. Content of a resource file can be pre-processed by Jinja2 rendering engine before being passed to JSON-parser. :param fpath: pathlib.Path, path to a source file. :param emheading: str, heading to be added to the exception's description :param render: bool, perform template rendering :param context: ResourceContext, rendering context data object :return: json obj, JSON-formatted data """ assert isinstance(fpath, Path) and fpath.is_absolute(), ( f'Argument: fpath: Absolute pathlib.Path is required: {fpath}' ) if context is None: context_items = {} else: assert isinstance(context, ResourceContext), ( f'Argument: context:' f' Got {type(context).__name__} instead of ResourceContext' ) context_items = context.get_items(json_ready=True) try: if render is True: jj2_env = jj2.Environment( loader=jj2.FileSystemLoader(str(fpath.parent)) ) jj2_tmpl = jj2_env.get_template(str(fpath.name)) json_str = jj2_tmpl.render(**context_items) LOG.debug(f'rc_load_json(): json_str: {json_str}') j_body = json.loads(json_str, strict=False) else: with fpath.open() as fp: j_body = json.load(fp, strict=False) except (FileNotFoundError, jj2.TemplateNotFound) as e: err_msg = f'Load: {fpath}' err_msg = f'{emheading}: {err_msg}' if emheading else err_msg raise cr_exc.RCNotFoundError(err_msg) from e except (OSError, RuntimeError) as e: err_msg = f'Load: {fpath}: {type(e).__name__}: {e}' err_msg = f'{emheading}: {err_msg}' if emheading else err_msg raise cr_exc.RCError(err_msg) from e except (jj2.TemplateError,) + cr_exc.JSON_ERRORS as e: err_msg = f'Load: {fpath}: {type(e).__name__}: {e}' err_msg = f'{emheading}: {err_msg}' if emheading else err_msg raise cr_exc.RCInvalidError(err_msg) from e else: return j_body
def delete_manifest(self, mheading: str=None, dpath: Path=None): """Delete package's manifest from filesystem. :param mheading: str, descriptive heading to be added to error/log messages :param dpath: Path, absolute path to the host directory where to delete from """ mhd_base = f'{self.msg_src}: {self.id.pkg_name}' mheading = f'{mheading}: {mhd_base}' if mheading else mhd_base try: self.manifest.delete(dpath) except (OSError, RuntimeError, cr_exc.RCError) as e: err_msg = f'{mheading}: Deregister package: {self.id}: {e}' raise cr_exc.RCError(err_msg) from e
def load_dcos_conf_template(fpath): """Load the DC/OS aggregated configuration template from disk. :param fpath: pathlib.Path, path to template """ p_key = re.compile(r' *- path: (?P<g1>.*)') c_key = re.compile(r' *content: [|].*') h_key = re.compile(r' *#.*$') try: with fpath.open() as fp: aggregator = {'package': []} path = '' content = [] for line in fp: pk_match = p_key.match(line) ck_match = c_key.match(line) hk_match = h_key.match(line) if pk_match: if path: item = {'path': path, 'content': ''.join(content)} aggregator['package'].append(item) path = pk_match.group('g1') content = [] else: path = pk_match.group('g1') elif ck_match: continue elif hk_match: continue else: if not path: continue else: content.append(line.strip(' ')) item = {'path': path, 'content': ''.join(content)} aggregator['package'].append(item) except (OSError, RuntimeError) as e: raise cr_exc.RCError(f'DC/OS aggregated config: Template: Load:' f' {fpath}: {type(e).__name__}: {e}') from e return aggregator
def save_manifest(self, mheading: str=None, dpath: Path=None): """Save package's manifest to filesystem. :param mheading: str, descriptive heading to be added to error/log messages :param dpath: Path, absolute path to the host directory where to save to """ mhd_base = f'{self.msg_src}: {self.id.pkg_name}' mheading = f'{mheading}: {mhd_base}' if mheading else mhd_base try: dpath.mkdir(parents=True, exist_ok=True) self.manifest.save(dpath) except (OSError, RuntimeError, cr_exc.RCError) as e: err_msg = f'{mheading}: Register package: {self.id}: {e}' raise cr_exc.RCError(err_msg) from e
def load_dcos_conf_template(fpath: Path): """Load the DC/OS aggregated configuration template from disk. :param fpath: Path, path to template """ # TODO: Functionality implemented in this method needs to be reused # in other application parts (e.g. CmdConfigUpgrade) and so, it # has been arranged as a standalone function # load_dcos_conf_template(). # Thus the CmdConfigSetup is to be moved to use that standalone # function instead of this method to avoid massive code # duplication. try: with fpath.open() as f: return template.parse_str(f.read()) except (OSError, RuntimeError) as e: raise cr_exc.RCError(f'DC/OS aggregated config: Template: Load:' f' {fpath}: {type(e).__name__}: {e}') from e
def delete(self, dpath: Path = None): """Delete package manifest file. :param dpath: Path, absolute path to the host directory where to delete from """ if dpath is None: dpath = getattr(self._istor_nodes, ISTOR_NODE.PKGACTIVE) fpath = dpath.joinpath(f'{self._pkg_id.pkg_id}.json') try: fpath.unlink() except (OSError, RuntimeError) as e: err_msg = f'{self.msg_src}: Delete: {type(e).__name__}: {e}' raise cr_exc.RCError(err_msg) from e LOG.debug(f'{self.msg_src}: Delete: {fpath}')
def wrapper(fpath: Path, emheading: str = None, render: bool = False, context: ResourceContext = None) -> Any: try: content = func(fpath, emheading, render, context) except (FileNotFoundError, jj2.TemplateNotFound) as e: err_msg = f'Load: {fpath}' err_msg = f'{emheading}: {err_msg}' if emheading else err_msg raise cr_exc.RCNotFoundError(err_msg) from e except (OSError, RuntimeError) as e: err_msg = f'Load: {fpath}: {type(e).__name__}: {e}' err_msg = f'{emheading}: {err_msg}' if emheading else err_msg raise cr_exc.RCError(err_msg) from e except (jj2.TemplateError, yaml.YAMLError) + cr_exc.JSON_ERRORS as e: err_msg = f'Load: {fpath}: {type(e).__name__}: {e}' err_msg = f'{emheading}: {err_msg}' if emheading else err_msg raise cr_exc.RCInvalidError(err_msg) from e else: return content
def save(self, dpath: Path = None): """Save package manifest to a file. :param dpath: Path, absolute path to the host directory where to save to """ if dpath is None: # Manifest host directory defaults to the active packages index dpath = getattr(self._istor_nodes, ISTOR_NODE.PKGACTIVE) fpath = dpath.joinpath(f'{self._pkg_id.pkg_id}.json') try: with fpath.open(mode='w') as fp: json.dump(self.body, fp) except (OSError, RuntimeError) as e: err_msg = f'{self.msg_src}: Save: {type(e).__name__}: {e}' raise cr_exc.RCError(err_msg) from e LOG.debug(f'{self.msg_src}: Save: {fpath}')
def _handle_teardown(self): """Teardown the currently installed DC/OS.""" mheading = f'{self.msg_src}: Execute' pkg_manifests = ( self.config.inst_storage.get_pkgactive(PackageManifest.load) ) packages_bulk = { m.pkg_id.pkg_name: Package(manifest=m) for m in pkg_manifests } iroot_dpath = self.config.inst_storage.root_dpath itmp_dpath = self.config.inst_storage.tmp_dpath pkgactive_old_dpath = itmp_dpath.joinpath( f'{storage.DCOS_PKGACTIVE_DPATH_DFT}.old' ) sh_conf_dname = storage.DCOS_INST_CFG_DPATH_DFT sh_exec_dname = storage.DCOS_INST_BIN_DPATH_DFT sh_lib__dname = storage.DCOS_INST_LIB_DPATH_DFT # Teardown installed packages for package in cr_utl.pkg_sort_by_deps(packages_bulk): package.handle_svc_wipe(mheading) package.handle_uninst_extras(mheading) package.save_manifest(mheading, pkgactive_old_dpath) package.delete_manifest(mheading) # Remove/preserve shared directories for dname in sh_conf_dname, sh_exec_dname, sh_lib__dname: active_dpath = iroot_dpath.joinpath(dname) preserve_dpath = itmp_dpath.joinpath(f'{dname}.old') try: cm_utl.rmdir(str(preserve_dpath), recursive=True) active_dpath.rename(preserve_dpath) except (OSError, RuntimeError) as e: err_msg = (f'{mheading}: Preserve shared directory:' f' {active_dpath}: {type(e).__name__}: {e}') raise cr_exc.RCError(err_msg) from e LOG.debug(f'{mheading}: Preserve shared directory: {active_dpath}:' f' {preserve_dpath}')
def handle_vardata_wipe(self, mheading: str=None): """Execute steps on wiping of package's variable data. :param mheading: str, descriptive heading to be added to error/log messages """ mhd_base = f'{self.msg_src}: {self.id.pkg_name}' mheading = f'{mheading}: {mhd_base}' if mheading else mhd_base work_dpath = getattr(self.manifest.istor_nodes, ISTOR_NODE.WORK) run_dpath = getattr(self.manifest.istor_nodes, ISTOR_NODE.RUN) for host_dpath in work_dpath, run_dpath: dpath = host_dpath.joinpath(self.id.pkg_name) try: cm_utl.rmdir(str(dpath), recursive=True) dpath.mkdir(parents=True, exist_ok=True) LOG.debug(f'{mheading}: Wipe variable data: {dpath}: OK') except (OSError, RuntimeError) as e: err_msg = (f'{mheading}: Wipe variable data: {dpath}:' f' {type(e).__name__}: {e}') raise cr_exc.RCError(err_msg) from e