1"""
2Management of Jenkins
3=====================
4
5.. versionadded:: 2016.3.0
6
7"""
8
9
10import difflib
11import io
12import logging
13
14# Import XML parser
15import xml.etree.ElementTree as ET
16
17import salt.utils.files
18import salt.utils.stringutils
19from salt.exceptions import CommandExecutionError
20
21log = logging.getLogger(__name__)
22
23
24def _elements_equal(e1, e2):
25    if e1.tag != e2.tag:
26        return False
27    if e1.text != e2.text:
28        return False
29    if e1.tail != e2.tail:
30        return False
31    if e1.attrib != e2.attrib:
32        return False
33    if len(e1) != len(e2):
34        return False
35    return all(_elements_equal(c1, c2) for c1, c2 in zip(e1, e2))
36
37
38def _fail(ret, msg):
39    ret["comment"] = msg
40    ret["result"] = False
41    return ret
42
43
44def present(name, config=None, **kwargs):
45    """
46    Ensure the job is present in the Jenkins configured jobs
47
48    name
49        The unique name for the Jenkins job
50
51    config
52        The Salt URL for the file to use for configuring the job
53    """
54
55    ret = {
56        "name": name,
57        "result": True,
58        "changes": {},
59        "comment": ["Job {} is up to date.".format(name)],
60    }
61
62    if __salt__["jenkins.job_exists"](name):
63        _current_job_config = __salt__["jenkins.get_job_config"](name)
64        buf = io.StringIO(_current_job_config)
65        oldXML = ET.fromstring(buf.read())
66
67        cached_source_path = __salt__["cp.cache_file"](config, __env__)
68        with salt.utils.files.fopen(cached_source_path) as _fp:
69            newXML = ET.fromstring(salt.utils.stringutils.to_unicode(_fp.read()))
70        if not _elements_equal(oldXML, newXML):
71            diff = difflib.unified_diff(
72                ET.tostringlist(oldXML, encoding="utf8", method="xml"),
73                ET.tostringlist(newXML, encoding="utf8", method="xml"),
74                lineterm="",
75            )
76            try:
77                __salt__["jenkins.update_job"](name, config, __env__)
78            except CommandExecutionError as exc:
79                return _fail(ret, exc.strerror)
80            else:
81                ret["changes"] = "".join(diff)
82                ret["comment"].append("Job '{}' updated.".format(name))
83
84    else:
85        cached_source_path = __salt__["cp.cache_file"](config, __env__)
86        with salt.utils.files.fopen(cached_source_path) as _fp:
87            new_config_xml = salt.utils.stringutils.to_unicode(_fp.read())
88
89        try:
90            __salt__["jenkins.create_job"](name, config, __env__)
91        except CommandExecutionError as exc:
92            return _fail(ret, exc.strerror)
93
94        buf = io.StringIO(new_config_xml)
95        diff = difflib.unified_diff("", buf.readlines(), lineterm="")
96        ret["changes"][name] = "".join(diff)
97        ret["comment"].append("Job '{}' added.".format(name))
98
99    ret["comment"] = "\n".join(ret["comment"])
100    return ret
101
102
103def absent(name, **kwargs):
104    """
105    Ensure the job is absent from the Jenkins configured jobs
106
107    name
108        The name of the Jenkins job to remove
109    """
110    ret = {"name": name, "result": True, "changes": {}, "comment": []}
111
112    if __salt__["jenkins.job_exists"](name):
113        try:
114            __salt__["jenkins.delete_job"](name)
115        except CommandExecutionError as exc:
116            return _fail(ret, exc.strerror)
117        else:
118            ret["comment"] = "Job '{}' deleted.".format(name)
119    else:
120        ret["comment"] = "Job '{}' already absent.".format(name)
121    return ret
122