11
from httplib import ACCEPTED, HTTPConnection, HTTPException, OK, CREATED
12
from urllib import urlencode
13
from urlparse import urlparse
15
log = logging.getLogger()
19
def __init__(self, host=None, port=None, user=None, key=None, prefix=None):
21
host = os.environ.get('DASHBOARD_HOST', None)
23
port = int(os.environ.get('DASHBOARD_PORT', '80'))
25
user = os.environ.get('DASHBOARD_USER', None)
27
key = os.environ.get('DASHBOARD_KEY', None)
29
prefix = os.environ.get('DASHBOARD_PREFIX', None)
33
self.resource_base = prefix
38
'Content-Type': 'application/json',
39
'Authorization': 'ApiKey %s:%s' % (user, key)
41
# mod_wsgi will strip the Authorization header, but tastypie
42
# allows it as GET param also. More details for fixing apache:
43
# http://django-tastypie.rtfd.org/en/latest/authentication.html
44
self._auth_param = '?username=%s&api_key=%s' % (user, key)
48
return HTTPConnection(self.host, self.port)
51
def _http_get(self, resource):
54
# we just mock this for the case where the caller wants to
55
# use our API transparently enabled/disabled
58
if self.resource_base:
59
resource = self.resource_base + resource
61
logging.debug('doing get on: %s', resource)
62
headers = {'Content-Type': 'application/json'}
63
con.request('GET', resource, headers=headers)
64
resp = con.getresponse()
68
msg = resp.read().decode()
71
fmt = '%d error getting resource(%s): %s'
72
raise HTTPException(fmt % (resp.status, resource, msg))
73
data = json.loads(resp.read().decode())
74
if len(data['objects']) == 0:
75
raise HTTPException('resource not found: %s' % resource)
76
assert len(data['objects']) == 1
77
return data['objects'][0]['resource_uri']
79
def _http_post(self, resource, params):
81
if not con or not self._headers:
84
if self.resource_base:
85
resource = self.resource_base + resource
86
resource += self._auth_param
88
params = json.dumps(params)
89
log.debug('posting (%s): %s', resource, params)
90
con.request('POST', resource, params, self._headers)
91
resp = con.getresponse()
92
if resp.status != CREATED:
95
msg = str(resp.getheaders())
96
msg += resp.read().decode()
100
'%d creating resource(%s): %s' % (resp.status, resource, msg))
101
uri = resp.getheader('Location')
102
return urlparse(uri).path
104
def _http_patch(self, resource, params):
105
con = self._connect()
106
if not con or not self._headers:
109
resource += self._auth_param
111
con.request('PATCH', resource, json.dumps(params), self._headers)
112
resp = con.getresponse()
113
if resp.status != ACCEPTED:
116
msg = resp.getheaders()
120
'%d patching resource(%s): %s' % (resp.status, resource, msg))
124
def _uri_to_pk(resource_uri):
126
return resource_uri.split('/')[-2]
127
return None # we are mocked
129
def job_get(self, name):
130
resource = '/smokeng/api/v1/job/?' + urlencode({'name': name})
131
return self._http_get(resource)
133
def job_add(self, name):
134
resource = '/smokeng/api/v1/job/'
137
'url': 'http://jenkins.qa.ubuntu.com/job/' + name + '/'
139
return self._http_post(resource, params)
141
def build_add(self, job_name, job_number):
143
logging.debug('trying to find job: %s', job_name)
144
job = self.job_get(job_name)
145
except HTTPException:
146
job = self.job_add(job_name)
147
logging.info('job is: %s', job)
149
resource = '/smokeng/api/v1/build/'
151
'build_number': job_number,
153
'ran_at': datetime.datetime.now().isoformat(),
154
'build_description': 'inprogress',
156
return self._http_post(resource, params)
158
def _image_get(self, build_number, release, variant, arch, flavor):
159
resource = '/smokeng/api/v1/image/?'
160
resource += urlencode({
161
'build_number': build_number,
167
return self._http_get(resource)
169
def image_add(self, build_number, release, variant, arch, flavor):
171
img = self._image_get(build_number, release, variant, arch, flavor)
173
except HTTPException:
174
# image doesn't exist so go continue and create
177
resource = '/smokeng/api/v1/image/'
179
'build_number': build_number,
186
return self._http_post(resource, params)
187
except HTTPException:
188
# race situation. Both callers saw _image_get fail and tried to
189
# create. Only one of them can succeed, so the failed call should
190
# now safely be able to get the image created by the other
191
img = self._image_get(build_number, release, variant, arch, flavor)
194
def result_get(self, image, test):
195
# deal with getting resource uri's as parameters instead of id's
196
image = API._uri_to_pk(image)
198
resource = '/smokeng/api/v1/result/?'
199
resource += urlencode({
203
return self._http_get(resource)
205
def _result_status(self, image, build, test, status, results=None):
208
'ran_at': datetime.datetime.now().isoformat(),
210
'jenkins_build': build,
213
params['results'] = results
216
resource = self.result_get(image, test)
217
except HTTPException:
219
resource = '/smokeng/api/v1/result/'
220
params['image'] = image
221
params['name'] = test
224
return self._http_post(resource, params)
226
return self._http_patch(resource, params)
228
def result_queue(self, image, build, test):
229
return self._result_status(image, build, test, 0)
231
def result_running(self, image, build, test):
232
return self._result_status(image, build, test, 1)
234
def result_syncing(self, image, build, test, results):
235
return self._result_status(image, build, test, 2, results)
238
def _result_running(api, args):
239
return api.result_running(args.image, args.build, args.test)
242
def _result_syncing(api, args):
244
with open(args.results) as f:
245
results = yaml.safe_load(f.read())
246
return api.result_syncing(args.image, args.build, args.test, results)
249
def _set_args(parser, names, func):
251
parser.add_argument(n, required=True)
252
parser.set_defaults(func=func)
256
parser = argparse.ArgumentParser(
257
description='Interact with the CI dashboard API')
259
sub = parser.add_subparsers(title='Commands', metavar='')
261
args = ['--image', '--build', '--test']
262
p = sub.add_parser('result-running', help='Set a SmokeResult "Running".')
263
_set_args(p, args, _result_running)
265
p = sub.add_parser('result-syncing', help='Set a SmokeResult "Syncing".')
266
_set_args(p, args, _result_syncing)
267
p.add_argument('--results', required=True, help='UTAH yaml file')
274
'DASHBOARD_HOST', 'DASHBOARD_PORT', 'DASHBOARD_USER', 'DASHBOARD_KEY']
277
if r not in os.environ:
280
print('Missing the following environment variables:')
291
val = args.func(api, args)
294
log.debug('stripping api-key from response')
295
val = val.split('/?')[0] + '/'
297
except HTTPException as e:
298
print('ERROR: %s' % e)
303
if __name__ == '__main__':
304
args = _get_parser().parse_args()