blob: 64044327dd114438373e1f7d21e7aa034ebea71b [file] [log] [blame]
Dennis Dmitriev61115112018-05-31 06:48:43 +03001from __future__ import print_function
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +03002import time
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +02003
4import jenkins
Dennis Dmitriev27a96792018-07-30 07:52:03 +03005import json
Dennis Dmitriev61115112018-05-31 06:48:43 +03006import requests
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +02007
8from devops.helpers import helpers
9
Dmitry Tyzhnenko80ce0202019-02-07 13:27:19 +020010from requests.exceptions import ConnectionError
11
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020012
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +030013class JenkinsWrapper(jenkins.Jenkins):
14 """Workaround for the bug:
15 https://bugs.launchpad.net/python-jenkins/+bug/1775047
16 """
17 def _response_handler(self, response):
18 '''Handle response objects'''
19
20 # raise exceptions if occurred
21 response.raise_for_status()
22
23 headers = response.headers
24 if (headers.get('content-length') is None and
25 headers.get('transfer-encoding') is None and
26 (response.status_code == 201 and
27 headers.get('location') is None) and
28 (response.content is None or len(response.content) <= 0)):
29 # response body should only exist if one of these is provided
30 raise jenkins.EmptyResponseException(
31 "Error communicating with server[%s]: "
32 "empty response" % self.server)
33
34 # Response objects will automatically return unicode encoded
35 # when accessing .text property
36 return response
37
38
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020039class JenkinsClient(object):
40
Dennis Dmitriev27a96792018-07-30 07:52:03 +030041 def __init__(self, host=None, username='admin', password='r00tme'):
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020042 host = host or 'http://172.16.44.33:8081'
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +030043 # self.__client = jenkins.Jenkins(
44 self.__client = JenkinsWrapper(
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020045 host,
46 username=username,
47 password=password)
48
49 def jobs(self):
50 return self.__client.get_jobs()
51
52 def find_jobs(self, name):
53 return filter(lambda x: name in x['fullname'], self.jobs())
54
55 def job_info(self, name):
56 return self.__client.get_job_info(name)
57
58 def list_builds(self, name):
59 return self.job_info(name).get('builds')
60
61 def build_info(self, name, build_id):
62 return self.__client.get_build_info(name, build_id)
63
64 def job_params(self, name):
65 job = self.job_info(name)
66 job_params = next(
67 p for p in job['property'] if
68 'hudson.model.ParametersDefinitionProperty' == p['_class'])
69 job_params = job_params['parameterDefinitions']
70 return job_params
71
72 def make_defults_params(self, name):
73 job_params = self.job_params(name)
74 def_params = dict(
75 [(j['name'], j['defaultParameterValue']['value'])
76 for j in job_params])
77 return def_params
78
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030079 def run_build(self, name, params=None, timeout=600, verbose=False):
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020080 params = params or self.make_defults_params(name)
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030081 num = self.__client.build_job(name, params)
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +030082 time.sleep(2) # wait while job is started
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030083
84 def is_blocked():
85 queued = self.__client.get_queue_item(num)
86 status = not queued['blocked']
87 if not status and verbose:
88 print("pending the job [{}] : {}".format(name, queued['why']))
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +030089 return (status and
90 'executable' in (queued or {}) and
91 'number' in (queued['executable'] or {}))
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030092
93 helpers.wait(
94 is_blocked,
95 timeout=timeout,
96 interval=30,
97 timeout_msg='Timeout waiting to run the job [{}]'.format(name))
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030098 build_id = self.__client.get_queue_item(num)['executable']['number']
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020099 return name, build_id
100
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300101 def wait_end_of_build(self, name, build_id, timeout=600, interval=5,
102 verbose=False, job_output_prefix=''):
103 '''Wait until the specified build is finished
104
105 :param name: ``str``, job name
106 :param build_id: ``int``, build id
107 :param timeout: ``int``, timeout waiting the job, sec
108 :param interval: ``int``, interval of polling the job result, sec
109 :param verbose: ``bool``, print the job console updates during waiting
110 :param job_output_prefix: ``str``, print the prefix for each console
111 output line, with the pre-defined
112 substitution keys:
113 - '{name}' : the current job name
114 - '{build_id}' : the current build-id
115 - '{time}' : the current time
116 :returns: requests object with headers and console output, ``obj``
117 '''
Dennis Dmitriev61115112018-05-31 06:48:43 +0300118 start = [0]
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300119 time_str = time.strftime("%H:%M:%S")
120 prefix = "\n" + job_output_prefix.format(job_name=name,
121 build_number=build_id,
122 time=time_str)
123 if verbose:
124 print(prefix, end='')
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200125
126 def building():
Dmitry Tyzhnenko80ce0202019-02-07 13:27:19 +0200127 try:
128 status = not self.build_info(name, build_id)['building']
129 except ConnectionError:
130 status = False
131
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300132 if verbose:
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300133 time_str = time.strftime("%H:%M:%S")
134 prefix = "\n" + job_output_prefix.format(
135 job_name=name, build_number=build_id, time=time_str)
Dennis Dmitriev61115112018-05-31 06:48:43 +0300136 res = self.get_progressive_build_output(name,
137 build_id,
138 start=start[0])
139 if 'X-Text-Size' in res.headers:
140 text_size = int(res.headers['X-Text-Size'])
141 if start[0] < text_size:
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300142 text = res.content.decode('utf-8',
143 errors='backslashreplace')
144 print(text.replace("\n", prefix), end='')
Dennis Dmitriev61115112018-05-31 06:48:43 +0300145 start[0] = text_size
146 return status
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200147
148 helpers.wait(
149 building,
150 timeout=timeout,
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300151 interval=interval,
Dennis Dmitriev13e804b2018-10-09 19:25:14 +0300152 timeout_msg=('Timeout waiting the job {0}:{1} in {2} sec.'
153 .format(name, build_id, timeout)))
Dmitry Tyzhnenkob610afd2018-02-19 15:43:45 +0200154
155 def get_build_output(self, name, build_id):
156 return self.__client.get_build_console_output(name, build_id)
Dennis Dmitriev61115112018-05-31 06:48:43 +0300157
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300158 def get_progressive_build_output(self, name, build_id, start=0):
Dennis Dmitriev61115112018-05-31 06:48:43 +0300159 '''Get build console text.
160
161 :param name: Job name, ``str``
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300162 :param build_id: Build id, ``int``
163 :param start: Start offset, ``int``
Dennis Dmitriev61115112018-05-31 06:48:43 +0300164 :returns: requests object with headers and console output, ``obj``
165 '''
166 folder_url, short_name = self.__client._get_job_folder(name)
167
168 PROGRESSIVE_CONSOLE_OUTPUT = (
169 '%(folder_url)sjob/%(short_name)s/%(build_id)d/'
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300170 'logText/progressiveText?start=%(start)d')
171 req = requests.Request(
172 'GET',
173 self.__client._build_url(PROGRESSIVE_CONSOLE_OUTPUT, locals()))
174 return(self.__client.jenkins_request(req))
Dennis Dmitriev27a96792018-07-30 07:52:03 +0300175
176 def get_workflow(self, name, build_id, enode=None, mode='describe'):
177 '''Get workflow results from pipeline job
178
179 :param name: job name
180 :param build_id: str, build number or 'lastBuild'
181 :param enode: int, execution node in the workflow
182 :param mode: the stage or execution node description if 'describe',
183 the execution node log if 'log'
184 '''
185 folder_url, short_name = self.__client._get_job_folder(name)
186
187 if enode:
188 WORKFLOW_DESCRIPTION = (
189 '%(folder_url)sjob/%(short_name)s/%(build_id)s/'
190 'execution/node/%(enode)d/wfapi/%(mode)s')
191 else:
192 WORKFLOW_DESCRIPTION = (
193 '%(folder_url)sjob/%(short_name)s/%(build_id)s/wfapi/%(mode)s')
194 req = requests.Request(
195 'GET',
196 self.__client._build_url(WORKFLOW_DESCRIPTION, locals()))
197 response = self.__client.jenkins_open(req)
198 return json.loads(response)
Dennis Dmitriev8565c342019-02-11 23:45:03 +0200199
200 def get_artifact(self, name, build_id, artifact_path, destination_name):
201 '''Wait until the specified build is finished
202
203 :param name: ``str``, job name
204 :param build_id: ``str``, build id or "lastBuild"
205 :param artifact_path: ``str``, path and filename of the artifact
206 relative to the job URL
207 :param artifact_path: ``str``, destination path and filename
208 on the local filesystem where to save
209 the artifact content
210 :returns: requests object with headers and console output, ``obj``
211 '''
212 folder_url, short_name = self.__client._get_job_folder(name)
213
214 DOWNLOAD_URL = ('%(folder_url)sjob/%(short_name)s/%(build_id)s/'
215 'artifact/%(artifact_path)s')
216 req = requests.Request(
217 'GET',
218 self.__client._build_url(DOWNLOAD_URL, locals()))
219
220 response = self.__client.jenkins_request(req)
221 return response.content