Add autohold delete/info commands to web API
The autohold-delete and autohold-info CLI commands were added in earlier changes. This adds support for them to the web API. Change-Id: I2bfb3dbeb34221c978964afd63be536df9e11229
This commit is contained in:
parent
9f5743366d
commit
4931eed2b8
|
@ -89,6 +89,10 @@ class BaseTestWeb(ZuulTestCase):
|
||||||
return requests.post(
|
return requests.post(
|
||||||
urllib.parse.urljoin(self.base_url, url), *args, **kwargs)
|
urllib.parse.urljoin(self.base_url, url), *args, **kwargs)
|
||||||
|
|
||||||
|
def delete_url(self, url, *args, **kwargs):
|
||||||
|
return requests.delete(
|
||||||
|
urllib.parse.urljoin(self.base_url, url), *args, **kwargs)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.executor_server.hold_jobs_in_build = False
|
self.executor_server.hold_jobs_in_build = False
|
||||||
self.executor_server.release()
|
self.executor_server.release()
|
||||||
|
@ -746,6 +750,44 @@ class TestWeb(BaseTestWeb):
|
||||||
resp = self.get_url("api/tenant/non-tenant/status")
|
resp = self.get_url("api/tenant/non-tenant/status")
|
||||||
self.assertEqual(404, resp.status_code)
|
self.assertEqual(404, resp.status_code)
|
||||||
|
|
||||||
|
def test_autohold_info_404_on_invalid_id(self):
|
||||||
|
resp = self.get_url("api/tenant/tenant-one/autohold/12345")
|
||||||
|
self.assertEqual(404, resp.status_code)
|
||||||
|
|
||||||
|
def test_autohold_delete_404_on_invalid_id(self):
|
||||||
|
resp = self.delete_url("api/tenant/tenant-one/autohold/12345")
|
||||||
|
self.assertEqual(404, resp.status_code)
|
||||||
|
|
||||||
|
def test_autohold_info(self):
|
||||||
|
client = zuul.rpcclient.RPCClient('127.0.0.1',
|
||||||
|
self.gearman_server.port)
|
||||||
|
self.addCleanup(client.shutdown)
|
||||||
|
r = client.autohold('tenant-one', 'org/project', 'project-test2',
|
||||||
|
"", "", "reason text", 1)
|
||||||
|
self.assertTrue(r)
|
||||||
|
|
||||||
|
# Use autohold-list API to retrieve request ID
|
||||||
|
resp = self.get_url(
|
||||||
|
"api/tenant/tenant-one/autohold")
|
||||||
|
self.assertEqual(200, resp.status_code, resp.text)
|
||||||
|
autohold_requests = resp.json()
|
||||||
|
self.assertNotEqual([], autohold_requests)
|
||||||
|
self.assertEqual(1, len(autohold_requests))
|
||||||
|
request_id = autohold_requests[0]['id']
|
||||||
|
|
||||||
|
# Now try the autohold-info API
|
||||||
|
resp = self.get_url("api/tenant/tenant-one/autohold/%s" % request_id)
|
||||||
|
self.assertEqual(200, resp.status_code, resp.text)
|
||||||
|
request = resp.json()
|
||||||
|
|
||||||
|
self.assertEqual(request_id, request['id'])
|
||||||
|
self.assertEqual('tenant-one', request['tenant'])
|
||||||
|
self.assertIn('org/project', request['project'])
|
||||||
|
self.assertEqual('project-test2', request['job'])
|
||||||
|
self.assertEqual(".*", request['ref_filter'])
|
||||||
|
self.assertEqual(1, request['count'])
|
||||||
|
self.assertEqual("reason text", request['reason'])
|
||||||
|
|
||||||
def test_autohold_list(self):
|
def test_autohold_list(self):
|
||||||
"""test listing autoholds through zuul-web"""
|
"""test listing autoholds through zuul-web"""
|
||||||
client = zuul.rpcclient.RPCClient('127.0.0.1',
|
client = zuul.rpcclient.RPCClient('127.0.0.1',
|
||||||
|
@ -761,7 +803,6 @@ class TestWeb(BaseTestWeb):
|
||||||
|
|
||||||
self.assertNotEqual([], autohold_requests)
|
self.assertNotEqual([], autohold_requests)
|
||||||
self.assertEqual(1, len(autohold_requests))
|
self.assertEqual(1, len(autohold_requests))
|
||||||
# The single dict key should be a CSV string value
|
|
||||||
ah_request = autohold_requests[0]
|
ah_request = autohold_requests[0]
|
||||||
|
|
||||||
self.assertEqual('tenant-one', ah_request['tenant'])
|
self.assertEqual('tenant-one', ah_request['tenant'])
|
||||||
|
@ -784,7 +825,6 @@ class TestWeb(BaseTestWeb):
|
||||||
|
|
||||||
self.assertNotEqual([], autohold_requests)
|
self.assertNotEqual([], autohold_requests)
|
||||||
self.assertEqual(1, len(autohold_requests))
|
self.assertEqual(1, len(autohold_requests))
|
||||||
# The single dict key should be a CSV string value
|
|
||||||
ah_request = autohold_requests[0]
|
ah_request = autohold_requests[0]
|
||||||
|
|
||||||
self.assertEqual('tenant-one', ah_request['tenant'])
|
self.assertEqual('tenant-one', ah_request['tenant'])
|
||||||
|
@ -1265,6 +1305,38 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
||||||
'pipeline': 'check'})
|
'pipeline': 'check'})
|
||||||
self.assertEqual(403, resp.status_code)
|
self.assertEqual(403, resp.status_code)
|
||||||
|
|
||||||
|
# For autohold-delete, we first must make sure that an autohold
|
||||||
|
# exists before the delete attempt.
|
||||||
|
good_authz = {'iss': 'zuul_operator',
|
||||||
|
'aud': 'zuul.example.com',
|
||||||
|
'sub': 'testuser',
|
||||||
|
'zuul': {'admin': ['tenant-one', ]},
|
||||||
|
'exp': time.time() + 3600}
|
||||||
|
args = {"reason": "some reason",
|
||||||
|
"count": 1,
|
||||||
|
'job': 'project-test2',
|
||||||
|
'change': None,
|
||||||
|
'ref': None,
|
||||||
|
'node_hold_expiration': None}
|
||||||
|
good_token = jwt.encode(good_authz, key='NoDanaOnlyZuul',
|
||||||
|
algorithm='HS256').decode('utf-8')
|
||||||
|
req = self.post_url(
|
||||||
|
'api/tenant/tenant-one/project/org/project/autohold',
|
||||||
|
headers={'Authorization': 'Bearer %s' % good_token},
|
||||||
|
json=args)
|
||||||
|
self.assertEqual(200, req.status_code, req.text)
|
||||||
|
client = zuul.rpcclient.RPCClient('127.0.0.1',
|
||||||
|
self.gearman_server.port)
|
||||||
|
self.addCleanup(client.shutdown)
|
||||||
|
autohold_requests = client.autohold_list()
|
||||||
|
self.assertNotEqual([], autohold_requests)
|
||||||
|
self.assertEqual(1, len(autohold_requests))
|
||||||
|
request = autohold_requests[0]
|
||||||
|
resp = self.delete_url(
|
||||||
|
"api/tenant/tenant-one/autohold/%s" % request['id'],
|
||||||
|
headers={'Authorization': 'Bearer %s' % token})
|
||||||
|
self.assertEqual(403, resp.status_code)
|
||||||
|
|
||||||
def test_autohold(self):
|
def test_autohold(self):
|
||||||
"""Test that autohold can be set through the admin web interface"""
|
"""Test that autohold can be set through the admin web interface"""
|
||||||
args = {"reason": "some reason",
|
args = {"reason": "some reason",
|
||||||
|
@ -1305,6 +1377,46 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
||||||
self.assertEqual("some reason", request['reason'])
|
self.assertEqual("some reason", request['reason'])
|
||||||
self.assertEqual(1, request['max_count'])
|
self.assertEqual(1, request['max_count'])
|
||||||
|
|
||||||
|
def test_autohold_delete(self):
|
||||||
|
authz = {'iss': 'zuul_operator',
|
||||||
|
'aud': 'zuul.example.com',
|
||||||
|
'sub': 'testuser',
|
||||||
|
'zuul': {
|
||||||
|
'admin': ['tenant-one', ]
|
||||||
|
},
|
||||||
|
'exp': time.time() + 3600}
|
||||||
|
token = jwt.encode(authz, key='NoDanaOnlyZuul',
|
||||||
|
algorithm='HS256').decode('utf-8')
|
||||||
|
|
||||||
|
client = zuul.rpcclient.RPCClient('127.0.0.1',
|
||||||
|
self.gearman_server.port)
|
||||||
|
self.addCleanup(client.shutdown)
|
||||||
|
r = client.autohold('tenant-one', 'org/project', 'project-test2',
|
||||||
|
"", "", "reason text", 1)
|
||||||
|
self.assertTrue(r)
|
||||||
|
|
||||||
|
# Use autohold-list API to retrieve request ID
|
||||||
|
resp = self.get_url(
|
||||||
|
"api/tenant/tenant-one/autohold")
|
||||||
|
self.assertEqual(200, resp.status_code, resp.text)
|
||||||
|
autohold_requests = resp.json()
|
||||||
|
self.assertNotEqual([], autohold_requests)
|
||||||
|
self.assertEqual(1, len(autohold_requests))
|
||||||
|
request_id = autohold_requests[0]['id']
|
||||||
|
|
||||||
|
# now try the autohold-delete API
|
||||||
|
resp = self.delete_url(
|
||||||
|
"api/tenant/tenant-one/autohold/%s" % request_id,
|
||||||
|
headers={'Authorization': 'Bearer %s' % token})
|
||||||
|
self.assertEqual(204, resp.status_code, resp.text)
|
||||||
|
|
||||||
|
# autohold-list should be empty now
|
||||||
|
resp = self.get_url(
|
||||||
|
"api/tenant/tenant-one/autohold")
|
||||||
|
self.assertEqual(200, resp.status_code, resp.text)
|
||||||
|
autohold_requests = resp.json()
|
||||||
|
self.assertEqual([], autohold_requests)
|
||||||
|
|
||||||
def test_enqueue(self):
|
def test_enqueue(self):
|
||||||
"""Test that the admin web interface can enqueue a change"""
|
"""Test that the admin web interface can enqueue a change"""
|
||||||
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
|
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
|
||||||
|
|
|
@ -422,7 +422,8 @@ class ZuulWebAPI(object):
|
||||||
if (project is None or
|
if (project is None or
|
||||||
request['project'].endswith(project)):
|
request['project'].endswith(project)):
|
||||||
result.append(
|
result.append(
|
||||||
{'tenant': request['tenant'],
|
{'id': request['id'],
|
||||||
|
'tenant': request['tenant'],
|
||||||
'project': request['project'],
|
'project': request['project'],
|
||||||
'job': request['job'],
|
'job': request['job'],
|
||||||
'ref_filter': request['ref_filter'],
|
'ref_filter': request['ref_filter'],
|
||||||
|
@ -432,6 +433,68 @@ class ZuulWebAPI(object):
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||||
|
def autohold_by_request_id(self, tenant, request_id):
|
||||||
|
if cherrypy.request.method == 'GET':
|
||||||
|
return self._autohold_info(request_id)
|
||||||
|
elif cherrypy.request.method == 'DELETE':
|
||||||
|
return self._autohold_delete(request_id)
|
||||||
|
else:
|
||||||
|
raise cherrypy.HTTPError(405)
|
||||||
|
|
||||||
|
def _autohold_info(self, request_id):
|
||||||
|
job = self.rpc.submitJob('zuul:autohold_info',
|
||||||
|
{'request_id': request_id})
|
||||||
|
if job.failure:
|
||||||
|
raise cherrypy.HTTPError(500, 'autohold-info failed')
|
||||||
|
else:
|
||||||
|
request = json.loads(job.data[0])
|
||||||
|
if not request:
|
||||||
|
raise cherrypy.HTTPError(
|
||||||
|
404, 'Hold request %s does not exist.' % request_id)
|
||||||
|
return {
|
||||||
|
'id': request['id'],
|
||||||
|
'tenant': request['tenant'],
|
||||||
|
'project': request['project'],
|
||||||
|
'job': request['job'],
|
||||||
|
'ref_filter': request['ref_filter'],
|
||||||
|
'count': request['max_count'],
|
||||||
|
'reason': request['reason'],
|
||||||
|
'node_hold_expiration': request['node_expiration']
|
||||||
|
}
|
||||||
|
|
||||||
|
def _autohold_delete(self, request_id):
|
||||||
|
# We need tenant info from the request for authz
|
||||||
|
request = self._autohold_info(request_id)
|
||||||
|
|
||||||
|
basic_error = self._basic_auth_header_check()
|
||||||
|
if basic_error is not None:
|
||||||
|
return basic_error
|
||||||
|
# AuthN/AuthZ
|
||||||
|
rawToken = cherrypy.request.headers['Authorization'][len('Bearer '):]
|
||||||
|
try:
|
||||||
|
claims = self.zuulweb.authenticators.authenticate(rawToken)
|
||||||
|
except exceptions.AuthTokenException as e:
|
||||||
|
for header, contents in e.getAdditionalHeaders().items():
|
||||||
|
cherrypy.response.headers[header] = contents
|
||||||
|
cherrypy.response.status = e.HTTPError
|
||||||
|
return {'description': e.error_description,
|
||||||
|
'error': e.error,
|
||||||
|
'realm': e.realm}
|
||||||
|
self.is_authorized(claims, request['tenant'])
|
||||||
|
msg = 'User "%s" requesting "%s" on %s/%s'
|
||||||
|
self.log.info(
|
||||||
|
msg % (claims['__zuul_uid_claim'], 'autohold-delete',
|
||||||
|
request['tenant'], request['project']))
|
||||||
|
|
||||||
|
job = self.rpc.submitJob('zuul:autohold_delete',
|
||||||
|
{'request_id': request_id})
|
||||||
|
if job.failure:
|
||||||
|
raise cherrypy.HTTPError(500, 'autohold-delete failed')
|
||||||
|
|
||||||
|
cherrypy.response.status = 204
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||||
def index(self):
|
def index(self):
|
||||||
|
@ -1037,6 +1100,8 @@ class ZuulWeb(object):
|
||||||
'api',
|
'api',
|
||||||
'/api/tenant/{tenant}/project/{project:.*}/dequeue',
|
'/api/tenant/{tenant}/project/{project:.*}/dequeue',
|
||||||
controller=api, action='dequeue')
|
controller=api, action='dequeue')
|
||||||
|
route_map.connect('api', '/api/tenant/{tenant}/autohold/{request_id}',
|
||||||
|
controller=api, action='autohold_by_request_id')
|
||||||
route_map.connect('api', '/api/tenant/{tenant}/autohold',
|
route_map.connect('api', '/api/tenant/{tenant}/autohold',
|
||||||
controller=api, action='autohold_list')
|
controller=api, action='autohold_list')
|
||||||
route_map.connect('api', '/api/tenant/{tenant}/projects',
|
route_map.connect('api', '/api/tenant/{tenant}/projects',
|
||||||
|
|
Loading…
Reference in New Issue