blob: a7c2315972270e7d19a2d3131138c7e32703730e [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
14class JenkinsClient(object):
15
Dennis Dmitriev27a96792018-07-30 07:52:03 +030016 def __init__(self, host=None, username='admin', password='r00tme'):
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020017 host = host or 'http://172.16.44.33:8081'
obutenkoca858402019-07-04 18:31:39 +030018 self.__client = jenkins.Jenkins(
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020019 host,
20 username=username,
21 password=password)
obutenkoca858402019-07-04 18:31:39 +030022 self.__client._session.verify = False
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020023
24 def jobs(self):
25 return self.__client.get_jobs()
26
27 def find_jobs(self, name):
28 return filter(lambda x: name in x['fullname'], self.jobs())
29
30 def job_info(self, name):
31 return self.__client.get_job_info(name)
32
33 def list_builds(self, name):
34 return self.job_info(name).get('builds')
35
36 def build_info(self, name, build_id):
37 return self.__client.get_build_info(name, build_id)
38
39 def job_params(self, name):
40 job = self.job_info(name)
41 job_params = next(
42 p for p in job['property'] if
43 'hudson.model.ParametersDefinitionProperty' == p['_class'])
44 job_params = job_params['parameterDefinitions']
45 return job_params
46
47 def make_defults_params(self, name):
48 job_params = self.job_params(name)
49 def_params = dict(
50 [(j['name'], j['defaultParameterValue']['value'])
51 for j in job_params])
52 return def_params
53
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030054 def run_build(self, name, params=None, timeout=600, verbose=False):
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +020055 params = params or self.make_defults_params(name)
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030056 num = self.__client.build_job(name, params)
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +030057 time.sleep(2) # wait while job is started
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030058
Dmitry Tyzhnenkob39de052019-03-21 17:05:07 +020059 def is_build_queued():
60 try:
61 item = self.__client.get_queue_item(num)
62 ts = item['inQueueSince'] / 1000
63 since_time = datetime.datetime.fromtimestamp(ts)
64 print("Build in the queue since {}".format(since_time))
65 return True
66 except jenkins.JenkinsException:
67 if verbose:
68 print("Build have not been queued {} yet".format(num))
69
70 helpers.wait(
71 is_build_queued,
72 timeout=timeout,
73 interval=30,
74 timeout_msg='Timeout waiting to queue the build '
75 'for {} job'.format(name))
76
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030077 def is_blocked():
78 queued = self.__client.get_queue_item(num)
79 status = not queued['blocked']
80 if not status and verbose:
81 print("pending the job [{}] : {}".format(name, queued['why']))
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +030082 return (status and
83 'executable' in (queued or {}) and
84 'number' in (queued['executable'] or {}))
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030085
86 helpers.wait(
87 is_blocked,
88 timeout=timeout,
89 interval=30,
90 timeout_msg='Timeout waiting to run the job [{}]'.format(name))
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +030091 build_id = self.__client.get_queue_item(num)['executable']['number']
Dmitry Tyzhnenkob39de052019-03-21 17:05:07 +020092
93 def is_build_started():
94 try:
95 build = self.__client.get_build_info(name, build_id)
96 ts = float(build['timestamp']) / 1000
97 start_time = datetime.datetime.fromtimestamp(ts)
98 print("the build {} in {} have started at {} UTC".format(
99 build_id, name, start_time))
100 return True
101 except jenkins.JenkinsException:
102 if verbose:
103 print("the build {} in {} have not strated yet".format(
104 build_id, name))
105 helpers.wait(
106 is_build_started,
107 timeout=timeout,
108 interval=30,
109 timeout_msg='Timeout waiting to run build of '
110 'the job [{}]'.format(name))
111
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200112 return name, build_id
113
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300114 def wait_end_of_build(self, name, build_id, timeout=600, interval=5,
115 verbose=False, job_output_prefix=''):
116 '''Wait until the specified build is finished
117
118 :param name: ``str``, job name
119 :param build_id: ``int``, build id
120 :param timeout: ``int``, timeout waiting the job, sec
121 :param interval: ``int``, interval of polling the job result, sec
122 :param verbose: ``bool``, print the job console updates during waiting
123 :param job_output_prefix: ``str``, print the prefix for each console
124 output line, with the pre-defined
125 substitution keys:
126 - '{name}' : the current job name
127 - '{build_id}' : the current build-id
128 - '{time}' : the current time
129 :returns: requests object with headers and console output, ``obj``
130 '''
Dennis Dmitriev61115112018-05-31 06:48:43 +0300131 start = [0]
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300132 time_str = time.strftime("%H:%M:%S")
133 prefix = "\n" + job_output_prefix.format(job_name=name,
134 build_number=build_id,
135 time=time_str)
136 if verbose:
137 print(prefix, end='')
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200138
139 def building():
Dmitry Tyzhnenko80ce0202019-02-07 13:27:19 +0200140 try:
141 status = not self.build_info(name, build_id)['building']
142 except ConnectionError:
143 status = False
144
Dennis Dmitrieva5bd1652018-05-31 20:57:19 +0300145 if verbose:
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300146 time_str = time.strftime("%H:%M:%S")
147 prefix = "\n" + job_output_prefix.format(
148 job_name=name, build_number=build_id, time=time_str)
Dennis Dmitriev61115112018-05-31 06:48:43 +0300149 res = self.get_progressive_build_output(name,
150 build_id,
151 start=start[0])
152 if 'X-Text-Size' in res.headers:
153 text_size = int(res.headers['X-Text-Size'])
154 if start[0] < text_size:
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300155 text = res.content.decode('utf-8',
156 errors='backslashreplace')
157 print(text.replace("\n", prefix), end='')
Dennis Dmitriev61115112018-05-31 06:48:43 +0300158 start[0] = text_size
159 return status
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200160
161 helpers.wait(
162 building,
163 timeout=timeout,
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300164 interval=interval,
Dennis Dmitriev13e804b2018-10-09 19:25:14 +0300165 timeout_msg=('Timeout waiting the job {0}:{1} in {2} sec.'
166 .format(name, build_id, timeout)))
Dmitry Tyzhnenkob610afd2018-02-19 15:43:45 +0200167
168 def get_build_output(self, name, build_id):
169 return self.__client.get_build_console_output(name, build_id)
Dennis Dmitriev61115112018-05-31 06:48:43 +0300170
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300171 def get_progressive_build_output(self, name, build_id, start=0):
Dennis Dmitriev61115112018-05-31 06:48:43 +0300172 '''Get build console text.
173
174 :param name: Job name, ``str``
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300175 :param build_id: Build id, ``int``
176 :param start: Start offset, ``int``
Dennis Dmitriev61115112018-05-31 06:48:43 +0300177 :returns: requests object with headers and console output, ``obj``
178 '''
179 folder_url, short_name = self.__client._get_job_folder(name)
180
181 PROGRESSIVE_CONSOLE_OUTPUT = (
182 '%(folder_url)sjob/%(short_name)s/%(build_id)d/'
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300183 'logText/progressiveText?start=%(start)d')
184 req = requests.Request(
185 'GET',
186 self.__client._build_url(PROGRESSIVE_CONSOLE_OUTPUT, locals()))
187 return(self.__client.jenkins_request(req))
Dennis Dmitriev27a96792018-07-30 07:52:03 +0300188
189 def get_workflow(self, name, build_id, enode=None, mode='describe'):
190 '''Get workflow results from pipeline job
191
192 :param name: job name
193 :param build_id: str, build number or 'lastBuild'
194 :param enode: int, execution node in the workflow
195 :param mode: the stage or execution node description if 'describe',
196 the execution node log if 'log'
197 '''
198 folder_url, short_name = self.__client._get_job_folder(name)
199
200 if enode:
201 WORKFLOW_DESCRIPTION = (
202 '%(folder_url)sjob/%(short_name)s/%(build_id)s/'
203 'execution/node/%(enode)d/wfapi/%(mode)s')
204 else:
205 WORKFLOW_DESCRIPTION = (
206 '%(folder_url)sjob/%(short_name)s/%(build_id)s/wfapi/%(mode)s')
207 req = requests.Request(
208 'GET',
209 self.__client._build_url(WORKFLOW_DESCRIPTION, locals()))
210 response = self.__client.jenkins_open(req)
211 return json.loads(response)
Dennis Dmitriev8565c342019-02-11 23:45:03 +0200212
213 def get_artifact(self, name, build_id, artifact_path, destination_name):
214 '''Wait until the specified build is finished
215
216 :param name: ``str``, job name
217 :param build_id: ``str``, build id or "lastBuild"
218 :param artifact_path: ``str``, path and filename of the artifact
219 relative to the job URL
220 :param artifact_path: ``str``, destination path and filename
221 on the local filesystem where to save
222 the artifact content
223 :returns: requests object with headers and console output, ``obj``
224 '''
225 folder_url, short_name = self.__client._get_job_folder(name)
226
227 DOWNLOAD_URL = ('%(folder_url)sjob/%(short_name)s/%(build_id)s/'
228 'artifact/%(artifact_path)s')
229 req = requests.Request(
230 'GET',
231 self.__client._build_url(DOWNLOAD_URL, locals()))
232
233 response = self.__client.jenkins_request(req)
234 return response.content