Merge "Support promote via tenant scoped rest api"
This commit is contained in:
commit
c9a2f1086c
|
@ -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