blob: e93bcbb3c501a5ee85a34b12490e392f4c97e0c3 [file] [log] [blame]
Jakub Josefe3807982016-12-15 11:54:51 +01001# -*- coding: utf-8 -*-
2
3# Import Python libs
4from __future__ import absolute_import
5import difflib
6import logging
7
8# Import Salt libs
9import salt.ext.six as six
10import salt.utils
11
12# Import XML parser
13import xml.etree.ElementTree as ET
chnyda90f133f2017-08-02 10:46:13 +020014import hashlib
15
16# Jenkins
17try:
Adam Tengler70763e02017-08-21 16:50:32 +000018 import jenkins
19 HAS_JENKINS = True
chnyda90f133f2017-08-02 10:46:13 +020020except ImportError:
Adam Tengler70763e02017-08-21 16:50:32 +000021 HAS_JENKINS = False
Jakub Josefe3807982016-12-15 11:54:51 +010022
23log = logging.getLogger(__name__)
24
25
Ilya Kharin3d8bffe2017-06-22 17:40:31 +040026def __virtual__():
27 '''
28 Only load if jenkins_common module exist.
29 '''
Adam Tengler70763e02017-08-21 16:50:32 +000030 if not HAS_JENKINS:
31 return (
32 False,
33 'The jenkins_job state module cannot be loaded: '
34 'python Jenkins API client could not be imported')
35 if 'jenkins_common.call_groovy_script' not in __salt__:
Ilya Kharin3d8bffe2017-06-22 17:40:31 +040036 return (
37 False,
38 'The jenkins_job state module cannot be loaded: '
39 'jenkins_common not found')
40 return True
41
42
Jakub Josefe3807982016-12-15 11:54:51 +010043def _elements_equal(e1, e2):
chnyda90f133f2017-08-02 10:46:13 +020044 return hashlib.md5(e1).hexdigest() == hashlib.md5(e2).hexdigest()
Jakub Josefe3807982016-12-15 11:54:51 +010045
46
47def present(name,
48 config=None,
49 **kwargs):
50 '''
51 Ensure the job is present in the Jenkins
52 configured jobs
53 name
54 The unique name for the Jenkins job
55 config
56 The Salt URL for the file to use for
57 configuring the job.
58 '''
Jakub Josef2a7739b2017-01-24 18:33:44 +010059 test = __opts__['test']
Jakub Josefe3807982016-12-15 11:54:51 +010060 ret = {'name': name,
61 'result': True,
62 'changes': {},
63 'comment': ['Job {0} is up to date.'.format(name)]}
Jakub Josef2a7739b2017-01-24 18:33:44 +010064 if test:
65 status = 'CREATED'
66 ret['changes'][name] = status
67 ret['comment'] = 'Job %s %s' % (name, status.lower())
Jakub Josefe3807982016-12-15 11:54:51 +010068 else:
chnyda90f133f2017-08-02 10:46:13 +020069 _current_job_config = ''
70 _job_exists = True
71 try:
Jakub Josef2a7739b2017-01-24 18:33:44 +010072 _current_job_config = __salt__['jenkins.get_job_config'](name)
Filip Pytlounce9cae62017-12-03 15:42:25 +010073 except Exception as e:
74 if 'does not exist' in str(e):
chnyda2d787312017-08-07 20:07:56 +020075 _job_exists = False
76 else:
77 raise e
78
chnyda90f133f2017-08-02 10:46:13 +020079 if _job_exists:
Jakub Josef2a7739b2017-01-24 18:33:44 +010080 buf = six.moves.StringIO(_current_job_config)
chnyda90f133f2017-08-02 10:46:13 +020081 oldXMLstring = buf.read()
Jakub Josefe3807982016-12-15 11:54:51 +010082
Jakub Josef2a7739b2017-01-24 18:33:44 +010083 cached_source_path = __salt__['cp.cache_file'](config, __env__)
84 with salt.utils.fopen(cached_source_path) as _fp:
chnyda90f133f2017-08-02 10:46:13 +020085 newXMLstring = _fp.read()
86 if not _elements_equal(oldXMLstring.strip(), newXMLstring.strip()):
87 oldXML = ET.fromstring(oldXMLstring)
88 newXML = ET.fromstring(newXMLstring)
Jakub Josef2a7739b2017-01-24 18:33:44 +010089 diff = difflib.unified_diff(
90 ET.tostringlist(oldXML, encoding='utf8', method='xml'),
91 ET.tostringlist(newXML, encoding='utf8', method='xml'), lineterm='')
92 __salt__['jenkins.update_job'](name, config, __env__)
Jakub Josef654a1482017-01-26 17:41:16 +010093 ret['changes'][name] = ''.join(diff)
chnydaea190432017-08-08 15:37:29 +020094 ret['comment'].append('Job {0} updated.'.format(name))
Jakub Josefe3807982016-12-15 11:54:51 +010095
Jakub Josef2a7739b2017-01-24 18:33:44 +010096 else:
97 cached_source_path = __salt__['cp.cache_file'](config, __env__)
98 with salt.utils.fopen(cached_source_path) as _fp:
99 new_config_xml = _fp.read()
Jakub Josefe3807982016-12-15 11:54:51 +0100100
Jakub Josef2a7739b2017-01-24 18:33:44 +0100101 __salt__['jenkins.create_job'](name, config, __env__)
Jakub Josefe3807982016-12-15 11:54:51 +0100102
Jakub Josef2a7739b2017-01-24 18:33:44 +0100103 buf = six.moves.StringIO(new_config_xml)
Jakub Josef654a1482017-01-26 17:41:16 +0100104 diff = difflib.unified_diff('', buf.readlines(), lineterm='')
105 ret['changes'][name] = ''.join(diff)
Jakub Josef2a7739b2017-01-24 18:33:44 +0100106 ret['comment'].append('Job {0} added.'.format(name))
107
108 ret['comment'] = '\n'.join(ret['comment'])
Jakub Josefe3807982016-12-15 11:54:51 +0100109 return ret
110
111
112def absent(name,
113 **kwargs):
114 '''
115 Ensure the job is present in the Jenkins
116 configured jobs
117
118 name
119 The name of the Jenkins job to remove.
120
121 '''
Jakub Josef2a7739b2017-01-24 18:33:44 +0100122 test = __opts__['test']
Jakub Josefe3807982016-12-15 11:54:51 +0100123 ret = {'name': name,
124 'result': True,
125 'changes': {},
126 'comment': []}
Jakub Josef2a7739b2017-01-24 18:33:44 +0100127 if test:
128 status = 'DELETED'
129 ret['changes'][name] = status
130 ret['comment'] = 'Node %s %s' % (name, status.lower())
Jakub Josefe3807982016-12-15 11:54:51 +0100131 else:
Jakub Josef2a7739b2017-01-24 18:33:44 +0100132 _job_exists = __salt__['jenkins.job_exists'](name)
133
134 if _job_exists:
135 __salt__['jenkins.delete_job'](name)
136 ret['comment'] = 'Job {0} deleted.'.format(name)
137 else:
138 ret['comment'] = 'Job {0} already absent.'.format(name)
139 return ret
140
141
142def cleanup(name, jobs, **kwargs):
143 '''
144 Perform a cleanup - uninstall any installed job absents in given jobs list
145
146 name
147 The name of the Jenkins job to remove.
148 jobs
149 List of jobs which may NOT be uninstalled
150
151 '''
152 test = __opts__['test']
153 ret = {'name': name,
154 'result': True,
155 'changes': {},
156 'comment': "Cleanup not necessary"}
157 list_jobs_groovy = """\
Adam Tengler70763e02017-08-21 16:50:32 +0000158 print(Jenkins.instance.items.collect{it -> it.name})
Jakub Josef2a7739b2017-01-24 18:33:44 +0100159 """
160 deleted_jobs = []
161 if test:
162 status = 'CLEANED'
163 ret['changes'][name] = status
164 ret['comment'] = 'Jobs %s' % status.lower()
165 else:
Adam Tengler70763e02017-08-21 16:50:32 +0000166 call_result = __salt__['jenkins_common.call_groovy_script'](
167 list_jobs_groovy, {})
Jakub Josef2a7739b2017-01-24 18:33:44 +0100168 if call_result["code"] == 200:
169 existing_jobs = call_result["msg"]
170 if existing_jobs:
171 for job in existing_jobs[1:-1].split(","):
172 if job:
173 job = job.strip()
174 if job not in jobs:
175 __salt__['jenkins.delete_job'](job)
176 deleted_jobs.append(job)
177 else:
178 log.error("Cannot get existing jobs list from Jenkins")
179 if len(deleted_jobs) > 0:
180 for del_job in deleted_jobs:
181 ret['changes'][del_job] = "removed"
182 ret['comment'] = 'Jobs {} deleted.'.format(deleted_jobs)
183 else:
184 status = 'FAILED'
185 log.error(
186 "Jenkins jobs API call failure: %s", call_result["msg"])
187 ret['comment'] = 'Jenkins jobs API call failure: %s' % (
188 call_result["msg"])
Jakub Josefe3807982016-12-15 11:54:51 +0100189 return ret