PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/ini_manage.py |
""" Edit ini files :maintainer: <akilesh1597@gmail.com> :maturity: new :depends: re :platform: all (for example /etc/sysctl.conf) """ import logging import os import re import salt.utils.data import salt.utils.files import salt.utils.json import salt.utils.stringutils from salt.exceptions import CommandExecutionError from salt.utils.odict import OrderedDict log = logging.getLogger(__name__) __virtualname__ = "ini" def __virtual__(): """ Rename to ini """ return __virtualname__ INI_REGX = re.compile(r"^\s*\[(.+?)\]\s*$", flags=re.M) COM_REGX = re.compile(r"^\s*(#|;)\s*(.*)") INDENTED_REGX = re.compile(r"(\s+)(.*)") def set_option(file_name, sections=None, separator="=", encoding=None): """ Edit an ini file, replacing one or more sections. Returns a dictionary containing the changes made. Args: file_name (str): The full path to the ini file. sections (dict): A dictionary representing the sections to be edited in the ini file. The keys are the section names and the values are a dictionary containing the options. If the ini file does not contain sections the keys and values represent the options. The default is ``None``. separator (str): The character used to separate keys and values. Standard ini files use the "=" character. The default is ``=``. .. versionadded:: 2016.11.0 encoding (str): A string value representing encoding of the target ini file. If ``None`` is passed, it uses the system default which is likely ``utf-8``. Default is ``None`` .. versionadded:: 3006.6 Returns: dict: A dictionary representing the changes made to the ini file API Example: .. code-block:: python import salt.client with salt.client.get_local_client() as sc: sc.cmd( 'target', 'ini.set_option', ['path_to_ini_file', '{"section_to_change": {"key": "value"}}'] ) CLI Example: .. code-block:: bash salt '*' ini.set_option /path/to/ini '{section_foo: {key: value}}' """ sections = sections or {} inifile = _Ini.get_ini_file(file_name, separator=separator, encoding=encoding) changes = inifile.update(sections) inifile.flush() return changes def get_option(file_name, section, option, separator="=", encoding=None): """ Get value of a key from a section in an ini file. Returns ``None`` if no matching key was found. Args: file_name (str): The full path to the ini file. section (str): A string value representing the section of the ini that the option is in. If the option is not in a section, leave this empty. option (str): A string value representing the option to search for. separator (str): The character used to separate keys and values. Standard ini files use the "=" character. The default is ``=``. .. versionadded:: 2016.11.0 encoding (str): A string value representing encoding of the target ini file. If ``None`` is passed, it uses the system default which is likely ``utf-8``. Default is ``None`` .. versionadded:: 3006.6 Returns: str: The value as defined in the ini file, or ``None`` if empty or not found API Example: .. code-block:: python import salt.client with salt.client.get_local_client() as sc: sc.cmd('target', 'ini.get_option', [path_to_ini_file, section_name, option]) CLI Example: .. code-block:: bash salt '*' ini.get_option /path/to/ini section_name option_name """ inifile = _Ini.get_ini_file(file_name, separator=separator, encoding=encoding) if section: try: return inifile.get(section, {}).get(option, None) except AttributeError: return None else: return inifile.get(option, None) def remove_option(file_name, section, option, separator="=", encoding=None): """ Remove a key/value pair from a section in an ini file. Returns the value of the removed key, or ``None`` if nothing was removed. Args: file_name (str): The full path to the ini file. section (str): A string value representing the section of the ini that the option is in. If the option is not in a section, leave this empty. option (str): A string value representing the option to search for. separator (str): The character used to separate keys and values. Standard ini files use the "=" character. The default is ``=``. .. versionadded:: 2016.11.0 encoding (str): A string value representing encoding of the target ini file. If ``None`` is passed, it uses the system default which is likely ``utf-8``. Default is ``None`` .. versionadded:: 3006.6 Returns: str: A string value representing the option that was removed or ``None`` if nothing was removed API Example: .. code-block:: python import salt sc = salt.client.get_local_client() sc.cmd('target', 'ini.remove_option', [path_to_ini_file, section_name, option]) CLI Example: .. code-block:: bash salt '*' ini.remove_option /path/to/ini section_name option_name """ inifile = _Ini.get_ini_file(file_name, separator=separator, encoding=encoding) if isinstance(inifile.get(section), (dict, OrderedDict)): value = inifile.get(section, {}).pop(option, None) else: value = inifile.pop(option, None) inifile.flush() return value def get_section(file_name, section, separator="=", encoding=None): """ Retrieve a section from an ini file. Returns the section as a dictionary. If the section is not found, an empty dictionary is returned. Args: file_name (str): The full path to the ini file. section (str): A string value representing name of the section to search for. separator (str): The character used to separate keys and values. Standard ini files use the "=" character. The default is ``=``. .. versionadded:: 2016.11.0 encoding (str): A string value representing encoding of the target ini file. If ``None`` is passed, it uses the system default which is likely ``utf-8``. Default is ``None`` .. versionadded:: 3006.6 Returns: dict: A dictionary containing the names and values of all items in the section of the ini file. If the section is not found, an empty dictionary is returned API Example: .. code-block:: python import salt.client with salt.client.get_local_client() as sc: sc.cmd('target', 'ini.get_section', [path_to_ini_file, section_name]) CLI Example: .. code-block:: bash salt '*' ini.get_section /path/to/ini section_name """ inifile = _Ini.get_ini_file(file_name, separator=separator, encoding=encoding) ret = {} for key, value in inifile.get(section, {}).items(): if key[0] != "#": ret.update({key: value}) return ret def remove_section(file_name, section, separator="=", encoding=None): """ Remove a section in an ini file. Returns the removed section as a dictionary, or ``None`` if nothing is removed. Args: file_name (str): The full path to the ini file. section (str): A string value representing the name of the section search for. separator (str): The character used to separate keys and values. Standard ini files use the "=" character. The default is ``=``. .. versionadded:: 2016.11.0 encoding (str): A string value representing encoding of the target ini file. If ``None`` is passed, it uses the system default which is likely ``utf-8``. Default is ``None`` .. versionadded:: 3006.6 Returns: dict: A dictionary containing the names and values of all items in the section that was removed or ``None`` if nothing was removed API Example: .. code-block:: python import salt.client with salt.client.get_local_client() as sc: sc.cmd('target', 'ini.remove_section', [path_to_ini_file, section_name]) CLI Example: .. code-block:: bash salt '*' ini.remove_section /path/to/ini section_name """ inifile = _Ini.get_ini_file(file_name, separator=separator, encoding=encoding) if section in inifile: section = inifile.pop(section) inifile.flush() ret = {} for key, value in section.items(): if key[0] != "#": ret.update({key: value}) return ret def get_ini(file_name, separator="=", encoding=None): """ Retrieve the whole structure from an ini file and return it as a dictionary. Args: file_name (str): The full path to the ini file. separator (str): The character used to separate keys and values. Standard ini files use the "=" character. The default is ``=``. .. versionadded:: 2016.11.0 encoding (str): A string value representing encoding of the target ini file. If ``None`` is passed, it uses the system default which is likely ``utf-8``. Default is ``None`` .. versionadded:: 3006.6 Returns: dict: A dictionary containing the sections along with the values and names contained in each section API Example: .. code-block:: python import salt.client with salt.client.get_local_client() as sc: sc.cmd('target', 'ini.get_ini', [path_to_ini_file]) CLI Example: .. code-block:: bash salt '*' ini.get_ini /path/to/ini """ def ini_odict2dict(odict): """ Transform OrderedDict to regular dict recursively :param odict: OrderedDict :return: regular dict """ ret = {} for key, val in odict.items(): if key[0] != "#": if isinstance(val, (dict, OrderedDict)): ret.update({key: ini_odict2dict(val)}) else: ret.update({key: val}) return ret inifile = _Ini.get_ini_file(file_name, separator=separator, encoding=encoding) return ini_odict2dict(inifile) class _Section(OrderedDict): def __init__(self, name, inicontents="", separator="=", commenter="#"): super().__init__(self) self.name = name self.inicontents = inicontents self.sep = separator self.com = commenter opt_regx_prefix = r"(\s*)(.+?)\s*" opt_regx_suffix = r"\s*(.*)\s*" self.opt_regx_str = r"{}(\{}){}".format( opt_regx_prefix, self.sep, opt_regx_suffix ) self.opt_regx = re.compile(self.opt_regx_str) def refresh(self, inicontents=None): comment_count = 1 unknown_count = 1 curr_indent = "" inicontents = inicontents or self.inicontents inicontents = inicontents.strip(os.linesep) if not inicontents: return for opt in self: self.pop(opt) for opt_str in inicontents.split(os.linesep): # Match comments com_match = COM_REGX.match(opt_str) if com_match: name = f"#comment{comment_count}" self.com = com_match.group(1) comment_count += 1 self.update({name: opt_str}) continue # Add indented lines to the value of the previous entry. indented_match = INDENTED_REGX.match(opt_str) if indented_match: indent = indented_match.group(1).replace("\t", " ") if indent > curr_indent: options = list(self) if options: prev_opt = options[-1] value = self.get(prev_opt) self.update({prev_opt: os.linesep.join((value, opt_str))}) continue # Match normal key+value lines. opt_match = self.opt_regx.match(opt_str) if opt_match: curr_indent, name, self.sep, value = opt_match.groups() curr_indent = curr_indent.replace("\t", " ") self.update({name: value}) continue # Anything remaining is a mystery. name = f"#unknown{unknown_count}" self.update({name: opt_str}) unknown_count += 1 def _uncomment_if_commented(self, opt_key): # should be called only if opt_key is not already present # will uncomment the key if commented and create a place holder # for the key where the correct value can be update later # used to preserve the ordering of comments and commented options # and to make sure options without sectons go above any section options_backup = OrderedDict() comment_index = None for key, value in self.items(): if comment_index is not None: options_backup.update({key: value}) continue if "#comment" not in key: continue opt_match = self.opt_regx.match(value.lstrip("#")) if opt_match and opt_match.group(2) == opt_key: comment_index = key for key in options_backup: self.pop(key) self.pop(comment_index, None) super().update({opt_key: None}) for key, value in options_backup.items(): super().update({key: value}) def update(self, update_dict): changes = {} for key, value in update_dict.items(): # Ensure the value is either a _Section or a string if isinstance(value, (dict, OrderedDict)): sect = _Section( name=key, inicontents="", separator=self.sep, commenter=self.com ) sect.update(value) value = sect value_plain = value.as_dict() else: value = str(value) value_plain = value if key not in self: changes.update({key: {"before": None, "after": value_plain}}) # If it's not a section, it may already exist as a # commented-out key/value pair if not isinstance(value, _Section): self._uncomment_if_commented(key) super().update({key: value}) else: curr_value = self.get(key, None) if isinstance(curr_value, _Section): sub_changes = curr_value.update(value) if sub_changes: changes.update({key: sub_changes}) else: if curr_value != value: changes.update( {key: {"before": curr_value, "after": value_plain}} ) super().update({key: value}) return changes def gen_ini(self): yield "{0}[{1}]{0}".format(os.linesep, self.name) sections_dict = OrderedDict() for name, value in self.items(): # Handle Comment Lines if COM_REGX.match(name): yield f"{value}{os.linesep}" # Handle Sections elif isinstance(value, _Section): sections_dict.update({name: value}) # Key / Value pairs # Adds spaces between the separator else: yield "{}{}{}{}".format( name, f" {self.sep} " if self.sep != " " else self.sep, value, os.linesep, ) for name, value in sections_dict.items(): yield from value.gen_ini() def as_ini(self): return "".join(self.gen_ini()) def as_dict(self): return dict(self) def dump(self): print(str(self)) def __repr__(self, _repr_running=None): _repr_running = _repr_running or {} try: super_repr = super().__repr__(_repr_running) except TypeError: super_repr = super().__repr__() return os.linesep.join((super_repr, salt.utils.json.dumps(self, indent=4))) def __str__(self): return salt.utils.json.dumps(self, indent=4) def __eq__(self, item): return isinstance(item, self.__class__) and self.name == item.name def __ne__(self, item): return not (isinstance(item, self.__class__) and self.name == item.name) class _Ini(_Section): def __init__( self, name, inicontents="", separator="=", commenter="#", encoding=None ): super().__init__( self, inicontents=inicontents, separator=separator, commenter=commenter ) self.name = name if encoding is None: encoding = __salt_system_encoding__ self.encoding = encoding def refresh(self, inicontents=None): if inicontents is None: if not os.path.exists(self.name): log.trace("File %s does not exist and will be created", self.name) return try: # We need to set decode on open and not try to do it later with # stringutils with salt.utils.files.fopen( self.name, "r", encoding=self.encoding ) as rfh: inicontents = rfh.read() inicontents = os.linesep.join(inicontents.splitlines()) except OSError as exc: if __opts__["test"] is False: raise CommandExecutionError( f"Unable to open file '{self.name}'. Exception: {exc}" ) if not inicontents: return # Remove anything left behind from a previous run. self.clear() inicontents = INI_REGX.split(inicontents) inicontents.reverse() # Pop anything defined outside of a section (ie. at the top of # the ini file). super().refresh(inicontents.pop()) for section_name, sect_ini in self._gen_tuples(inicontents): try: sect_obj = _Section(section_name, sect_ini, separator=self.sep) sect_obj.refresh() self.update({sect_obj.name: sect_obj}) except StopIteration: pass def flush(self): try: # We need to encode in the fopen command instead of using # data.encode in the writelines command. Using data.encode will # cause a BoM to be placed on every line of the file with salt.utils.files.fopen( self.name, "w", encoding=self.encoding ) as outfile: ini_gen = self.gen_ini() next(ini_gen) ini_gen_list = list(ini_gen) # Avoid writing an initial line separator. if ini_gen_list: ini_gen_list[0] = ini_gen_list[0].lstrip(os.linesep) outfile.writelines(ini_gen_list) except OSError as exc: raise CommandExecutionError( f"Unable to write file '{self.name}'. Exception: {exc}" ) @staticmethod def get_ini_file(file_name, separator="=", encoding=None): inifile = _Ini(file_name, separator=separator, encoding=encoding) inifile.refresh() return inifile @staticmethod def _gen_tuples(list_object): while True: try: key = list_object.pop() value = list_object.pop() except IndexError: return else: yield key, value