­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ """ Manage ini files ================ :maintainer: :maturity: new :depends: re :platform: all """ from collections import OrderedDict __virtualname__ = "ini" def __virtual__(): """ Only load if the ini module is available """ return __virtualname__ if "ini.set_option" in __salt__ else False def options_present( name, sections=None, separator="=", strict=False, encoding=None, no_spaces=False ): """ Set or create a key/value pair in an ``ini`` file. Options present in the ini file and not specified in the sections dict will be untouched, unless the ``strict: True`` flag is used. Sections that do not exist will be created. Args: name (str): The path to the ini file sections (dict): A dictionary of sections and key/value pairs that will be used to update the ini file. Other sections and key/value pairs in the ini file will be untouched unless ``strict: True`` is passed. separator (str): The character used to separate keys and values. Standard ini files use the "=" character. The default is ``=``. strict (bool): A boolean value that specifies that the ``sections`` dictionary contains all settings in the ini file. ``True`` will create an ini file with only the values specified in ``sections``. ``False`` will append or update values in an existing ini file and leave the rest untouched. 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.10 no_spaces (bool): A bool value that specifies that the key/value separator will be wrapped with spaces. This parameter was added to have the ability to not wrap the separator with spaces. Default is ``False``, which maintains backwards compatibility. .. warning:: This will affect all key/value pairs in the ini file, not just the specific value being set. .. versionadded:: 3006.10 Returns: dict: A dictionary containing list of changes made Example: .. code-block:: yaml /home/saltminion/api-paste.ini: ini.options_present: - separator: '=' - strict: True - sections: test: testkey: 'testval' secondoption: 'secondvalue' test1: testkey1: 'testval121' """ ret = { "name": name, "changes": {}, "result": True, "comment": "No anomaly detected", } if __opts__["test"]: ret["comment"] = "" # pylint: disable=too-many-nested-blocks try: changes = {} if sections: options = {} for sname, sbody in sections.items(): if not isinstance(sbody, (dict, OrderedDict)): options.update({sname: sbody}) cur_ini = __salt__["ini.get_ini"]( file_name=name, separator=separator, encoding=encoding ) original_top_level_opts = {} original_sections = {} for key, val in cur_ini.items(): if isinstance(val, (dict, OrderedDict)): original_sections.update({key: val}) else: original_top_level_opts.update({key: val}) if __opts__["test"]: for option in options: if option in original_top_level_opts: if str(original_top_level_opts[option]) == str(options[option]): ret["comment"] += f"Unchanged key {option}.\n" else: ret["comment"] += f"Changed key {option}.\n" ret["result"] = None else: ret["comment"] += f"Changed key {option}.\n" ret["result"] = None else: options_updated = __salt__["ini.set_option"]( file_name=name, sections=options, separator=separator, encoding=encoding, no_spaces=no_spaces, ) changes.update(options_updated) if strict: for opt_to_remove in set(original_top_level_opts).difference(options): if __opts__["test"]: ret["comment"] += f"Removed key {opt_to_remove}.\n" ret["result"] = None else: __salt__["ini.remove_option"]( file_name=name, section=None, option=opt_to_remove, separator=separator, encoding=encoding, ) changes.update( { opt_to_remove: { "before": original_top_level_opts[opt_to_remove], "after": None, } } ) declared_sections = { sname for sname, sbody in sections.items() if isinstance(sbody, (dict, OrderedDict)) } for section_to_remove in set(original_sections).difference( declared_sections ): if __opts__["test"]: ret["comment"] += f"Removed section {section_to_remove}.\n" ret["result"] = None else: __salt__["ini.remove_section"]( file_name=name, section=section_to_remove, separator=separator, encoding=encoding, ) changes.update( { section_to_remove: { "before": dict( original_sections[section_to_remove] ), "after": None, } } ) for section_name, section_body in [ (sname, sbody) for sname, sbody in sections.items() if isinstance(sbody, (dict, OrderedDict)) ]: section_descr = " in section " + section_name if section_name else "" changes[section_name] = {} if strict: original = cur_ini.get(section_name, {}) for key_to_remove in set(original.keys()).difference( section_body.keys() ): orig_value = original_sections.get(section_name, {}).get( key_to_remove, "#-#-" ) if __opts__["test"]: ret["comment"] += "Deleted key {}{}.\n".format( key_to_remove, section_descr ) ret["result"] = None else: __salt__["ini.remove_option"]( file_name=name, section=section_name, option=key_to_remove, separator=separator, encoding=encoding, ) changes[section_name].update({key_to_remove: ""}) changes[section_name].update( {key_to_remove: {"before": orig_value, "after": None}} ) if __opts__["test"]: for option in section_body: if str(section_body[option]) == str( original_sections.get(section_name, {}).get(option, "#-#-") ): ret["comment"] += "Unchanged key {}{}.\n".format( option, section_descr ) else: ret["comment"] += "Changed key {}{}.\n".format( option, section_descr ) ret["result"] = None else: options_updated = __salt__["ini.set_option"]( file_name=name, sections={section_name: section_body}, separator=separator, encoding=encoding, no_spaces=no_spaces, ) if options_updated: changes[section_name].update(options_updated[section_name]) if not changes[section_name]: del changes[section_name] else: if not __opts__["test"]: changes = __salt__["ini.set_option"]( file_name=name, sections=sections, separator=separator, encoding=encoding, no_spaces=no_spaces, ) except (OSError, KeyError) as err: ret["comment"] = f"{err}" ret["result"] = False return ret if "error" in changes: ret["result"] = False ret["comment"] = "Errors encountered. {}".format(changes["error"]) ret["changes"] = {} else: for ciname, body in changes.items(): if body: ret["comment"] = "Changes take effect" ret["changes"].update({ciname: changes[ciname]}) return ret def options_absent(name, sections=None, separator="=", encoding=None): """ Remove a key/value pair from an ini file. Key/value pairs present in the ini file and not specified in sections dict will be untouched. Args: name (str): The path to the ini file sections (dict): A dictionary of sections and key/value pairs that will be removed from the ini file. Other key/value pairs in the ini file will be untouched. separator (str): The character used to separate keys and values. Standard ini files use the "=" character. The default is ``=``. 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.10 Returns: dict: A dictionary containing list of changes made Example: .. code-block:: yaml /home/saltminion/api-paste.ini: ini.options_absent: - separator: '=' - sections: test: - testkey - secondoption test1: - testkey1 """ ret = { "name": name, "changes": {}, "result": True, "comment": "No anomaly detected", } if __opts__["test"]: ret["result"] = True ret["comment"] = "" for section in sections or {}: section_name = " in section " + section if section else "" try: cur_section = __salt__["ini.get_section"]( file_name=name, section=section, separator=separator, encoding=encoding, ) except OSError as err: ret["comment"] = f"{err}" ret["result"] = False return ret except AttributeError: cur_section = section if isinstance(sections[section], list): for key in sections[section]: cur_value = cur_section.get(key) if not cur_value: ret["comment"] += "Key {}{} does not exist.\n".format( key, section_name ) continue ret["comment"] += f"Deleted key {key}{section_name}.\n" ret["result"] = None else: option = section if not __salt__["ini.get_option"]( file_name=name, section=None, option=option, separator=separator, encoding=encoding, ): ret["comment"] += f"Key {option} does not exist.\n" continue ret["comment"] += f"Deleted key {option}.\n" ret["result"] = None if ret["comment"] == "": ret["comment"] = "No changes detected." return ret sections = sections or {} for section, keys in sections.items(): for key in keys: try: current_value = __salt__["ini.remove_option"]( file_name=name, section=section, option=key, separator=separator, encoding=encoding, ) except OSError as err: ret["comment"] = f"{err}" ret["result"] = False return ret if not current_value: continue if section not in ret["changes"]: ret["changes"].update({section: {}}) ret["changes"][section].update({key: current_value}) if not isinstance(sections[section], list): ret["changes"].update({section: current_value}) # break ret["comment"] = "Changes take effect" return ret def sections_present(name, sections=None, separator="=", encoding=None): """ Add sections to an ini file. This will only create empty sections. To also create key/value pairs, use options_present state. Args: name (str): The path to the ini file sections (dict): A dictionary of sections and key/value pairs that will be used to update the ini file. Only the sections portion is used, key/value pairs are ignored. To also set key/value pairs, use the options_present state. separator (str): The character used to separate keys and values. Standard ini files use the "=" character. The default is ``=``. 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.10 Returns: dict: A dictionary containing list of changes made Example: .. code-block:: yaml /home/saltminion/api-paste.ini: ini.sections_present: - separator: '=' - sections: - section_one - section_two """ ret = { "name": name, "changes": {}, "result": True, "comment": "No anomaly detected", } if __opts__["test"]: ret["result"] = True ret["comment"] = "" try: cur_ini = __salt__["ini.get_ini"]( file_name=name, separator=separator, encoding=encoding ) except OSError as err: ret["result"] = False ret["comment"] = f"{err}" return ret for section in sections or {}: if section in cur_ini: ret["comment"] += f"Section unchanged {section}.\n" continue else: ret["comment"] += f"Created new section {section}.\n" ret["result"] = None if ret["comment"] == "": ret["comment"] = "No changes detected." return ret section_to_update = {} for section_name in sections or []: section_to_update.update({section_name: {}}) try: changes = __salt__["ini.set_option"]( file_name=name, section=section_to_update, separator=separator, encoding=encoding, ) except OSError as err: ret["result"] = False ret["comment"] = f"{err}" return ret if "error" in changes: ret["result"] = False ret["changes"] = "Errors encountered {}".format(changes["error"]) return ret ret["changes"] = changes ret["comment"] = "Changes take effect" return ret def sections_absent(name, sections=None, separator="=", encoding=None): """ Remove sections from the ini file. All key/value pairs in the section will also be removed. Args: name (str): The path to the ini file sections (dict): A dictionary of sections and key/value pairs that will be used to update the ini file. Other sections and key/value pairs in the ini file will be untouched unless ``strict: True`` is passed. separator (str): The character used to separate keys and values. Standard ini files use the "=" character. The default is ``=``. 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 list of changes made Example: .. code-block:: yaml /home/saltminion/api-paste.ini: ini.sections_absent: - separator: '=' - sections: - test - test1 """ ret = { "name": name, "changes": {}, "result": True, "comment": "No anomaly detected", } if __opts__["test"]: ret["result"] = True ret["comment"] = "" try: cur_ini = __salt__["ini.get_ini"]( file_name=name, separator=separator, encoding=encoding ) except OSError as err: ret["result"] = False ret["comment"] = f"{err}" return ret for section in sections or []: if section not in cur_ini: ret["comment"] += f"Section {section} does not exist.\n" continue ret["comment"] += f"Deleted section {section}.\n" ret["result"] = None if ret["comment"] == "": ret["comment"] = "No changes detected." return ret for section in sections or []: try: cur_section = __salt__["ini.remove_section"]( file_name=name, section=section, separator=separator, encoding=encoding ) except OSError as err: ret["result"] = False ret["comment"] = f"{err}" return ret if not cur_section: continue ret["changes"][section] = cur_section ret["comment"] = "Changes take effect" return ret