1"""
2Manage ini files
3================
4
5:maintainer: <akilesh1597@gmail.com>
6:maturity: new
7:depends: re
8:platform: all
9
10"""
11
12
13from salt.utils.odict import OrderedDict
14
15__virtualname__ = "ini"
16
17
18def __virtual__():
19    """
20    Only load if the ini module is available
21    """
22    return __virtualname__ if "ini.set_option" in __salt__ else False
23
24
25def options_present(name, sections=None, separator="=", strict=False):
26    """
27    .. code-block:: yaml
28
29        /home/saltminion/api-paste.ini:
30          ini.options_present:
31            - separator: '='
32            - strict: True
33            - sections:
34                test:
35                  testkey: 'testval'
36                  secondoption: 'secondvalue'
37                test1:
38                  testkey1: 'testval121'
39
40    options present in file and not specified in sections
41    dict will be untouched, unless `strict: True` flag is
42    used
43
44    changes dict will contain the list of changes made
45    """
46    ret = {
47        "name": name,
48        "changes": {},
49        "result": True,
50        "comment": "No anomaly detected",
51    }
52    if __opts__["test"]:
53        ret["comment"] = ""
54    # pylint: disable=too-many-nested-blocks
55    try:
56        changes = {}
57        if sections:
58            options = {}
59            for sname, sbody in sections.items():
60                if not isinstance(sbody, (dict, OrderedDict)):
61                    options.update({sname: sbody})
62            cur_ini = __salt__["ini.get_ini"](name, separator)
63            original_top_level_opts = {}
64            original_sections = {}
65            for key, val in cur_ini.items():
66                if isinstance(val, (dict, OrderedDict)):
67                    original_sections.update({key: val})
68                else:
69                    original_top_level_opts.update({key: val})
70            if __opts__["test"]:
71                for option in options:
72                    if option in original_top_level_opts:
73                        if str(original_top_level_opts[option]) == str(options[option]):
74                            ret["comment"] += "Unchanged key {}.\n".format(option)
75                        else:
76                            ret["comment"] += "Changed key {}.\n".format(option)
77                            ret["result"] = None
78                    else:
79                        ret["comment"] += "Changed key {}.\n".format(option)
80                        ret["result"] = None
81            else:
82                options_updated = __salt__["ini.set_option"](name, options, separator)
83                changes.update(options_updated)
84            if strict:
85                for opt_to_remove in set(original_top_level_opts).difference(options):
86                    if __opts__["test"]:
87                        ret["comment"] += "Removed key {}.\n".format(opt_to_remove)
88                        ret["result"] = None
89                    else:
90                        __salt__["ini.remove_option"](
91                            name, None, opt_to_remove, separator
92                        )
93                        changes.update(
94                            {
95                                opt_to_remove: {
96                                    "before": original_top_level_opts[opt_to_remove],
97                                    "after": None,
98                                }
99                            }
100                        )
101            for section_name, section_body in [
102                (sname, sbody)
103                for sname, sbody in sections.items()
104                if isinstance(sbody, (dict, OrderedDict))
105            ]:
106                section_descr = " in section " + section_name if section_name else ""
107                changes[section_name] = {}
108                if strict:
109                    original = cur_ini.get(section_name, {})
110                    for key_to_remove in set(original.keys()).difference(
111                        section_body.keys()
112                    ):
113                        orig_value = original_sections.get(section_name, {}).get(
114                            key_to_remove, "#-#-"
115                        )
116                        if __opts__["test"]:
117                            ret["comment"] += "Deleted key {}{}.\n".format(
118                                key_to_remove, section_descr
119                            )
120                            ret["result"] = None
121                        else:
122                            __salt__["ini.remove_option"](
123                                name, section_name, key_to_remove, separator
124                            )
125                            changes[section_name].update({key_to_remove: ""})
126                            changes[section_name].update(
127                                {key_to_remove: {"before": orig_value, "after": None}}
128                            )
129                if __opts__["test"]:
130                    for option in section_body:
131                        if str(section_body[option]) == str(
132                            original_sections.get(section_name, {}).get(option, "#-#-")
133                        ):
134                            ret["comment"] += "Unchanged key {}{}.\n".format(
135                                option, section_descr
136                            )
137                        else:
138                            ret["comment"] += "Changed key {}{}.\n".format(
139                                option, section_descr
140                            )
141                            ret["result"] = None
142                else:
143                    options_updated = __salt__["ini.set_option"](
144                        name, {section_name: section_body}, separator
145                    )
146                    if options_updated:
147                        changes[section_name].update(options_updated[section_name])
148                    if not changes[section_name]:
149                        del changes[section_name]
150        else:
151            if not __opts__["test"]:
152                changes = __salt__["ini.set_option"](name, sections, separator)
153    except (OSError, KeyError) as err:
154        ret["comment"] = "{}".format(err)
155        ret["result"] = False
156        return ret
157    if "error" in changes:
158        ret["result"] = False
159        ret["comment"] = "Errors encountered. {}".format(changes["error"])
160        ret["changes"] = {}
161    else:
162        for ciname, body in changes.items():
163            if body:
164                ret["comment"] = "Changes take effect"
165                ret["changes"].update({ciname: changes[ciname]})
166    return ret
167
168
169def options_absent(name, sections=None, separator="="):
170    """
171    .. code-block:: yaml
172
173        /home/saltminion/api-paste.ini:
174          ini.options_absent:
175            - separator: '='
176            - sections:
177                test:
178                  - testkey
179                  - secondoption
180                test1:
181                  - testkey1
182
183    options present in file and not specified in sections
184    dict will be untouched
185
186    changes dict will contain the list of changes made
187    """
188    ret = {
189        "name": name,
190        "changes": {},
191        "result": True,
192        "comment": "No anomaly detected",
193    }
194    if __opts__["test"]:
195        ret["result"] = True
196        ret["comment"] = ""
197        for section in sections or {}:
198            section_name = " in section " + section if section else ""
199            try:
200                cur_section = __salt__["ini.get_section"](name, section, separator)
201            except OSError as err:
202                ret["comment"] = "{}".format(err)
203                ret["result"] = False
204                return ret
205            except AttributeError:
206                cur_section = section
207            if isinstance(sections[section], list):
208                for key in sections[section]:
209                    cur_value = cur_section.get(key)
210                    if not cur_value:
211                        ret["comment"] += "Key {}{} does not exist.\n".format(
212                            key, section_name
213                        )
214                        continue
215                    ret["comment"] += "Deleted key {}{}.\n".format(key, section_name)
216                    ret["result"] = None
217            else:
218                option = section
219                if not __salt__["ini.get_option"](name, None, option, separator):
220                    ret["comment"] += "Key {} does not exist.\n".format(option)
221                    continue
222                ret["comment"] += "Deleted key {}.\n".format(option)
223                ret["result"] = None
224
225        if ret["comment"] == "":
226            ret["comment"] = "No changes detected."
227        return ret
228    sections = sections or {}
229    for section, keys in sections.items():
230        for key in keys:
231            try:
232                current_value = __salt__["ini.remove_option"](
233                    name, section, key, separator
234                )
235            except OSError as err:
236                ret["comment"] = "{}".format(err)
237                ret["result"] = False
238                return ret
239            if not current_value:
240                continue
241            if section not in ret["changes"]:
242                ret["changes"].update({section: {}})
243            ret["changes"][section].update({key: current_value})
244            if not isinstance(sections[section], list):
245                ret["changes"].update({section: current_value})
246                # break
247            ret["comment"] = "Changes take effect"
248    return ret
249
250
251def sections_present(name, sections=None, separator="="):
252    """
253    .. code-block:: yaml
254
255        /home/saltminion/api-paste.ini:
256          ini.sections_present:
257            - separator: '='
258            - sections:
259                - section_one
260                - section_two
261
262    This will only create empty sections. To also create options, use
263    options_present state
264
265    options present in file and not specified in sections will be deleted
266    changes dict will contain the sections that changed
267    """
268    ret = {
269        "name": name,
270        "changes": {},
271        "result": True,
272        "comment": "No anomaly detected",
273    }
274    if __opts__["test"]:
275        ret["result"] = True
276        ret["comment"] = ""
277        try:
278            cur_ini = __salt__["ini.get_ini"](name, separator)
279        except OSError as err:
280            ret["result"] = False
281            ret["comment"] = "{}".format(err)
282            return ret
283        for section in sections or {}:
284            if section in cur_ini:
285                ret["comment"] += "Section unchanged {}.\n".format(section)
286                continue
287            else:
288                ret["comment"] += "Created new section {}.\n".format(section)
289            ret["result"] = None
290        if ret["comment"] == "":
291            ret["comment"] = "No changes detected."
292        return ret
293    section_to_update = {}
294    for section_name in sections or []:
295        section_to_update.update({section_name: {}})
296    try:
297        changes = __salt__["ini.set_option"](name, section_to_update, separator)
298    except OSError as err:
299        ret["result"] = False
300        ret["comment"] = "{}".format(err)
301        return ret
302    if "error" in changes:
303        ret["result"] = False
304        ret["changes"] = "Errors encountered {}".format(changes["error"])
305        return ret
306    ret["changes"] = changes
307    ret["comment"] = "Changes take effect"
308    return ret
309
310
311def sections_absent(name, sections=None, separator="="):
312    """
313    .. code-block:: yaml
314
315        /home/saltminion/api-paste.ini:
316          ini.sections_absent:
317            - separator: '='
318            - sections:
319                - test
320                - test1
321
322    options present in file and not specified in sections will be deleted
323    changes dict will contain the sections that changed
324    """
325    ret = {
326        "name": name,
327        "changes": {},
328        "result": True,
329        "comment": "No anomaly detected",
330    }
331    if __opts__["test"]:
332        ret["result"] = True
333        ret["comment"] = ""
334        try:
335            cur_ini = __salt__["ini.get_ini"](name, separator)
336        except OSError as err:
337            ret["result"] = False
338            ret["comment"] = "{}".format(err)
339            return ret
340        for section in sections or []:
341            if section not in cur_ini:
342                ret["comment"] += "Section {} does not exist.\n".format(section)
343                continue
344            ret["comment"] += "Deleted section {}.\n".format(section)
345            ret["result"] = None
346        if ret["comment"] == "":
347            ret["comment"] = "No changes detected."
348        return ret
349    for section in sections or []:
350        try:
351            cur_section = __salt__["ini.remove_section"](name, section, separator)
352        except OSError as err:
353            ret["result"] = False
354            ret["comment"] = "{}".format(err)
355            return ret
356        if not cur_section:
357            continue
358        ret["changes"][section] = cur_section
359        ret["comment"] = "Changes take effect"
360    return ret
361