Add pragma directive

This allows the user to override the implied branch matcher behavior.

Change-Id: I3ef43fd868988666cb01e8a6bb28552cc42151b4
This commit is contained in:
James E. Blair 2017-10-26 10:47:14 -07:00
parent df91ab36e1
commit 7edc25f773
10 changed files with 207 additions and 1 deletions

View File

@ -668,6 +668,9 @@ Here is an example of two job definitions:
affect that branch, and likewise, changes to the master branch
only affect it.
See :attr:`pragma.implied-branch-matchers` for how to override
this behavior on a per-file basis.
.. attr:: files
This attribute indicates that the job should only run on changes
@ -1275,3 +1278,41 @@ where it is updated is merged. An example follows:
:default: 1
The maximum number of running jobs which can use this semaphore.
.. _pragma:
Pragma
~~~~~~
The `pragma` item does not behave like the others. It can not be
included or excluded from configuration loading by the administrator,
and does not form part of the final configuration itself. It is used
to alter how the configuration is processed while loading.
A pragma item only affects the current file. The same file in another
branch of the same project will not be affected, nor any other files
or any other projects. The effect is global within that file --
pragma directives may not be set and then unset within the same file.
.. code-block:: yaml
- pragma:
implied-branch-matchers: False
.. attr:: pragma
The pragma item currently only supports one attribute:
.. attr:: implied-branch-matchers
This is a boolean, which, if set, may be used to enable
(``True``) or disable (``False``) the addition of implied branch
matchers to job definitions. Normally Zuul decides whether to
add these based on heuristics described in :attr:`job.branches`.
This attribute overrides that behavior.
This can be useful if a project has multiple branches, yet the
jobs defined in the master branch should apply to all branches.
Note that if a job contains an explicit branch matcher, it will
be used regardless of the value supplied here.

View File

@ -0,0 +1,53 @@
- pipeline:
name: check
manager: independent
trigger:
gerrit:
- event: patchset-created
success:
gerrit:
Verified: 1
failure:
gerrit:
Verified: -1
- pipeline:
name: gate
manager: dependent
post-review: True
trigger:
gerrit:
- event: comment-added
approval:
- Approved: 1
success:
gerrit:
Verified: 2
submit: true
failure:
gerrit:
Verified: -2
start:
gerrit:
Verified: 0
precedence: high
- job:
name: base
parent: null
- project:
name: common-config
check:
jobs: []
gate:
jobs:
- noop
- project:
name: org/project
check:
jobs: []
gate:
jobs:
- noop

View File

@ -0,0 +1 @@
test

View File

@ -0,0 +1,2 @@
- job:
name: test-job

View File

@ -0,0 +1,2 @@
- hosts: all
tasks: []

View File

@ -0,0 +1,5 @@
- pragma:
implied-branch-matchers: False
- job:
name: test-job

View File

@ -0,0 +1,8 @@
- tenant:
name: tenant-one
source:
gerrit:
config-projects:
- common-config
untrusted-projects:
- org/project

View File

@ -1907,6 +1907,57 @@ class TestMaxTimeout(AnsibleZuulTestCase):
"B should not fail because of timeout limit")
class TestPragma(ZuulTestCase):
tenant_config_file = 'config/pragma/main.yaml'
def test_no_pragma(self):
self.create_branch('org/project', 'stable')
with open(os.path.join(FIXTURE_DIR,
'config/pragma/git/',
'org_project/nopragma.yaml')) as f:
config = f.read()
file_dict = {'.zuul.yaml': config}
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
files=file_dict)
A.addApproval('Code-Review', 2)
self.fake_gerrit.addEvent(A.addApproval('Approved', 1))
self.waitUntilSettled()
self.fake_gerrit.addEvent(A.getChangeMergedEvent())
self.waitUntilSettled()
# This is an untrusted repo with 2 branches, so it should have
# an implied branch matcher for the job.
tenant = self.sched.abide.tenants.get('tenant-one')
jobs = tenant.layout.getJobs('test-job')
self.assertEqual(len(jobs), 1)
for job in tenant.layout.getJobs('test-job'):
self.assertIsNotNone(job.branch_matcher)
def test_pragma(self):
self.create_branch('org/project', 'stable')
with open(os.path.join(FIXTURE_DIR,
'config/pragma/git/',
'org_project/pragma.yaml')) as f:
config = f.read()
file_dict = {'.zuul.yaml': config}
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
files=file_dict)
A.addApproval('Code-Review', 2)
self.fake_gerrit.addEvent(A.addApproval('Approved', 1))
self.waitUntilSettled()
self.fake_gerrit.addEvent(A.getChangeMergedEvent())
self.waitUntilSettled()
# This is an untrusted repo with 2 branches, so it would
# normally have an implied branch matcher, but our pragma
# overrides it.
tenant = self.sched.abide.tenants.get('tenant-one')
jobs = tenant.layout.getJobs('test-job')
self.assertEqual(len(jobs), 1)
for job in tenant.layout.getJobs('test-job'):
self.assertIsNone(job.branch_matcher)
class TestBaseJobs(ZuulTestCase):
tenant_config_file = 'config/base-jobs/main.yaml'

View File

@ -238,7 +238,7 @@ class ZuulMark(object):
class ZuulSafeLoader(yaml.SafeLoader):
zuul_node_types = frozenset(('job', 'nodeset', 'secret', 'pipeline',
'project', 'project-template',
'semaphore'))
'semaphore', 'pragma'))
def __init__(self, stream, context):
wrapped_stream = io.StringIO(stream)
@ -313,6 +313,30 @@ class EncryptedPKCS1_OAEP(yaml.YAMLObject):
private_key).decode('utf8')
class PragmaParser(object):
pragma = {
'implied-branch-matchers': bool,
'_source_context': model.SourceContext,
'_start_mark': ZuulMark,
}
schema = vs.Schema(pragma)
def __init__(self):
self.log = logging.getLogger("zuul.PragmaParser")
def fromYaml(self, conf):
with configuration_exceptions('project-template', conf):
self.schema(conf)
bm = conf.get('implied-branch-matchers')
if bm is None:
return
source_context = conf['_source_context']
source_context.implied_branch_matchers = bm
class NodeSetParser(object):
@staticmethod
def getSchema(anonymous=False):
@ -459,6 +483,13 @@ class JobParser(object):
if project_pipeline:
return None
# If the user has set a pragma directive for this, use the
# value (if unset, the value is None).
if job.source_context.implied_branch_matchers is True:
return [job.source_context.branch]
elif job.source_context.implied_branch_matchers is False:
return None
# If this is a trusted project, don't create implied branch
# matchers.
if job.source_context.trusted:
@ -1462,6 +1493,12 @@ class TenantParser(object):
@staticmethod
def _parseLayoutItems(layout, tenant, data, scheduler, connections,
skip_pipelines=False, skip_semaphores=False):
# Handle pragma items first since they modify the source context
# used by other classes.
pragma_parser = PragmaParser()
for config_pragma in data.pragmas:
pragma_parser.fromYaml(config_pragma)
if not skip_pipelines:
for config_pipeline in data.pipelines:
classes = TenantParser._getLoadClasses(

View File

@ -635,6 +635,7 @@ class SourceContext(object):
self.branch = branch
self.path = path
self.trusted = trusted
self.implied_branch_matchers = None
def __str__(self):
return '%s/%s@%s' % (self.project, self.path, self.branch)
@ -2338,6 +2339,7 @@ class UnparsedTenantConfig(object):
"""A collection of yaml lists that has not yet been parsed into objects."""
def __init__(self):
self.pragmas = []
self.pipelines = []
self.jobs = []
self.project_templates = []
@ -2348,6 +2350,7 @@ class UnparsedTenantConfig(object):
def copy(self):
r = UnparsedTenantConfig()
r.pragmas = copy.deepcopy(self.pragmas)
r.pipelines = copy.deepcopy(self.pipelines)
r.jobs = copy.deepcopy(self.jobs)
r.project_templates = copy.deepcopy(self.project_templates)
@ -2359,6 +2362,7 @@ class UnparsedTenantConfig(object):
def extend(self, conf):
if isinstance(conf, UnparsedTenantConfig):
self.pragmas.extend(conf.pragmas)
self.pipelines.extend(conf.pipelines)
self.jobs.extend(conf.jobs)
self.project_templates.extend(conf.project_templates)
@ -2393,6 +2397,8 @@ class UnparsedTenantConfig(object):
self.secrets.append(value)
elif key == 'semaphore':
self.semaphores.append(value)
elif key == 'pragma':
self.pragmas.append(value)
else:
raise ConfigItemUnknownError()