blob: 12565525e8c39f3fdcccc9adbf36d2906894a7c5 [file] [log] [blame]
Dennis Dmitriev61115112018-05-31 06:48:43 +03001from __future__ import print_function
Dmitry Tyzhnenkob39de052019-03-21 17:05:07 +02002import datetime
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +03003import time
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +02004
5import jenkins
Dennis Dmitriev27a96792018-07-30 07:52:03 +03006import json
Dennis Dmitriev61115112018-05-31 06:48:43 +03007import requests
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +02008
9from devops.helpers import helpers
10
Dmitry Tyzhnenko80ce0202019-02-07 13:27:19 +020011from requests.exceptions import ConnectionError
12
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020013
Hanna Arhipovae1f0b472020-01-03 17:38:11 +020014def send_request(action, endpoint):
15 """
16 Wait 2 seconds time and retry requests if it fail with connection to
17 endpoint
18 Raises Connection exceptions after 5 unsuccessful steps
19
20 :param action: string
21 :param body: string, url to send request
22 :return: response
23 :raise ConnectionError after several unsuccessful connections
24 """
25 max_retries = 5
26 sleep_time_before_repeat = 2
27
28 step = 0
29 while step < max_retries:
30 try:
31 response = requests.Request(action, endpoint)
32 return response
33 except ConnectionError as error:
34 step = step + 1
35 print("Can't get {} due to error: {}.\nRetry {}/{}".format(
36 endpoint,
37 error,
38 step,
39 max_retries
40 ))
41 time.sleep(sleep_time_before_repeat)
42 raise ConnectionError
43
44
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020045class JenkinsClient(object):
46
Dennis Dmitriev27a96792018-07-30 07:52:03 +030047 def __init__(self, host=None, username='admin', password='r00tme'):
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020048 host = host or 'http://172.16.44.33:8081'
obutenkoca858402019-07-04 18:31:39 +030049 self.__client = jenkins.Jenkins(
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020050 host,
51 username=username,
52 password=password)
obutenkoca858402019-07-04 18:31:39 +030053 self.__client._session.verify = False
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020054
55 def jobs(self):
56 return self.__client.get_jobs()
57
58 def find_jobs(self, name):
59 return filter(lambda x: name in x['fullname'], self.jobs())
60
61 def job_info(self, name):
Hanna Arhipovae1f8b242020-03-31 13:37:11 +030062 max_count = 6
63 for count in range(max_count):
64 try:
65 return self.__client.get_job_info(name)
66 except jenkins.JenkinsException as err:
67 print("caught JenkinsException: {err}. \
68 repeat {count}/{max_count}".
69 format(err=err,
70 count=count,
71 max_count=max_count))
72 time.sleep(10)
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020073
74 def list_builds(self, name):
75 return self.job_info(name).get('builds')
76
77 def build_info(self, name, build_id):
Hanna Arhipovaf3c88ff2020-09-17 17:17:02 +030078 max_count = 6
79 for count in range(max_count):
80 try:
81 build = self.__client.get_build_info(name, build_id)
82 return build
83 except jenkins.JenkinsException as err:
84 print("caught JenkinsException: {err}. \
85 repeat {count}/{max_count}".
86 format(err=err,
87 count=count,
88 max_count=max_count))
89 time.sleep(10)
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020090
91 def job_params(self, name):
92 job = self.job_info(name)
93 job_params = next(
94 p for p in job['property'] if
95 'hudson.model.ParametersDefinitionProperty' == p['_class'])
96 job_params = job_params['parameterDefinitions']
97 return job_params
98
99 def make_defults_params(self, name):
100 job_params = self.job_params(name)
101 def_params = dict(
102 [(j['name'], j['defaultParameterValue']['value'])
103 for j in job_params])
104 return def_params
105
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300106 def run_build(self, name, params=None, timeout=600, verbose=False):
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200107 params = params or self.make_defults_params(name)
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300108 num = self.__client.build_job(name, params)
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300109 time.sleep(2) # wait while job is started
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300110
Dmitry Tyzhnenkob39de052019-03-21 17:05:07 +0200111 def is_build_queued():
112 try:
113 item = self.__client.get_queue_item(num)
114 ts = item['inQueueSince'] / 1000
115 since_time = datetime.datetime.fromtimestamp(ts)
116 print("Build in the queue since {}".format(since_time))
117 return True
118 except jenkins.JenkinsException:
119 if verbose:
120 print("Build have not been queued {} yet".format(num))
121
122 helpers.wait(
123 is_build_queued,
124 timeout=timeout,
125 interval=30,
126 timeout_msg='Timeout waiting to queue the build '
127 'for {} job'.format(name))
128
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300129 def is_blocked():
130 queued = self.__client.get_queue_item(num)
131 status = not queued['blocked']
132 if not status and verbose:
133 print("pending the job [{}] : {}".format(name, queued['why']))
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300134 return (status and
135 'executable' in (queued or {}) and
136 'number' in (queued['executable'] or {}))
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300137
138 helpers.wait(
139 is_blocked,
140 timeout=timeout,
141 interval=30,
142 timeout_msg='Timeout waiting to run the job [{}]'.format(name))
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300143 build_id = self.__client.get_queue_item(num)['executable']['number']
Dmitry Tyzhnenkob39de052019-03-21 17:05:07 +0200144
145 def is_build_started():
146 try:
147 build = self.__client.get_build_info(name, build_id)
148 ts = float(build['timestamp']) / 1000
149 start_time = datetime.datetime.fromtimestamp(ts)
150 print("the build {} in {} have started at {} UTC".format(
151 build_id, name, start_time))
152 return True
153 except jenkins.JenkinsException:
154 if verbose:
155 print("the build {} in {} have not strated yet".format(
156 build_id, name))
157 helpers.wait(
158 is_build_started,
159 timeout=timeout,
160 interval=30,
161 timeout_msg='Timeout waiting to run build of '
162 'the job [{}]'.format(name))
163
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200164 return name, build_id
165
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300166 def wait_end_of_build(self, name, build_id, timeout=600, interval=5,
167 verbose=False, job_output_prefix=''):
168 '''Wait until the specified build is finished
169
170 :param name: ``str``, job name
171 :param build_id: ``int``, build id
172 :param timeout: ``int``, timeout waiting the job, sec
173 :param interval: ``int``, interval of polling the job result, sec
174 :param verbose: ``bool``, print the job console updates during waiting
175 :param job_output_prefix: ``str``, print the prefix for each console
176 output line, with the pre-defined
177 substitution keys:
178 - '{name}' : the current job name
179 - '{build_id}' : the current build-id
180 - '{time}' : the current time
181 :returns: requests object with headers and console output, ``obj``
182 '''
Dennis Dmitriev61115112018-05-31 06:48:43 +0300183 start = [0]
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300184 time_str = time.strftime("%H:%M:%S")
185 prefix = "\n" + job_output_prefix.format(job_name=name,
186 build_number=build_id,
187 time=time_str)
188 if verbose:
189 print(prefix, end='')
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200190
191 def building():
Dmitry Tyzhnenko80ce0202019-02-07 13:27:19 +0200192 try:
193 status = not self.build_info(name, build_id)['building']
194 except ConnectionError:
195 status = False
196
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300197 if verbose:
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300198 time_str = time.strftime("%H:%M:%S")
199 prefix = "\n" + job_output_prefix.format(
200 job_name=name, build_number=build_id, time=time_str)
Dennis Dmitriev61115112018-05-31 06:48:43 +0300201 res = self.get_progressive_build_output(name,
202 build_id,
203 start=start[0])
204 if 'X-Text-Size' in res.headers:
205 text_size = int(res.headers['X-Text-Size'])
206 if start[0] < text_size:
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300207 text = res.content.decode('utf-8',
208 errors='backslashreplace')
209 print(text.replace("\n", prefix), end='')
Dennis Dmitriev61115112018-05-31 06:48:43 +0300210 start[0] = text_size
211 return status
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200212
213 helpers.wait(
214 building,
215 timeout=timeout,
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300216 interval=interval,
Dennis Dmitriev13e804b2018-10-09 19:25:14 +0300217 timeout_msg=('Timeout waiting the job {0}:{1} in {2} sec.'
218 .format(name, build_id, timeout)))
Dmitry Tyzhnenkob610afd2018-02-19 15:43:45 +0200219
220 def get_build_output(self, name, build_id):
221 return self.__client.get_build_console_output(name, build_id)
Dennis Dmitriev61115112018-05-31 06:48:43 +0300222
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300223 def get_progressive_build_output(self, name, build_id, start=0):
Dennis Dmitriev61115112018-05-31 06:48:43 +0300224 '''Get build console text.
225
226 :param name: Job name, ``str``
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300227 :param build_id: Build id, ``int``
228 :param start: Start offset, ``int``
Dennis Dmitriev61115112018-05-31 06:48:43 +0300229 :returns: requests object with headers and console output, ``obj``
230 '''
231 folder_url, short_name = self.__client._get_job_folder(name)
232
233 PROGRESSIVE_CONSOLE_OUTPUT = (
234 '%(folder_url)sjob/%(short_name)s/%(build_id)d/'
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300235 'logText/progressiveText?start=%(start)d')
Hanna Arhipovae1f0b472020-01-03 17:38:11 +0200236 req = send_request(
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300237 'GET',
238 self.__client._build_url(PROGRESSIVE_CONSOLE_OUTPUT, locals()))
239 return(self.__client.jenkins_request(req))
Dennis Dmitriev27a96792018-07-30 07:52:03 +0300240
241 def get_workflow(self, name, build_id, enode=None, mode='describe'):
242 '''Get workflow results from pipeline job
243
244 :param name: job name
245 :param build_id: str, build number or 'lastBuild'
246 :param enode: int, execution node in the workflow
247 :param mode: the stage or execution node description if 'describe',
248 the execution node log if 'log'
249 '''
250 folder_url, short_name = self.__client._get_job_folder(name)
251
252 if enode:
253 WORKFLOW_DESCRIPTION = (
254 '%(folder_url)sjob/%(short_name)s/%(build_id)s/'
255 'execution/node/%(enode)d/wfapi/%(mode)s')
256 else:
257 WORKFLOW_DESCRIPTION = (
258 '%(folder_url)sjob/%(short_name)s/%(build_id)s/wfapi/%(mode)s')
Hanna Arhipovae1f0b472020-01-03 17:38:11 +0200259 req = send_request(
Dennis Dmitriev27a96792018-07-30 07:52:03 +0300260 'GET',
261 self.__client._build_url(WORKFLOW_DESCRIPTION, locals()))
262 response = self.__client.jenkins_open(req)
263 return json.loads(response)
Dennis Dmitriev8565c342019-02-11 23:45:03 +0200264
265 def get_artifact(self, name, build_id, artifact_path, destination_name):
266 '''Wait until the specified build is finished
267
268 :param name: ``str``, job name
269 :param build_id: ``str``, build id or "lastBuild"
270 :param artifact_path: ``str``, path and filename of the artifact
271 relative to the job URL
272 :param artifact_path: ``str``, destination path and filename
273 on the local filesystem where to save
274 the artifact content
275 :returns: requests object with headers and console output, ``obj``
276 '''
277 folder_url, short_name = self.__client._get_job_folder(name)
278
279 DOWNLOAD_URL = ('%(folder_url)sjob/%(short_name)s/%(build_id)s/'
280 'artifact/%(artifact_path)s')
Hanna Arhipovae1f0b472020-01-03 17:38:11 +0200281 req = send_request(
Dennis Dmitriev8565c342019-02-11 23:45:03 +0200282 'GET',
283 self.__client._build_url(DOWNLOAD_URL, locals()))
284
285 response = self.__client.jenkins_request(req)
286 return response.content