-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathapi.py
More file actions
456 lines (383 loc) · 15.3 KB
/
api.py
File metadata and controls
456 lines (383 loc) · 15.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
"""
This module contains the BambooAPIClient, used for communicating with the
Bamboo server web service API.
"""
from bs4 import BeautifulSoup
import os
import requests
class BambooAPIClient(object):
"""
Adapter for Bamboo's web service API.
"""
# Default host is local
DEFAULT_HOST = 'http://localhost'
DEFAULT_PORT = 8085
# Endpoints
BUILD_SERVICE = '/rest/api/latest/result'
PROJECT_SERVICE = 'rest/api/latest/project'
DEPLOY_SERVICE = '/rest/api/latest/deploy/project'
ENVIRONMENT_SERVICE = '/rest/api/latest/deploy/environment/{env_id}/results'
PLAN_SERVICE = '/rest/api/latest/plan'
QUEUE_SERVICE = '/rest/api/latest/queue'
RESULT_SERVICE = '/rest/api/latest/result'
SERVER_SERVICE = '/rest/api/latest/server'
BFL_ACTION = '/build/label/viewBuildsForLabel.action'
BRANCH_SERVICE = PLAN_SERVICE + '/{key}/branch'
BRANCH_RESULT_SERVICE = RESULT_SERVICE + '/{key}/branch/{branch_name}'
DELETE_ACTION = '/chain/admin/deleteChain!doDelete.action'
def __init__(self, host=None, port=None, user=None, password=None, prefix=None):
"""
Set connection and auth information (if user+password were provided).
"""
self._host = host or self.DEFAULT_HOST
self._port = port or self.DEFAULT_PORT
self._prefix = prefix or ''
self._session = requests.Session()
if user and password:
self._session.auth = (user, password)
def _get_response(self, url, params=None):
"""
Make the call to the service with the given queryset and whatever params
were set initially (auth).
"""
res = self._session.get(url, params=params or {}, headers={'Accept': 'application/json'})
if res.status_code != 200:
raise Exception(f'failed to get {url} with params={params}: status={res.status_code} reason="{res.reason}"')
return res
def _post_response(self, url, params=None, data=None):
"""
Post to the service with the given queryset and whatever params
were set initially (auth).
"""
res = self._session.post(url, params=params or {}, headers={'Accept': 'application/json'}, data=data or {})
if res.status_code != 200:
raise Exception(res.reason)
return res
def _put_response(self, url, params=None):
"""
Put to the service with the given queryset and whatever params
were set initially (auth).
"""
res = self._session.put(url, params=params or {}, headers={'Accept': 'application/json'})
if res.status_code != 200:
raise Exception(res.reason)
return res
def _get_url(self, endpoint):
"""
Get full url string for host, port and given endpoint.
:param endpoint: path to service endpoint
:return: full url to make request
"""
return '{}:{}{}{}'.format(self._host, self._port, self._prefix, endpoint)
def _build_expand(self, expand):
valid_expands = set(['artifacts',
'comments',
'labels',
'jiraIssues',
'stages',
'stages.stage',
'stages.stage.results',
'stages.stage.results.result'])
expands = map(lambda x: '.'.join(['results.result', x]),
set(expand) & valid_expands)
return ','.join(expands)
def get_builds_by_label(self, labels=None):
"""
Get the master/branch builds in the Bamboo server via viewBuildsForLabel.action
- No REST API for this: https://jira.atlassian.com/browse/BAM-18428
- Scrape https://bamboo/build/label/viewBuildsForLabel.action?pageIndex=2&pageSize=50&labelName=foo
Simple response API dict projectKey, planKey and buildKey
:param labels: [str]
:return: Generator
"""
# Until BAM-18428, call the UI
url = self._get_url(self.BFL_ACTION)
qs = {}
# Cannot search multiple labels in a single shot,
# so iterate search - caller should de-dupe.
for label in labels:
qs['labelName'] = label
# Cycle through paged results
page_index = 1
while 1:
qs['pageIndex'] = page_index
response = self._get_response(url, qs)
# Build links are clustered in three inside a td containing a
# span with build indicator icons.
soup = BeautifulSoup(response.text, 'html.parser')
for span in soup.find_all('span', {'class': ['aui-icon', 'aui-icon-small']}):
cell = span.find_parent('td')
if cell is not None and len(cell):
prj, plan, build = cell.find_all('a')[:3]
yield {'projectKey': os.path.basename(prj['href']),
'planKey': os.path.basename(plan['href']),
'buildKey': os.path.basename(build['href'])}
# XXX rather than deconstruct the href, we advance our own
# qs{pageIndex} until there are no more nextLinks
page_index += 1
nl = soup.find('a', {'class': ['nextLink']})
if nl is None:
break
def get_builds(self, plan_key=None, labels=None, expand=None, max_result=25):
"""
Get the builds in the Bamboo server.
:param plan_key: str
:param labels: list str
:param expand: list str
:return: Generator
"""
# Build starting qs params
qs = {'max-result': max_result, 'start-index': 0}
if expand:
qs['expand'] = self._build_expand(expand)
if labels:
qs['label'] = ','.join(labels)
# Get url
if plan_key:
# All builds for one plan
url = '{}/{}'.format(self._get_url(self.BUILD_SERVICE), plan_key)
else:
# Latest build for all plans
url = self._get_url(self.BUILD_SERVICE)
# Cycle through paged results
size = 1
while size:
# Get page, update page and size
response = self._get_response(url, qs).json()
results = response['results']
size = results['size']
# Check if start index was reset
# Note: see https://github.com/liocuevas/python-bamboo-api/issues/6
if results['start-index'] < qs['start-index']:
# Not the page we wanted, abort
break
# Yield results
for r in results['result']:
yield r
# Update paging info
# Note: do this here to keep it current with yields
qs['start-index'] += size
def get_deployments(self, project_key=None):
"""
Returns the list of deployment projects set up on the Bamboo server.
:param project_key: str
:return: Generator
"""
url = "{}/{}".format(self._get_url(self.DEPLOY_SERVICE), project_key or 'all')
response = self._get_response(url).json()
for r in response:
yield r
def get_environment_results(self, environment_id, max_result=25):
"""
Returns the list of environment results.
:param environment_id: int
:return: Generator
"""
# Build starting qs params
qs = {'max-result': max_result, 'start-index': 0}
# Get url for results
url = self._get_url(self.ENVIRONMENT_SERVICE.format(env_id=environment_id))
# Cycle through paged results
size = 1
while qs['start-index'] < size:
# Get page, update page size and yield results
response = self._get_response(url, qs).json()
size = response['size']
for r in response['results']:
yield r
# Update paging info
# Note: do this here to keep it current with yields
qs['start-index'] += response['max-result']
def get_plans(self, expand=None, max_result=25):
"""
Return all the plans in a Bamboo server.
:return: generator of plans
"""
# Build starting qs params
qs = {'max-result': max_result, 'start-index': 0}
if expand:
qs['expand'] = self._build_expand(expand)
# Get url for results
url = self._get_url(self.PLAN_SERVICE)
# Cycle through paged results
size = 1
while qs['start-index'] < size:
# Get page, update page size and yield plans
response = self._get_response(url, qs).json()
plans = response['plans']
size = plans['size']
for r in plans['plan']:
yield r
# Update paging info
# Note: do this here to keep it current with yields
qs['start-index'] += plans['max-result']
def get_branches(self, plan_key, enabled_only=False, max_result=25):
"""
Return all branches in a plan.
:param plan_key: str
:param enabled_only: bool
:return: Generator
"""
# Build qs params
qs = {'max-result': max_result, 'start-index': 0}
if enabled_only:
qs['enabledOnly'] = 'true'
# Get url for results
url = self._get_url(self.BRANCH_SERVICE.format(key=plan_key))
# Cycle through paged results
size = 1
while qs['start-index'] < size:
# Get page, update page size and yield branches
response = self._get_response(url, qs).json()
branches = response['branches']
size = branches['size']
for r in branches['branch']:
yield r
# Update paging info
# Note: do this here to keep it current with yields
qs['start-index'] += branches['max-result']
def delete_plan(self, build_key):
"""
Delete a plan or plan branch with its key.
:param build_key: str
:return: dict Response
"""
# Build qs params
# qs = {}
# Get url
url = self._get_url(self.DELETE_ACTION)
# Build Data Object
data = {'buildKey': build_key}
r = self._post_response(url, data=data)
r.raise_for_status()
def queue_build(self, plan_key, build_vars={}):
"""
Queue a build for building
:param plan_key: str
:param build_vars: dict
"""
url = "{}/{}".format(self._get_url(self.QUEUE_SERVICE), plan_key)
# Custom builds
qs = {}
for k, v in build_vars.items():
qs_k = 'bamboo.variable.{}'.format(k)
qs[qs_k] = v
return self._post_response(url, qs).json()
def continue_build(self, plan_key, build_number, stage=None, executeAllStages=False, build_vars={}):
"""
Queue a build for continuation
:param plan_key: str
:param build_vars: dict
:param build_number: int
:param stage: str
"""
url = "{}/{}-{}".format(self._get_url(self.QUEUE_SERVICE), plan_key, build_number)
# Custom builds
qs = {}
if executeAllStages:
qs['executeAllStages']='true'
if stage:
qs['stage']=stage
for k, v in build_vars.items():
qs_k = 'bamboo.variable.{}'.format(k)
qs[qs_k] = v
return self._put_response(url, qs).json()
def get_build_queue(self):
"""
List all builds currently in the Queue
"""
url = "{}".format(self._get_url(self.QUEUE_SERVICE))
return self._get_response(url).json()
def get_results(self, plan_key=None, build_number=None, expand=None, max_result=25):
"""
Returns a list of results for builds
:param plan_key: str
:return: Generator
"""
# Build qs params
qs = {'max-result': max_result, 'start-index': 0}
if expand:
qs['expand'] = self._build_expand(expand)
if build_number is not None and plan_key is not None:
plan_key = plan_key + '-' + build_number
url = "{}/{}".format(self._get_url(self.RESULT_SERVICE), plan_key or 'all')
# Cycle through paged results
size = 1
while qs['start-index'] < size:
# Get page, update page size and yield branches
response = self._get_response(url, qs).json()
results = response['results']
size = results['size']
for r in results['result']:
yield r
# Update paging info
# Note: do this here to keep it current with yields
qs['start-index'] += results['max-result']
def get_branch_results(self, plan_key, branch_name=None, expand=None, favorite=False,
labels=None, issue_keys=None, include_all_states=False,
continuable=False, build_state=None, max_result=25):
"""
Returns a list of results for plan branch builds
:param plan_key: str
:param branch_name: str
:param expand: list str
:param favorite: bool
:param labels: list
:param issue_keys: list
:param include_all_states: bool
:param continuable: bool
:param build_state: str
:return: Generator
"""
# Build qs params
qs = {'max-result': max_result, 'start-index': 0}
if expand:
qs['expand'] = self._build_expand(expand)
if favorite:
qs['favorite'] = True
if labels:
qs['label'] = ','.join(labels)
if issue_keys:
qs['issueKey'] = ','.join(issue_keys)
if include_all_states:
qs['includeAllStates'] = True
if continuable:
qs['continuable'] = True
if build_state:
valid_build_states = ('Successful', 'Failed', 'Unknown')
if build_state not in valid_build_states:
raise ValueError('Incorrect value for \'build_state\'. Valid values include: %s', ','.join(valid_build_states))
qs['build_state'] = build_state
# Get url for results
url = self._get_url(self.BRANCH_RESULT_SERVICE.format(key=plan_key, branch_name=branch_name))
# Cycle through paged results
size = 1
while qs['start-index'] < size:
# Get page, update page size and yield branches
response = self._get_response(url, qs).json()
results = response['results']
size = results['size']
for r in results['result']:
yield r
# Update paging info
# Note: do this here to keep it current with yields
qs['start-index'] += results['max-result']
def get_projects(self):
"""
List all projects
"""
url = "{}".format(self._get_url(self.PROJECT_SERVICE))
response = self._get_response(url).json()
return response
def pause(self):
"""
Pause server
"""
url = "{}/{}".format(self._get_url(self.SERVER_SERVICE), "pause")
return self._post_response(url).json()
def resume(self):
"""
Resume server
"""
url = "{}/{}".format(self._get_url(self.SERVER_SERVICE), "resume")
return self._post_response(url).json()