blob: 2628e7abfef48f5e93352c3652725bf4c7ea5e7d [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 Dmitriev61115112018-05-31 06:48:43 +03005import requests
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +02006
7from devops.helpers import helpers
8
9
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +030010class JenkinsWrapper(jenkins.Jenkins):
11 """Workaround for the bug:
12 https://bugs.launchpad.net/python-jenkins/+bug/1775047
13 """
14 def _response_handler(self, response):
15 '''Handle response objects'''
16
17 # raise exceptions if occurred
18 response.raise_for_status()
19
20 headers = response.headers
21 if (headers.get('content-length') is None and
22 headers.get('transfer-encoding') is None and
23 (response.status_code == 201 and
24 headers.get('location') is None) and
25 (response.content is None or len(response.content) <= 0)):
26 # response body should only exist if one of these is provided
27 raise jenkins.EmptyResponseException(
28 "Error communicating with server[%s]: "
29 "empty response" % self.server)
30
31 # Response objects will automatically return unicode encoded
32 # when accessing .text property
33 return response
34
35
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020036class JenkinsClient(object):
37
38 def __init__(self, host=None, username=None, password=None):
39 host = host or 'http://172.16.44.33:8081'
40 username = username or 'admin'
41 password = password or 'r00tme'
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +030042 # self.__client = jenkins.Jenkins(
43 self.__client = JenkinsWrapper(
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020044 host,
45 username=username,
46 password=password)
47
48 def jobs(self):
49 return self.__client.get_jobs()
50
51 def find_jobs(self, name):
52 return filter(lambda x: name in x['fullname'], self.jobs())
53
54 def job_info(self, name):
55 return self.__client.get_job_info(name)
56
57 def list_builds(self, name):
58 return self.job_info(name).get('builds')
59
60 def build_info(self, name, build_id):
61 return self.__client.get_build_info(name, build_id)
62
63 def job_params(self, name):
64 job = self.job_info(name)
65 job_params = next(
66 p for p in job['property'] if
67 'hudson.model.ParametersDefinitionProperty' == p['_class'])
68 job_params = job_params['parameterDefinitions']
69 return job_params
70
71 def make_defults_params(self, name):
72 job_params = self.job_params(name)
73 def_params = dict(
74 [(j['name'], j['defaultParameterValue']['value'])
75 for j in job_params])
76 return def_params
77
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030078 def run_build(self, name, params=None, timeout=600, verbose=False):
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020079 params = params or self.make_defults_params(name)
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030080 num = self.__client.build_job(name, params)
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +030081 time.sleep(2) # wait while job is started
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030082
83 def is_blocked():
84 queued = self.__client.get_queue_item(num)
85 status = not queued['blocked']
86 if not status and verbose:
87 print("pending the job [{}] : {}".format(name, queued['why']))
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +030088 return (status and
89 'executable' in (queued or {}) and
90 'number' in (queued['executable'] or {}))
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030091
92 helpers.wait(
93 is_blocked,
94 timeout=timeout,
95 interval=30,
96 timeout_msg='Timeout waiting to run the job [{}]'.format(name))
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030097 build_id = self.__client.get_queue_item(num)['executable']['number']
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020098 return name, build_id
99
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300100 def wait_end_of_build(self, name, build_id, timeout=600, interval=5,
101 verbose=False, job_output_prefix=''):
102 '''Wait until the specified build is finished
103
104 :param name: ``str``, job name
105 :param build_id: ``int``, build id
106 :param timeout: ``int``, timeout waiting the job, sec
107 :param interval: ``int``, interval of polling the job result, sec
108 :param verbose: ``bool``, print the job console updates during waiting
109 :param job_output_prefix: ``str``, print the prefix for each console
110 output line, with the pre-defined
111 substitution keys:
112 - '{name}' : the current job name
113 - '{build_id}' : the current build-id
114 - '{time}' : the current time
115 :returns: requests object with headers and console output, ``obj``
116 '''
Dennis Dmitriev61115112018-05-31 06:48:43 +0300117 start = [0]
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300118 time_str = time.strftime("%H:%M:%S")
119 prefix = "\n" + job_output_prefix.format(job_name=name,
120 build_number=build_id,
121 time=time_str)
122 if verbose:
123 print(prefix, end='')
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200124
125 def building():
Dennis Dmitriev61115112018-05-31 06:48:43 +0300126 status = not self.build_info(name, build_id)['building']
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300127 if verbose:
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300128 time_str = time.strftime("%H:%M:%S")
129 prefix = "\n" + job_output_prefix.format(
130 job_name=name, build_number=build_id, time=time_str)
Dennis Dmitriev61115112018-05-31 06:48:43 +0300131 res = self.get_progressive_build_output(name,
132 build_id,
133 start=start[0])
134 if 'X-Text-Size' in res.headers:
135 text_size = int(res.headers['X-Text-Size'])
136 if start[0] < text_size:
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300137 text = res.content.decode('utf-8',
138 errors='backslashreplace')
139 print(text.replace("\n", prefix), end='')
Dennis Dmitriev61115112018-05-31 06:48:43 +0300140 start[0] = text_size
141 return status
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200142
143 helpers.wait(
144 building,
145 timeout=timeout,
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300146 interval=interval,
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200147 timeout_msg='Timeout waiting, job {0} are not finished "{1}" build'
148 ' still'.format(name, build_id))
Dmitry Tyzhnenkob610afd2018-02-19 15:43:45 +0200149
150 def get_build_output(self, name, build_id):
151 return self.__client.get_build_console_output(name, build_id)
Dennis Dmitriev61115112018-05-31 06:48:43 +0300152
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300153 def get_progressive_build_output(self, name, build_id, start=0):
Dennis Dmitriev61115112018-05-31 06:48:43 +0300154 '''Get build console text.
155
156 :param name: Job name, ``str``
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300157 :param build_id: Build id, ``int``
158 :param start: Start offset, ``int``
Dennis Dmitriev61115112018-05-31 06:48:43 +0300159 :returns: requests object with headers and console output, ``obj``
160 '''
161 folder_url, short_name = self.__client._get_job_folder(name)
162
163 PROGRESSIVE_CONSOLE_OUTPUT = (
164 '%(folder_url)sjob/%(short_name)s/%(build_id)d/'
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300165 'logText/progressiveText?start=%(start)d')
166 req = requests.Request(
167 'GET',
168 self.__client._build_url(PROGRESSIVE_CONSOLE_OUTPUT, locals()))
169 return(self.__client.jenkins_request(req))