blob: fbd5c43d842fc4c8b90776489795fcee4eb598d3 [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
10
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +030011class JenkinsWrapper(jenkins.Jenkins):
12 """Workaround for the bug:
13 https://bugs.launchpad.net/python-jenkins/+bug/1775047
14 """
15 def _response_handler(self, response):
16 '''Handle response objects'''
17
18 # raise exceptions if occurred
19 response.raise_for_status()
20
21 headers = response.headers
22 if (headers.get('content-length') is None and
23 headers.get('transfer-encoding') is None and
24 (response.status_code == 201 and
25 headers.get('location') is None) and
26 (response.content is None or len(response.content) <= 0)):
27 # response body should only exist if one of these is provided
28 raise jenkins.EmptyResponseException(
29 "Error communicating with server[%s]: "
30 "empty response" % self.server)
31
32 # Response objects will automatically return unicode encoded
33 # when accessing .text property
34 return response
35
36
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020037class JenkinsClient(object):
38
Dennis Dmitriev27a96792018-07-30 07:52:03 +030039 def __init__(self, host=None, username='admin', password='r00tme'):
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020040 host = host or 'http://172.16.44.33:8081'
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +030041 # self.__client = jenkins.Jenkins(
42 self.__client = JenkinsWrapper(
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020043 host,
44 username=username,
45 password=password)
46
47 def jobs(self):
48 return self.__client.get_jobs()
49
50 def find_jobs(self, name):
51 return filter(lambda x: name in x['fullname'], self.jobs())
52
53 def job_info(self, name):
54 return self.__client.get_job_info(name)
55
56 def list_builds(self, name):
57 return self.job_info(name).get('builds')
58
59 def build_info(self, name, build_id):
60 return self.__client.get_build_info(name, build_id)
61
62 def job_params(self, name):
63 job = self.job_info(name)
64 job_params = next(
65 p for p in job['property'] if
66 'hudson.model.ParametersDefinitionProperty' == p['_class'])
67 job_params = job_params['parameterDefinitions']
68 return job_params
69
70 def make_defults_params(self, name):
71 job_params = self.job_params(name)
72 def_params = dict(
73 [(j['name'], j['defaultParameterValue']['value'])
74 for j in job_params])
75 return def_params
76
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030077 def run_build(self, name, params=None, timeout=600, verbose=False):
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020078 params = params or self.make_defults_params(name)
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030079 num = self.__client.build_job(name, params)
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +030080 time.sleep(2) # wait while job is started
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030081
82 def is_blocked():
83 queued = self.__client.get_queue_item(num)
84 status = not queued['blocked']
85 if not status and verbose:
86 print("pending the job [{}] : {}".format(name, queued['why']))
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +030087 return (status and
88 'executable' in (queued or {}) and
89 'number' in (queued['executable'] or {}))
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030090
91 helpers.wait(
92 is_blocked,
93 timeout=timeout,
94 interval=30,
95 timeout_msg='Timeout waiting to run the job [{}]'.format(name))
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030096 build_id = self.__client.get_queue_item(num)['executable']['number']
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020097 return name, build_id
98
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +030099 def wait_end_of_build(self, name, build_id, timeout=600, interval=5,
100 verbose=False, job_output_prefix=''):
101 '''Wait until the specified build is finished
102
103 :param name: ``str``, job name
104 :param build_id: ``int``, build id
105 :param timeout: ``int``, timeout waiting the job, sec
106 :param interval: ``int``, interval of polling the job result, sec
107 :param verbose: ``bool``, print the job console updates during waiting
108 :param job_output_prefix: ``str``, print the prefix for each console
109 output line, with the pre-defined
110 substitution keys:
111 - '{name}' : the current job name
112 - '{build_id}' : the current build-id
113 - '{time}' : the current time
114 :returns: requests object with headers and console output, ``obj``
115 '''
Dennis Dmitriev61115112018-05-31 06:48:43 +0300116 start = [0]
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300117 time_str = time.strftime("%H:%M:%S")
118 prefix = "\n" + job_output_prefix.format(job_name=name,
119 build_number=build_id,
120 time=time_str)
121 if verbose:
122 print(prefix, end='')
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200123
124 def building():
Dennis Dmitriev61115112018-05-31 06:48:43 +0300125 status = not self.build_info(name, build_id)['building']
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300126 if verbose:
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300127 time_str = time.strftime("%H:%M:%S")
128 prefix = "\n" + job_output_prefix.format(
129 job_name=name, build_number=build_id, time=time_str)
Dennis Dmitriev61115112018-05-31 06:48:43 +0300130 res = self.get_progressive_build_output(name,
131 build_id,
132 start=start[0])
133 if 'X-Text-Size' in res.headers:
134 text_size = int(res.headers['X-Text-Size'])
135 if start[0] < text_size:
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300136 text = res.content.decode('utf-8',
137 errors='backslashreplace')
138 print(text.replace("\n", prefix), end='')
Dennis Dmitriev61115112018-05-31 06:48:43 +0300139 start[0] = text_size
140 return status
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200141
142 helpers.wait(
143 building,
144 timeout=timeout,
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300145 interval=interval,
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200146 timeout_msg='Timeout waiting, job {0} are not finished "{1}" build'
147 ' still'.format(name, build_id))
Dmitry Tyzhnenkob610afd2018-02-19 15:43:45 +0200148
149 def get_build_output(self, name, build_id):
150 return self.__client.get_build_console_output(name, build_id)
Dennis Dmitriev61115112018-05-31 06:48:43 +0300151
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300152 def get_progressive_build_output(self, name, build_id, start=0):
Dennis Dmitriev61115112018-05-31 06:48:43 +0300153 '''Get build console text.
154
155 :param name: Job name, ``str``
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300156 :param build_id: Build id, ``int``
157 :param start: Start offset, ``int``
Dennis Dmitriev61115112018-05-31 06:48:43 +0300158 :returns: requests object with headers and console output, ``obj``
159 '''
160 folder_url, short_name = self.__client._get_job_folder(name)
161
162 PROGRESSIVE_CONSOLE_OUTPUT = (
163 '%(folder_url)sjob/%(short_name)s/%(build_id)d/'
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300164 'logText/progressiveText?start=%(start)d')
165 req = requests.Request(
166 'GET',
167 self.__client._build_url(PROGRESSIVE_CONSOLE_OUTPUT, locals()))
168 return(self.__client.jenkins_request(req))
Dennis Dmitriev27a96792018-07-30 07:52:03 +0300169
170 def get_workflow(self, name, build_id, enode=None, mode='describe'):
171 '''Get workflow results from pipeline job
172
173 :param name: job name
174 :param build_id: str, build number or 'lastBuild'
175 :param enode: int, execution node in the workflow
176 :param mode: the stage or execution node description if 'describe',
177 the execution node log if 'log'
178 '''
179 folder_url, short_name = self.__client._get_job_folder(name)
180
181 if enode:
182 WORKFLOW_DESCRIPTION = (
183 '%(folder_url)sjob/%(short_name)s/%(build_id)s/'
184 'execution/node/%(enode)d/wfapi/%(mode)s')
185 else:
186 WORKFLOW_DESCRIPTION = (
187 '%(folder_url)sjob/%(short_name)s/%(build_id)s/wfapi/%(mode)s')
188 req = requests.Request(
189 'GET',
190 self.__client._build_url(WORKFLOW_DESCRIPTION, locals()))
191 response = self.__client.jenkins_open(req)
192 return json.loads(response)