Support promote via tenant scoped rest api
The tenant scoped rest api already supports enqueue, dequeue and some others. For project admins it's also useful to be able to use promote to push high priority changes to the front of the gate. Change-Id: I27e06ea2fc813c6f084fd01a2e9af284d3b15b89
This commit is contained in:
parent
4502f91807
commit
79889887bc
|
@ -8,9 +8,9 @@ Tenant Scoped REST API
|
|||
Users can perform some privileged actions at the tenant level through protected
|
||||
endpoints of the REST API, if these endpoints are activated.
|
||||
|
||||
The supported actions are **autohold**, **enqueue/enqueue-ref** and
|
||||
**dequeue/dequeue-ref**. These are similar to the ones available through Zuul's
|
||||
CLI.
|
||||
The supported actions are **autohold**, **enqueue/enqueue-ref**,
|
||||
**dequeue/dequeue-ref** and **promote**. These are similar to the ones available
|
||||
through Zuul's CLI.
|
||||
|
||||
The protected endpoints require a bearer token, passed to Zuul Web Server as the
|
||||
**Authorization** header of the request. The token and this workflow follow the
|
||||
|
|
|
@ -141,8 +141,6 @@ for these more advanced operations.
|
|||
Promote
|
||||
^^^^^^^
|
||||
|
||||
.. note:: This command is only available through a Gearman connection.
|
||||
|
||||
.. program-output:: zuul promote --help
|
||||
|
||||
Example::
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
The action "promote" is now available via the tenant-scoped REST API using
|
||||
the zuul cli.
|
|
@ -2220,3 +2220,89 @@ class TestCLIViaWebApi(BaseTestWeb):
|
|||
self.executor_server.release()
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(self.countJobResults(self.history, 'ABORTED'), 1)
|
||||
|
||||
def test_promote(self):
|
||||
"Test that the RPC client can promote a change"
|
||||
self.executor_server.hold_jobs_in_build = True
|
||||
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
|
||||
B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
|
||||
C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
|
||||
A.addApproval('Code-Review', 2)
|
||||
B.addApproval('Code-Review', 2)
|
||||
C.addApproval('Code-Review', 2)
|
||||
|
||||
self.fake_gerrit.addEvent(A.addApproval('Approved', 1))
|
||||
self.fake_gerrit.addEvent(B.addApproval('Approved', 1))
|
||||
self.fake_gerrit.addEvent(C.addApproval('Approved', 1))
|
||||
|
||||
self.waitUntilSettled()
|
||||
|
||||
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
|
||||
items = tenant.layout.pipelines['gate'].getAllItems()
|
||||
enqueue_times = {}
|
||||
for item in items:
|
||||
enqueue_times[str(item.change)] = item.enqueue_time
|
||||
|
||||
# Promote B and C using the cli
|
||||
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')
|
||||
p = subprocess.Popen(
|
||||
[os.path.join(sys.prefix, 'bin/zuul'),
|
||||
'--zuul-url', self.base_url, '--auth-token', token,
|
||||
'promote', '--tenant', 'tenant-one',
|
||||
'--pipeline', 'gate', '--changes', '2,1', '3,1'],
|
||||
stdout=subprocess.PIPE)
|
||||
output = p.communicate()
|
||||
self.assertEqual(p.returncode, 0, output[0])
|
||||
self.waitUntilSettled()
|
||||
|
||||
# ensure that enqueue times are durable
|
||||
items = tenant.layout.pipelines['gate'].getAllItems()
|
||||
for item in items:
|
||||
self.assertEqual(
|
||||
enqueue_times[str(item.change)], item.enqueue_time)
|
||||
|
||||
self.waitUntilSettled()
|
||||
self.executor_server.release('.*-merge')
|
||||
self.waitUntilSettled()
|
||||
self.executor_server.release('.*-merge')
|
||||
self.waitUntilSettled()
|
||||
self.executor_server.release('.*-merge')
|
||||
self.waitUntilSettled()
|
||||
|
||||
self.assertEqual(len(self.builds), 6)
|
||||
self.assertEqual(self.builds[0].name, 'project-test1')
|
||||
self.assertEqual(self.builds[1].name, 'project-test2')
|
||||
self.assertEqual(self.builds[2].name, 'project-test1')
|
||||
self.assertEqual(self.builds[3].name, 'project-test2')
|
||||
self.assertEqual(self.builds[4].name, 'project-test1')
|
||||
self.assertEqual(self.builds[5].name, 'project-test2')
|
||||
|
||||
self.assertTrue(self.builds[0].hasChanges(B))
|
||||
self.assertFalse(self.builds[0].hasChanges(A))
|
||||
self.assertFalse(self.builds[0].hasChanges(C))
|
||||
|
||||
self.assertTrue(self.builds[2].hasChanges(B))
|
||||
self.assertTrue(self.builds[2].hasChanges(C))
|
||||
self.assertFalse(self.builds[2].hasChanges(A))
|
||||
|
||||
self.assertTrue(self.builds[4].hasChanges(B))
|
||||
self.assertTrue(self.builds[4].hasChanges(C))
|
||||
self.assertTrue(self.builds[4].hasChanges(A))
|
||||
|
||||
self.executor_server.release()
|
||||
self.waitUntilSettled()
|
||||
|
||||
self.assertEqual(A.data['status'], 'MERGED')
|
||||
self.assertEqual(A.reported, 2)
|
||||
self.assertEqual(B.data['status'], 'MERGED')
|
||||
self.assertEqual(B.reported, 2)
|
||||
self.assertEqual(C.data['status'], 'MERGED')
|
||||
self.assertEqual(C.reported, 2)
|
||||
|
|
|
@ -140,9 +140,19 @@ class ZuulRESTClient(object):
|
|||
self._check_status(req)
|
||||
return req.json()
|
||||
|
||||
def promote(self, *args, **kwargs):
|
||||
raise NotImplementedError(
|
||||
'This action is unsupported by the REST API')
|
||||
def promote(self, tenant, pipeline, change_ids):
|
||||
if not self.auth_token:
|
||||
raise Exception('Auth Token required')
|
||||
args = {
|
||||
"pipeline": pipeline,
|
||||
"changes": change_ids,
|
||||
}
|
||||
url = urllib.parse.urljoin(
|
||||
self.base_url,
|
||||
'tenant/%s/promote' % tenant)
|
||||
req = self.session.post(url, json=args)
|
||||
self._check_status(req)
|
||||
return req.json()
|
||||
|
||||
def get_running_jobs(self, *args, **kwargs):
|
||||
raise NotImplementedError(
|
||||
|
|
|
@ -371,6 +371,42 @@ class ZuulWebAPI(object):
|
|||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return result
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_in()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
@cherrypy.tools.handle_options(allowed_methods=['POST', ])
|
||||
def promote(self, tenant):
|
||||
basic_error = self._basic_auth_header_check()
|
||||
if basic_error is not None:
|
||||
return basic_error
|
||||
if cherrypy.request.method != 'POST':
|
||||
raise cherrypy.HTTPError(405)
|
||||
# AuthN/AuthZ
|
||||
claims, token_error = self._auth_token_check()
|
||||
if token_error is not None:
|
||||
return token_error
|
||||
self.is_authorized(claims, tenant)
|
||||
|
||||
body = cherrypy.request.json
|
||||
pipeline = body.get('pipeline')
|
||||
changes = body.get('changes')
|
||||
|
||||
msg = 'User "%s" requesting "%s" on %s/%s'
|
||||
self.log.info(
|
||||
msg % (claims['__zuul_uid_claim'], 'promote',
|
||||
tenant, pipeline))
|
||||
|
||||
job = self.rpc.submitJob('zuul:promote',
|
||||
{
|
||||
'tenant': tenant,
|
||||
'pipeline': pipeline,
|
||||
'change_ids': changes,
|
||||
})
|
||||
result = not job.failure
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return result
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
def autohold_list(self, tenant, *args, **kwargs):
|
||||
|
@ -563,6 +599,7 @@ class ZuulWebAPI(object):
|
|||
'autohold_delete': '/api/tenant/{tenant}/autohold/{request_id}',
|
||||
'enqueue': '/api/tenant/{tenant}/project/{project:.*}/enqueue',
|
||||
'dequeue': '/api/tenant/{tenant}/project/{project:.*}/dequeue',
|
||||
'promote': '/api/tenant/{tenant}/promote',
|
||||
}
|
||||
|
||||
@cherrypy.expose
|
||||
|
@ -1199,6 +1236,10 @@ class ZuulWeb(object):
|
|||
'api',
|
||||
'/api/tenant/{tenant}/project/{project:.*}/dequeue',
|
||||
controller=api, action='dequeue')
|
||||
route_map.connect(
|
||||
'api',
|
||||
'/api/tenant/{tenant}/promote',
|
||||
controller=api, action='promote')
|
||||
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',
|
||||
|
|
Loading…
Reference in New Issue