summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTobias Henkel <tobias.henkel@bmw.de>2017-11-26 20:27:59 +0100
committerTobias Henkel <tobias.henkel@bmw.de>2017-12-15 19:31:02 +0100
commit130b00064f035d2f99dc7eec57f7781bfc3bd555 (patch)
treeff8a9a3f4d3ebffd11fe88ad2cac498a51764519
parentefd8e827791c513a181d4b5dde149a65f8e60131 (diff)
downloadzuul-130b00064f035d2f99dc7eec57f7781bfc3bd555.tar.gz
Add support for protected jobs
For some use cases protected jobs can be useful. Protected jobs can only be inherited by jobs defined in the same project. This adds support for these protected jobs. Change-Id: I62a8ecbbfa9eec54ab599bb34148976dcabfd40a
-rw-r--r--doc/source/user/config.rst7
-rw-r--r--tests/fixtures/config/protected/git/common-config/zuul.yaml16
-rw-r--r--tests/fixtures/config/protected/git/org_project/playbooks/job-protected.yaml2
-rw-r--r--tests/fixtures/config/protected/git/org_project/zuul.yaml9
-rw-r--r--tests/fixtures/config/protected/git/org_project1/README1
-rw-r--r--tests/fixtures/config/protected/git/org_project1/playbooks/job-child-notok.yaml2
-rw-r--r--tests/fixtures/config/protected/git/org_project1/playbooks/placeholder0
-rw-r--r--tests/fixtures/config/protected/main.yaml9
-rwxr-xr-xtests/unit/test_v3.py104
-rw-r--r--zuul/configloader.py2
-rw-r--r--zuul/model.py24
11 files changed, 175 insertions, 1 deletions
diff --git a/doc/source/user/config.rst b/doc/source/user/config.rst
index 96e55a8f0..d1711078f 100644
--- a/doc/source/user/config.rst
+++ b/doc/source/user/config.rst
@@ -539,6 +539,13 @@ Here is an example of two job definitions:
specified in a project's pipeline, set this attribute to
``true``.
+ .. attr:: protected
+ :default: false
+
+ When set to ``true`` only jobs defined in the same project may inherit
+ from this job. Once this is set to ``true`` it cannot be reset to
+ ``false``.
+
.. attr:: success-message
:default: SUCCESS
diff --git a/tests/fixtures/config/protected/git/common-config/zuul.yaml b/tests/fixtures/config/protected/git/common-config/zuul.yaml
new file mode 100644
index 000000000..c941573e6
--- /dev/null
+++ b/tests/fixtures/config/protected/git/common-config/zuul.yaml
@@ -0,0 +1,16 @@
+- pipeline:
+ name: check
+ manager: independent
+ trigger:
+ gerrit:
+ - event: patchset-created
+ success:
+ gerrit:
+ Verified: 1
+ failure:
+ gerrit:
+ Verified: -1
+
+- job:
+ name: base
+ parent: null
diff --git a/tests/fixtures/config/protected/git/org_project/playbooks/job-protected.yaml b/tests/fixtures/config/protected/git/org_project/playbooks/job-protected.yaml
new file mode 100644
index 000000000..f679dceae
--- /dev/null
+++ b/tests/fixtures/config/protected/git/org_project/playbooks/job-protected.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+ tasks: []
diff --git a/tests/fixtures/config/protected/git/org_project/zuul.yaml b/tests/fixtures/config/protected/git/org_project/zuul.yaml
new file mode 100644
index 000000000..95f33df6f
--- /dev/null
+++ b/tests/fixtures/config/protected/git/org_project/zuul.yaml
@@ -0,0 +1,9 @@
+- job:
+ name: job-protected
+ protected: true
+ run: playbooks/job-protected.yaml
+
+- project:
+ name: org/project
+ check:
+ jobs: []
diff --git a/tests/fixtures/config/protected/git/org_project1/README b/tests/fixtures/config/protected/git/org_project1/README
new file mode 100644
index 000000000..9daeafb98
--- /dev/null
+++ b/tests/fixtures/config/protected/git/org_project1/README
@@ -0,0 +1 @@
+test
diff --git a/tests/fixtures/config/protected/git/org_project1/playbooks/job-child-notok.yaml b/tests/fixtures/config/protected/git/org_project1/playbooks/job-child-notok.yaml
new file mode 100644
index 000000000..f679dceae
--- /dev/null
+++ b/tests/fixtures/config/protected/git/org_project1/playbooks/job-child-notok.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+ tasks: []
diff --git a/tests/fixtures/config/protected/git/org_project1/playbooks/placeholder b/tests/fixtures/config/protected/git/org_project1/playbooks/placeholder
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/fixtures/config/protected/git/org_project1/playbooks/placeholder
diff --git a/tests/fixtures/config/protected/main.yaml b/tests/fixtures/config/protected/main.yaml
new file mode 100644
index 000000000..5f57245cc
--- /dev/null
+++ b/tests/fixtures/config/protected/main.yaml
@@ -0,0 +1,9 @@
+- tenant:
+ name: tenant-one
+ source:
+ gerrit:
+ config-projects:
+ - common-config
+ untrusted-projects:
+ - org/project
+ - org/project1
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index b9c9b3253..c5d19ceb4 100755
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -73,6 +73,110 @@ class TestMultipleTenants(AnsibleZuulTestCase):
"not affect tenant one")
+class TestProtected(ZuulTestCase):
+
+ tenant_config_file = 'config/protected/main.yaml'
+
+ def test_protected_ok(self):
+ # test clean usage of final parent job
+ in_repo_conf = textwrap.dedent(
+ """
+ - job:
+ name: job-protected
+ protected: true
+ run: playbooks/job-protected.yaml
+
+ - project:
+ name: org/project
+ check:
+ jobs:
+ - job-child-ok
+
+ - job:
+ name: job-child-ok
+ parent: job-protected
+
+ - project:
+ name: org/project
+ check:
+ jobs:
+ - job-child-ok
+
+ """)
+
+ file_dict = {'zuul.yaml': in_repo_conf}
+ A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
+ files=file_dict)
+ self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+ self.waitUntilSettled()
+
+ self.assertEqual(A.reported, 1)
+ self.assertEqual(A.patchsets[-1]['approvals'][0]['value'], '1')
+
+ def test_protected_reset(self):
+ # try to reset protected flag
+ in_repo_conf = textwrap.dedent(
+ """
+ - job:
+ name: job-protected
+ protected: true
+ run: playbooks/job-protected.yaml
+
+ - job:
+ name: job-child-reset-protected
+ parent: job-protected
+ protected: false
+
+ - project:
+ name: org/project
+ check:
+ jobs:
+ - job-child-reset-protected
+
+ """)
+
+ file_dict = {'zuul.yaml': in_repo_conf}
+ A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
+ files=file_dict)
+ self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+ self.waitUntilSettled()
+
+ # The second patch tried to override some variables.
+ # Thus it should fail.
+ self.assertEqual(A.reported, 1)
+ self.assertEqual(A.patchsets[-1]['approvals'][0]['value'], '-1')
+ self.assertIn('Unable to reset protected attribute', A.messages[0])
+
+ def test_protected_inherit_not_ok(self):
+ # try to inherit from a protected job in different project
+ in_repo_conf = textwrap.dedent(
+ """
+ - job:
+ name: job-child-notok
+ run: playbooks/job-child-notok.yaml
+ parent: job-protected
+
+ - project:
+ name: org/project1
+ check:
+ jobs:
+ - job-child-notok
+
+ """)
+
+ file_dict = {'zuul.yaml': in_repo_conf}
+ A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A',
+ files=file_dict)
+ self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+ self.waitUntilSettled()
+
+ self.assertEqual(A.reported, 1)
+ self.assertEqual(A.patchsets[-1]['approvals'][0]['value'], '-1')
+ self.assertIn(
+ "which is defined in review.example.com/org/project is protected "
+ "and cannot be inherited from other projects.", A.messages[0])
+
+
class TestFinal(ZuulTestCase):
tenant_config_file = 'config/final/main.yaml'
diff --git a/zuul/configloader.py b/zuul/configloader.py
index fb1695c18..669cd8bbd 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -428,6 +428,7 @@ class JobParser(object):
# Attributes of a job that can also be used in Project and ProjectTemplate
job_attributes = {'parent': vs.Any(str, None),
'final': bool,
+ 'protected': bool,
'failure-message': str,
'success-message': str,
'failure-url': str,
@@ -467,6 +468,7 @@ class JobParser(object):
simple_attributes = [
'final',
+ 'protected',
'timeout',
'workspace',
'voting',
diff --git a/zuul/model.py b/zuul/model.py
index 56d08a16f..04df7a80f 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -843,6 +843,7 @@ class Job(object):
semaphore=None,
attempts=3,
final=False,
+ protected=None,
roles=(),
required_projects={},
allowed_projects=None,
@@ -860,6 +861,7 @@ class Job(object):
inheritance_path=(),
parent_data=None,
description=None,
+ protected_origin=None,
)
self.inheritable_attributes = {}
@@ -1037,12 +1039,21 @@ class Job(object):
for k in self.execution_attributes:
if (other._get(k) is not None and
- k not in set(['final'])):
+ k not in set(['final', 'protected'])):
if self.final:
raise Exception("Unable to modify final job %s attribute "
"%s=%s with variant %s" % (
repr(self), k, other._get(k),
repr(other)))
+ if self.protected_origin:
+ # this is a protected job, check origin of job definition
+ this_origin = self.protected_origin
+ other_origin = other.source_context.project.canonical_name
+ if this_origin != other_origin:
+ raise Exception("Job %s which is defined in %s is "
+ "protected and cannot be inherited "
+ "from other projects."
+ % (repr(self), this_origin))
if k not in set(['pre_run', 'run', 'post_run', 'roles',
'variables', 'required_projects']):
# TODO(jeblair): determine if deepcopy is required
@@ -1053,6 +1064,17 @@ class Job(object):
if other.final != self.attributes['final']:
self.final = other.final
+ # Protected may only be set to true
+ if other.protected is not None:
+ # don't allow to reset protected flag
+ if not other.protected and self.protected_origin:
+ raise Exception("Unable to reset protected attribute of job"
+ " %s by job %s" % (
+ repr(self), repr(other)))
+ if not self.protected_origin:
+ self.protected_origin = \
+ other.source_context.project.canonical_name
+
# We must update roles before any playbook contexts
if other._get('roles') is not None:
self.addRoles(other.roles)