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(
|
||||
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):
|
||||
self.executor_server.hold_jobs_in_build = False
|
||||
self.executor_server.release()
|
||||
|
@ -746,6 +750,44 @@ class TestWeb(BaseTestWeb):
|
|||
resp = self.get_url("api/tenant/non-tenant/status")
|
||||
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):
|
||||
"""test listing autoholds through zuul-web"""
|
||||
client = zuul.rpcclient.RPCClient('127.0.0.1',
|
||||
|
@ -761,7 +803,6 @@ class TestWeb(BaseTestWeb):
|
|||
|
||||
self.assertNotEqual([], autohold_requests)
|
||||
self.assertEqual(1, len(autohold_requests))
|
||||
# The single dict key should be a CSV string value
|
||||
ah_request = autohold_requests[0]
|
||||
|
||||
self.assertEqual('tenant-one', ah_request['tenant'])
|
||||
|
@ -784,7 +825,6 @@ class TestWeb(BaseTestWeb):
|
|||
|
||||
self.assertNotEqual([], autohold_requests)
|
||||
self.assertEqual(1, len(autohold_requests))
|
||||
# The single dict key should be a CSV string value
|
||||
ah_request = autohold_requests[0]
|
||||
|
||||
self.assertEqual('tenant-one', ah_request['tenant'])
|
||||
|
@ -1265,6 +1305,38 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
'pipeline': 'check'})
|
||||
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):
|
||||
"""Test that autohold can be set through the admin web interface"""
|
||||
args = {"reason": "some reason",
|
||||
|
@ -1305,6 +1377,46 @@ class TestTenantScopedWebApi(BaseTestWeb):
|
|||
self.assertEqual("some reason", request['reason'])
|
||||
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):
|
||||
"""Test that the admin web interface can enqueue a change"""
|
||||
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
|
||||
|
|
|
@ -422,7 +422,8 @@ class ZuulWebAPI(object):
|
|||
if (project is None or
|
||||
request['project'].endswith(project)):
|
||||
result.append(
|
||||
{'tenant': request['tenant'],
|
||||
{'id': request['id'],
|
||||
'tenant': request['tenant'],
|
||||
'project': request['project'],
|
||||
'job': request['job'],
|
||||
'ref_filter': request['ref_filter'],
|
||||
|
@ -432,6 +433,68 @@ class ZuulWebAPI(object):
|
|||
})
|
||||
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.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
def index(self):
|
||||
|
@ -1037,6 +1100,8 @@ class ZuulWeb(object):
|
|||
'api',
|
||||
'/api/tenant/{tenant}/project/{project:.*}/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',
|
||||
controller=api, action='autohold_list')
|
||||
route_map.connect('api', '/api/tenant/{tenant}/projects',
|
||||
|
|
Loading…
Reference in New Issue