Add support for excluding locked branches
This adds support for excluding locked (read-only) branches. This is currently only supported by the Github driver. Change-Id: I360edeb04c9734189396e8c5ddbed17e7f7464a8
This commit is contained in:
parent
a2459321bf
commit
49a9295c8f
|
@ -804,8 +804,9 @@ The rules prevent Pull requests to be merged on defined branches if they are
|
|||
not met. For instance a branch might require that specific status are marked
|
||||
as ``success`` before allowing the merge of the Pull request.
|
||||
|
||||
Zuul provides the attribute tenant.untrusted-projects.exclude-unprotected-branches.
|
||||
This attribute is by default set to ``false`` but we recommend to set it to
|
||||
Zuul provides the attribute
|
||||
:attr:`tenant.untrusted-projects.<project>.exclude-unprotected-branches`. This
|
||||
attribute is by default set to ``false`` but we recommend to set it to
|
||||
``true`` for the whole tenant. By doing so Zuul will benefit from:
|
||||
|
||||
- exluding in-repo development branches used to open Pull requests. This will
|
||||
|
@ -816,6 +817,10 @@ This attribute is by default set to ``false`` but we recommend to set it to
|
|||
Zuul only takes in account "Require status checks to pass before merging" and
|
||||
the checked status checkboxes.
|
||||
|
||||
Likewise, it is recommended to set the
|
||||
:attr:`tenant.untrusted-projects.<project>.exclude-locked-branches` setting to
|
||||
avoid expending resources on read-only branches.
|
||||
|
||||
With the use of the reference pipelines below, the Zuul project recommends to
|
||||
set the minimum following settings:
|
||||
|
||||
|
|
|
@ -210,13 +210,20 @@ configuration. Some examples of tenant definitions are:
|
|||
exclude-unprotected-branches. This currently only affects
|
||||
GitHub and GitLab projects.
|
||||
|
||||
.. attr:: exclude-locked-branches
|
||||
|
||||
Define if locked branches should be processed.
|
||||
Defaults to the tenant wide setting of
|
||||
exclude-locked-branches. This currently only affects
|
||||
GitHub projects.
|
||||
|
||||
.. attr:: include-branches
|
||||
|
||||
A list of regexes matching branches which should be
|
||||
processed. If omitted, all branches are included.
|
||||
Operates after *exclude-unprotected-branches* and so may
|
||||
be used to further reduce the set of branches (but not
|
||||
increase it).
|
||||
Operates after *exclude-unprotected-branches* and
|
||||
*exclude-locked-branches* and so may be used to further
|
||||
reduce the set of branches (but not increase it).
|
||||
|
||||
It has priority over *exclude-branches*.
|
||||
|
||||
|
@ -224,9 +231,9 @@ configuration. Some examples of tenant definitions are:
|
|||
|
||||
A list of regexes matching branches which should be
|
||||
processed. If omitted, all branches are included.
|
||||
Operates after *exclude-unprotected-branches* and so may
|
||||
be used to further reduce the set of branches (but not
|
||||
increase it).
|
||||
Operates after *exclude-unprotected-branches* and
|
||||
*exclude-locked-branches* and so may be used to further
|
||||
reduce the set of branches (but not increase it).
|
||||
|
||||
It will not exclude a branch which already matched
|
||||
*include-branches*.
|
||||
|
@ -377,6 +384,15 @@ configuration. Some examples of tenant definitions are:
|
|||
is a tenant wide setting and can be overridden per project.
|
||||
This currently only affects GitHub and GitLab projects.
|
||||
|
||||
.. attr:: exclude-locked-branches
|
||||
:default: false
|
||||
|
||||
Some code review systems support read-only, or "locked"
|
||||
branches. Enabling this setting will cause Zuul to ignore these
|
||||
branches. This is a tenant wide setting and can be overridden
|
||||
per project. This currently only affects GitHub and GitLab
|
||||
projects.
|
||||
|
||||
.. attr:: default-parent
|
||||
:default: base
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
The GitHub driver now supports excluding locked (read-only) branches
|
||||
with the `exclude-locked-branches` tenant configuration setting.
|
|
@ -0,0 +1,23 @@
|
|||
- pipeline:
|
||||
name: check
|
||||
manager: independent
|
||||
trigger:
|
||||
github:
|
||||
- event: pull_request
|
||||
action:
|
||||
- opened
|
||||
- changed
|
||||
- reopened
|
||||
success:
|
||||
github:
|
||||
status: 'success'
|
||||
failure:
|
||||
github:
|
||||
status: 'failure'
|
||||
start:
|
||||
github:
|
||||
comment: true
|
||||
|
||||
- job:
|
||||
name: base
|
||||
parent: null
|
|
@ -0,0 +1 @@
|
|||
test
|
2
tests/fixtures/config/locked-branches/git/org_project1/playbooks/project-test.yaml
vendored
Normal file
2
tests/fixtures/config/locked-branches/git/org_project1/playbooks/project-test.yaml
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
- hosts: all
|
||||
tasks: []
|
|
@ -0,0 +1,9 @@
|
|||
- job:
|
||||
name: project-test
|
||||
run: playbooks/project-test.yaml
|
||||
|
||||
- project:
|
||||
name: org/project1
|
||||
check:
|
||||
jobs:
|
||||
- project-test
|
|
@ -0,0 +1 @@
|
|||
test
|
2
tests/fixtures/config/locked-branches/git/org_project2/playbooks/used-job.yaml
vendored
Normal file
2
tests/fixtures/config/locked-branches/git/org_project2/playbooks/used-job.yaml
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
- hosts: all
|
||||
tasks: []
|
|
@ -0,0 +1,8 @@
|
|||
- job:
|
||||
name: used-job
|
||||
run: playbooks/used-job.yaml
|
||||
|
||||
- project:
|
||||
check:
|
||||
jobs:
|
||||
- used-job
|
|
@ -0,0 +1 @@
|
|||
test
|
|
@ -0,0 +1,13 @@
|
|||
- tenant:
|
||||
name: tenant-one
|
||||
source:
|
||||
github:
|
||||
config-projects:
|
||||
- org/common-config
|
||||
untrusted-projects:
|
||||
- org/project1
|
||||
- org/project2:
|
||||
exclude-locked-branches: true
|
||||
- org/project3:
|
||||
exclude-unprotected-branches: true
|
||||
exclude-locked-branches: true
|
|
@ -1954,6 +1954,71 @@ class TestGithubUnprotectedBranches(ZuulTestCase):
|
|||
prev_layout = new_layout
|
||||
|
||||
|
||||
class TestGithubLockedBranches(ZuulTestCase):
|
||||
config_file = 'zuul-github-driver.conf'
|
||||
tenant_config_file = 'config/locked-branches/main.yaml'
|
||||
scheduler_count = 1
|
||||
|
||||
def test_exclude_locked_branches(self):
|
||||
tenant = self.scheds.first.sched.abide.tenants\
|
||||
.get('tenant-one')
|
||||
|
||||
project1 = tenant.untrusted_projects[0]
|
||||
project2 = tenant.untrusted_projects[1]
|
||||
project3 = tenant.untrusted_projects[2]
|
||||
|
||||
tpc1 = tenant.project_configs[project1.canonical_name]
|
||||
tpc2 = tenant.project_configs[project2.canonical_name]
|
||||
tpc3 = tenant.project_configs[project3.canonical_name]
|
||||
|
||||
# projects 1 and 2 should have parsed master
|
||||
self.assertIn('master', tpc1.parsed_branch_config.keys())
|
||||
self.assertIn('master', tpc2.parsed_branch_config.keys())
|
||||
# project 3 should not because it excludes unprotected
|
||||
self.assertEqual(0, len(tpc3.parsed_branch_config.keys()))
|
||||
|
||||
# now lock project2 and trigger reload
|
||||
github = self.fake_github.getGithubClient()
|
||||
repo = github.repo_from_project('org/project2')
|
||||
rule = repo._set_branch_protection('master', True)
|
||||
rule.lock_branch = True
|
||||
|
||||
pevent = self.fake_github.getPushEvent(project='org/project2',
|
||||
ref='refs/heads/master')
|
||||
self.fake_github.emitEvent(pevent)
|
||||
self.waitUntilSettled()
|
||||
|
||||
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
|
||||
tpc1 = tenant.project_configs[project1.canonical_name]
|
||||
tpc2 = tenant.project_configs[project2.canonical_name]
|
||||
tpc3 = tenant.project_configs[project3.canonical_name]
|
||||
|
||||
# project2 should no longer have a master branch
|
||||
self.assertIn('master', tpc1.parsed_branch_config.keys())
|
||||
self.assertEqual(0, len(tpc2.parsed_branch_config.keys()))
|
||||
self.assertEqual(0, len(tpc3.parsed_branch_config.keys()))
|
||||
|
||||
# Lock project 3 as well and ensure it's still excluded
|
||||
repo = github.repo_from_project('org/project3')
|
||||
rule = repo._set_branch_protection('master', True)
|
||||
rule.lock_branch = True
|
||||
|
||||
pevent = self.fake_github.getPushEvent(project='org/project3',
|
||||
ref='refs/heads/master')
|
||||
self.fake_github.emitEvent(pevent)
|
||||
self.waitUntilSettled()
|
||||
|
||||
tenant = self.scheds.first.sched.abide.tenants.get('tenant-one')
|
||||
tpc1 = tenant.project_configs[project1.canonical_name]
|
||||
tpc2 = tenant.project_configs[project2.canonical_name]
|
||||
tpc3 = tenant.project_configs[project3.canonical_name]
|
||||
|
||||
# project3 is still excluded, but for a different reason
|
||||
self.assertIn('master', tpc1.parsed_branch_config.keys())
|
||||
self.assertEqual(0, len(tpc2.parsed_branch_config.keys()))
|
||||
self.assertEqual(0, len(tpc3.parsed_branch_config.keys()))
|
||||
|
||||
|
||||
class TestGithubWebhook(ZuulTestCase):
|
||||
config_file = 'zuul-github-driver.conf'
|
||||
scheduler_count = 1
|
||||
|
|
|
@ -1652,6 +1652,7 @@ class TenantParser(object):
|
|||
'exclude': to_list(classes),
|
||||
'shadow': to_list(str),
|
||||
'exclude-unprotected-branches': bool,
|
||||
'exclude-locked-branches': bool,
|
||||
'extra-config-paths': no_dup_config_paths,
|
||||
'load-branch': str,
|
||||
'include-branches': to_list(str),
|
||||
|
@ -1696,6 +1697,7 @@ class TenantParser(object):
|
|||
'max-job-timeout': int,
|
||||
'source': self.validateTenantSources(),
|
||||
'exclude-unprotected-branches': bool,
|
||||
'exclude-locked-branches': bool,
|
||||
'allowed-triggers': to_list(str),
|
||||
'allowed-reporters': to_list(str),
|
||||
'allowed-labels': to_list(str),
|
||||
|
@ -1736,6 +1738,9 @@ class TenantParser(object):
|
|||
if conf.get('exclude-unprotected-branches') is not None:
|
||||
tenant.exclude_unprotected_branches = \
|
||||
conf['exclude-unprotected-branches']
|
||||
if conf.get('exclude-locked-branches') is not None:
|
||||
tenant.exclude_locked_branches = \
|
||||
conf['exclude-locked-branches']
|
||||
if conf.get('admin-rules') is not None:
|
||||
tenant.admin_rules = as_list(conf['admin-rules'])
|
||||
if conf.get('access-rules') is not None:
|
||||
|
@ -1902,6 +1907,7 @@ class TenantParser(object):
|
|||
project_include = current_include
|
||||
shadow_projects = []
|
||||
project_exclude_unprotected_branches = None
|
||||
project_exclude_locked_branches = None
|
||||
project_include_branches = None
|
||||
project_exclude_branches = None
|
||||
project_always_dynamic_branches = None
|
||||
|
@ -1924,6 +1930,8 @@ class TenantParser(object):
|
|||
project_include = frozenset(project_include - project_exclude)
|
||||
project_exclude_unprotected_branches = conf[project_name].get(
|
||||
'exclude-unprotected-branches', None)
|
||||
project_exclude_locked_branches = conf[project_name].get(
|
||||
'exclude-locked-branches', None)
|
||||
project_include_branches = conf[project_name].get(
|
||||
'include-branches', None)
|
||||
if project_include_branches is not None:
|
||||
|
@ -1969,6 +1977,8 @@ class TenantParser(object):
|
|||
tenant_project_config.shadow_projects = shadow_projects
|
||||
tenant_project_config.exclude_unprotected_branches = \
|
||||
project_exclude_unprotected_branches
|
||||
tenant_project_config.exclude_locked_branches = \
|
||||
project_exclude_locked_branches
|
||||
tenant_project_config.include_branches = project_include_branches
|
||||
tenant_project_config.exclude_branches = project_exclude_branches
|
||||
tenant_project_config.always_dynamic_branches = \
|
||||
|
|
|
@ -278,7 +278,7 @@ class ZKBranchCacheMixin:
|
|||
:returns: The list of branch names.
|
||||
"""
|
||||
exclude_unprotected = tenant.getExcludeUnprotectedBranches(project)
|
||||
exclude_locked = False
|
||||
exclude_locked = tenant.getExcludeLockedBranches(project)
|
||||
branches = None
|
||||
|
||||
required_flags = self._fetchProjectBranchesRequiredFlags(
|
||||
|
|
|
@ -1857,14 +1857,16 @@ class GithubConnection(ZKChangeCacheMixin, ZKBranchCacheMixin, BaseConnection):
|
|||
|
||||
valid_flags = set()
|
||||
branch_infos = {}
|
||||
if BranchFlag.PROTECTED in required_flags:
|
||||
if {BranchFlag.PROTECTED, BranchFlag.LOCKED} & required_flags:
|
||||
valid_flags.add(BranchFlag.PROTECTED)
|
||||
valid_flags.add(BranchFlag.LOCKED)
|
||||
for branch_name, locked in \
|
||||
self.graphql_client.fetch_branch_protection(
|
||||
github, project).items():
|
||||
bi = branch_infos.setdefault(
|
||||
branch_name, BranchInfo(branch_name))
|
||||
bi.protected = True
|
||||
bi.locked = locked
|
||||
if BranchFlag.PRESENT in required_flags:
|
||||
valid_flags.add(BranchFlag.PRESENT)
|
||||
for branch_name in self._fetchProjectBranchesREST(
|
||||
|
|
|
@ -7168,6 +7168,7 @@ class TenantProjectConfig(object):
|
|||
# The tenant's default setting of exclude_unprotected_branches will
|
||||
# be overridden by this one if not None.
|
||||
self.exclude_unprotected_branches = None
|
||||
self.exclude_locked_branches = None
|
||||
self.include_branches = None
|
||||
self.exclude_branches = None
|
||||
self.always_dynamic_branches = None
|
||||
|
@ -8391,6 +8392,7 @@ class Tenant(object):
|
|||
self.max_job_timeout = 10800
|
||||
self.max_dependencies = None
|
||||
self.exclude_unprotected_branches = False
|
||||
self.exclude_locked_branches = False
|
||||
self.default_base_job = None
|
||||
self.layout = None
|
||||
# The unparsed configuration from the main zuul config for
|
||||
|
@ -8554,10 +8556,16 @@ class Tenant(object):
|
|||
# match wins. The order is project -> tenant (default is false).
|
||||
project_config = self.project_configs.get(project.canonical_name)
|
||||
if project_config.exclude_unprotected_branches is not None:
|
||||
exclude_unprotected = project_config.exclude_unprotected_branches
|
||||
else:
|
||||
exclude_unprotected = self.exclude_unprotected_branches
|
||||
return exclude_unprotected
|
||||
return project_config.exclude_unprotected_branches
|
||||
return self.exclude_unprotected_branches
|
||||
|
||||
def getExcludeLockedBranches(self, project):
|
||||
# Evaluate if locked branches should be excluded or not. The first
|
||||
# match wins. The order is project -> tenant (default is false).
|
||||
project_config = self.project_configs.get(project.canonical_name)
|
||||
if project_config.exclude_locked_branches is not None:
|
||||
return project_config.exclude_locked_branches
|
||||
return self.exclude_locked_branches
|
||||
|
||||
def addConfigProject(self, tpc):
|
||||
self.config_projects.append(tpc.project)
|
||||
|
|
Loading…
Reference in New Issue