Add freeze-job subcommand
This adds support for the freeze-job endpoint and displays information about a job as it would run in a pipeline. Because there is so much data, the text formatter only displays a high-level summary of the most interesting parts of a job. The JSON formatter returns everything. Change-Id: Ia3e00bf10eae0d569aa49773e81bc8bab1584ba7
This commit is contained in:
parent
f999949aed
commit
f96ddd00fc
@ -164,6 +164,27 @@ Note that zero values for ``oldrev`` and ``newrev`` can indicate
|
|||||||
branch creation and deletion; the source code of Zuul is the best reference
|
branch creation and deletion; the source code of Zuul is the best reference
|
||||||
for these more advanced operations.
|
for these more advanced operations.
|
||||||
|
|
||||||
|
Freeze-job
|
||||||
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
Display information about a job as it would be run in a particular
|
||||||
|
project's pipeline. This causes Zuul to combine all of the matching
|
||||||
|
jobs and variants that would be used to form the final version of a
|
||||||
|
job that would be executed for a change or ref as enqueued into the
|
||||||
|
specified pipeline. This includes job attributes, playbook paths,
|
||||||
|
nodesets, variables, etc. Secret names may be included but the values
|
||||||
|
are redacted.
|
||||||
|
|
||||||
|
The default text output shows an abbreviated summary of only the most
|
||||||
|
pertinent information. The JSON output reports all available
|
||||||
|
information.
|
||||||
|
|
||||||
|
.. program-output:: zuul-client freeze-job --help
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
zuul-client freeze-job --tenant mytenant --pipeline check --project org/project --branch master --job tox
|
||||||
|
|
||||||
Job-graph
|
Job-graph
|
||||||
^^^^^^^^^
|
^^^^^^^^^
|
||||||
|
|
||||||
|
5
releasenotes/notes/freeze-job-8b2e86292952291e.yaml
Normal file
5
releasenotes/notes/freeze-job-8b2e86292952291e.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Add freeze-job subcommand to display information about jobs as they would
|
||||||
|
be run in a project's pipeline.
|
@ -488,3 +488,25 @@ GuS6/ewjS+arA1Iyeg/IxmECAwEAAQ==
|
|||||||
'https://fake.zuul/api/tenant/tenant1/pipeline/check/'
|
'https://fake.zuul/api/tenant/tenant1/pipeline/check/'
|
||||||
'project/project1/branch/master/freeze-jobs')
|
'project/project1/branch/master/freeze-jobs')
|
||||||
self.assertEqual(fakejson, graph)
|
self.assertEqual(fakejson, graph)
|
||||||
|
|
||||||
|
def test_freeze_job(self):
|
||||||
|
"""Test freeze-job endpoint"""
|
||||||
|
client = ZuulRESTClient(url='https://fake.zuul/')
|
||||||
|
# test status checks
|
||||||
|
self._test_status_check(
|
||||||
|
client, 'get', client.freeze_jobs,
|
||||||
|
'tenant1', 'check', 'project1', 'master')
|
||||||
|
|
||||||
|
fakejson = {
|
||||||
|
"job": "testjob",
|
||||||
|
"ansible_version": "5",
|
||||||
|
}
|
||||||
|
req = FakeRequestResponse(200, fakejson)
|
||||||
|
client.session.get = MagicMock(return_value=req)
|
||||||
|
client.info_ = {}
|
||||||
|
job = client.freeze_job('tenant1', 'check', 'project1', 'master',
|
||||||
|
'testjob')
|
||||||
|
client.session.get.assert_any_call(
|
||||||
|
'https://fake.zuul/api/tenant/tenant1/pipeline/check/'
|
||||||
|
'project/project1/branch/master/freeze-job/testjob')
|
||||||
|
self.assertEqual(fakejson, job)
|
||||||
|
@ -649,3 +649,45 @@ verify_ssl=True"""
|
|||||||
'project/project1/branch/master/freeze-jobs',
|
'project/project1/branch/master/freeze-jobs',
|
||||||
)
|
)
|
||||||
self.assertEqual(0, exit_code)
|
self.assertEqual(0, exit_code)
|
||||||
|
|
||||||
|
def test_freeze_job(self):
|
||||||
|
"""Test freeze-job subcommand"""
|
||||||
|
ZC = ZuulClient()
|
||||||
|
with patch('requests.Session') as mock_sesh:
|
||||||
|
session = mock_sesh.return_value
|
||||||
|
fakejson = {
|
||||||
|
"job": "testjob",
|
||||||
|
"ansible_version": "5",
|
||||||
|
"nodeset": {
|
||||||
|
"groups": [],
|
||||||
|
"name": "ubuntu-jammy",
|
||||||
|
},
|
||||||
|
"vars": {},
|
||||||
|
"pre_playbooks": [
|
||||||
|
{
|
||||||
|
"branch": "master",
|
||||||
|
"connection": "gerrit",
|
||||||
|
"path": "playbooks/base/pre.yaml",
|
||||||
|
"project": "opendev/base-jobs",
|
||||||
|
"roles": [],
|
||||||
|
"trusted": True,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
session.get = MagicMock(
|
||||||
|
side_effect=mock_get(
|
||||||
|
MagicMock(return_value=FakeRequestResponse(200, fakejson))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
exit_code = ZC._main(
|
||||||
|
['--zuul-url', 'https://fake.zuul', 'freeze-job',
|
||||||
|
'--tenant', 'tenant1',
|
||||||
|
'--pipeline', 'check',
|
||||||
|
'--project', 'project1',
|
||||||
|
'--branch', 'master',
|
||||||
|
'--job', 'testjob'])
|
||||||
|
session.get.assert_any_call(
|
||||||
|
'https://fake.zuul/api/tenant/tenant1/pipeline/check/'
|
||||||
|
'project/project1/branch/master/freeze-job/testjob',
|
||||||
|
)
|
||||||
|
self.assertEqual(0, exit_code)
|
||||||
|
@ -305,3 +305,15 @@ class ZuulRESTClient(object):
|
|||||||
req = self.session.get(url)
|
req = self.session.get(url)
|
||||||
self._check_request_status(req)
|
self._check_request_status(req)
|
||||||
return req.json()
|
return req.json()
|
||||||
|
|
||||||
|
def freeze_job(self, tenant, pipeline, project, branch, job):
|
||||||
|
suffix = (f'pipeline/{pipeline}/project/{project}/'
|
||||||
|
f'branch/{branch}/freeze-job/{job}')
|
||||||
|
if self.info.get("tenant"):
|
||||||
|
self._check_scope(tenant)
|
||||||
|
else:
|
||||||
|
suffix = f'tenant/{tenant}/{suffix}'
|
||||||
|
url = urllib.parse.urljoin(self.base_url, suffix)
|
||||||
|
req = self.session.get(url)
|
||||||
|
self._check_request_status(req)
|
||||||
|
return req.json()
|
||||||
|
@ -109,6 +109,7 @@ class ZuulClient():
|
|||||||
self.add_builds_list_subparser(subparsers)
|
self.add_builds_list_subparser(subparsers)
|
||||||
self.add_build_info_subparser(subparsers)
|
self.add_build_info_subparser(subparsers)
|
||||||
self.add_job_graph_subparser(subparsers)
|
self.add_job_graph_subparser(subparsers)
|
||||||
|
self.add_freeze_job_subparser(subparsers)
|
||||||
|
|
||||||
return subparsers
|
return subparsers
|
||||||
|
|
||||||
@ -814,6 +815,32 @@ class ZuulClient():
|
|||||||
print(formatted_result)
|
print(formatted_result)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def add_freeze_job_subparser(self, subparsers):
|
||||||
|
cmd_freeze_job = subparsers.add_parser(
|
||||||
|
'freeze-job', help='Freeze and display a job')
|
||||||
|
cmd_freeze_job.add_argument(
|
||||||
|
'--tenant', help='tenant name', required=False, default='')
|
||||||
|
cmd_freeze_job.add_argument('--pipeline', help='pipeline name',
|
||||||
|
required=True)
|
||||||
|
cmd_freeze_job.add_argument('--project', help='project name',
|
||||||
|
required=True)
|
||||||
|
cmd_freeze_job.add_argument('--branch', help='branch name',
|
||||||
|
required=True)
|
||||||
|
cmd_freeze_job.add_argument('--job', help='job name',
|
||||||
|
required=True)
|
||||||
|
cmd_freeze_job.set_defaults(func=self.freeze_job)
|
||||||
|
self.cmd_freeze_job = cmd_freeze_job
|
||||||
|
|
||||||
|
def freeze_job(self):
|
||||||
|
client = self.get_client()
|
||||||
|
self._check_tenant_scope(client)
|
||||||
|
job = client.freeze_job(self.tenant(), self.args.pipeline,
|
||||||
|
self.args.project, self.args.branch,
|
||||||
|
self.args.job)
|
||||||
|
formatted_result = self.formatter('FreezeJob')(job)
|
||||||
|
print(formatted_result)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
ZuulClient().main()
|
ZuulClient().main()
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
from dateutil.parser import isoparse
|
from dateutil.parser import isoparse
|
||||||
|
import pprint
|
||||||
|
|
||||||
import prettytable
|
import prettytable
|
||||||
import json
|
import json
|
||||||
@ -66,6 +67,9 @@ class BaseFormatter:
|
|||||||
def formatJobGraph(self, data):
|
def formatJobGraph(self, data):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def formatFreezeJob(self, data):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class JSONFormatter(BaseFormatter):
|
class JSONFormatter(BaseFormatter):
|
||||||
def __call__(self, data) -> str:
|
def __call__(self, data) -> str:
|
||||||
@ -285,6 +289,41 @@ class PrettyTableFormatter(BaseFormatter):
|
|||||||
])
|
])
|
||||||
return str(table)
|
return str(table)
|
||||||
|
|
||||||
|
def formatFreezeJob(self, data) -> str:
|
||||||
|
printer = pprint.PrettyPrinter(indent=2)
|
||||||
|
ret = ''
|
||||||
|
for label, key in [
|
||||||
|
('Job', 'job'),
|
||||||
|
('Branch', 'branch'),
|
||||||
|
('Ansible Version', 'ansible_version'),
|
||||||
|
('Timeout', 'timeout'),
|
||||||
|
('Post Timeout', 'post_timeout'),
|
||||||
|
('Workspace Scheme', 'workspace_scheme'),
|
||||||
|
('Override Checkout', 'override_checkout'),
|
||||||
|
]:
|
||||||
|
value = data.get(key)
|
||||||
|
if value is not None:
|
||||||
|
ret += f'{label}: {value}\n'
|
||||||
|
if data['nodeset']['name']:
|
||||||
|
ret += f"Nodeset: {data['nodeset']['name']}\n"
|
||||||
|
for label, key in [
|
||||||
|
('Pre-run Playbooks', 'pre_playbooks'),
|
||||||
|
('Run Playbooks', 'playbooks'),
|
||||||
|
('Post-run Playbooks', 'post_playbooks'),
|
||||||
|
('Cleanup Playbooks', 'cleanup_playbooks'),
|
||||||
|
]:
|
||||||
|
pbs = data.get(key)
|
||||||
|
if not pbs:
|
||||||
|
continue
|
||||||
|
ret += f"{label}:\n"
|
||||||
|
for pb in pbs:
|
||||||
|
trusted = ' [trusted]' if pb['trusted'] else ''
|
||||||
|
ret += (f" {pb['connection']}:{pb['project']}:"
|
||||||
|
f"{pb['path']}@{pb['branch']}{trusted}\n")
|
||||||
|
ret += 'Vars:\n'
|
||||||
|
ret += printer.pformat(data['vars'])
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class DotFormatter(BaseFormatter):
|
class DotFormatter(BaseFormatter):
|
||||||
"""Format for graphviz"""
|
"""Format for graphviz"""
|
||||||
|
Loading…
Reference in New Issue
Block a user