Expose final job attribute
Exposing the final job attribute make it possible to directly
configure a job as final.
Prohibit inheritance with final
This is no longer automatically set based on auth inheritance, so
now only exists as an attribute for a user to set explicitly.
The word "final" has a pretty specifig meaning for software developers
at least, so let's err on the side of safety there to provide folks
with the least surprise.
Also document it.
Change-Id: Ibeb7fd0ec1ce4f053a16066ccc8c2dd93c6f659e
Co-Authored-By: James E. Blair <jeblair@redhat.com>
diff --git a/doc/source/user/config.rst b/doc/source/user/config.rst
index 7ccce28..4898e17 100644
--- a/doc/source/user/config.rst
+++ b/doc/source/user/config.rst
@@ -438,7 +438,7 @@
jobs on the system should have, progressing through stages of
specialization before arriving at a particular job. A job may inherit
from any other job in any project (however, if the other job is marked
-as ``final``, some attributes may not be overidden).
+as :attr:`job.final`, jobs may not inherit from it).
A job with no parent is called a *base job* and may only be defined in
a :term:`config-project`. Every other job must have a parent, and so
@@ -452,7 +452,8 @@
These may have different selection criteria which indicate to Zuul
that, for instance, the job should behave differently on a different
git branch. Unlike inheritance, all job variants must be defined in
-the same project.
+the same project. Some attributes of jobs marked :attr:`job.final`
+may not be overidden
When Zuul decides to run a job, it performs a process known as
freezing the job. Because any number of job variants may be
@@ -529,6 +530,14 @@
to auto-document Zuul jobs (in which case it is interpreted as
ReStructuredText.
+ .. attr:: final
+ :default: false
+
+ To prevent other jobs from inheriting from this job, and also to
+ prevent changing execution-related attributes when this job is
+ specified in a project's pipeline, set this attribute to
+ ``true``.
+
.. attr:: success-message
:default: SUCCESS
diff --git a/tests/fixtures/config/final/git/common-config/playbooks/job-final.yaml b/tests/fixtures/config/final/git/common-config/playbooks/job-final.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/final/git/common-config/playbooks/job-final.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+ tasks: []
diff --git a/tests/fixtures/config/final/git/common-config/zuul.yaml b/tests/fixtures/config/final/git/common-config/zuul.yaml
new file mode 100644
index 0000000..f08d66e
--- /dev/null
+++ b/tests/fixtures/config/final/git/common-config/zuul.yaml
@@ -0,0 +1,28 @@
+- pipeline:
+ name: check
+ manager: independent
+ trigger:
+ gerrit:
+ - event: patchset-created
+ success:
+ gerrit:
+ Verified: 1
+ failure:
+ gerrit:
+ Verified: -1
+
+- job:
+ name: base
+ parent: null
+
+- job:
+ name: job-final
+ final: true
+ vars:
+ dont_override_this: dummy
+
+- project:
+ name: org/project
+ check:
+ jobs: []
+
diff --git a/tests/fixtures/config/final/git/org_project/README b/tests/fixtures/config/final/git/org_project/README
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/tests/fixtures/config/final/git/org_project/README
@@ -0,0 +1 @@
+test
diff --git a/tests/fixtures/config/final/git/org_project/playbooks/placeholder b/tests/fixtures/config/final/git/org_project/playbooks/placeholder
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/fixtures/config/final/git/org_project/playbooks/placeholder
diff --git a/tests/fixtures/config/final/main.yaml b/tests/fixtures/config/final/main.yaml
new file mode 100644
index 0000000..208e274
--- /dev/null
+++ b/tests/fixtures/config/final/main.yaml
@@ -0,0 +1,8 @@
+- tenant:
+ name: tenant-one
+ source:
+ gerrit:
+ config-projects:
+ - common-config
+ untrusted-projects:
+ - org/project
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index 74d72e7..5e3a6fc 100755
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -67,6 +67,89 @@
"not affect tenant one")
+class TestFinal(ZuulTestCase):
+
+ tenant_config_file = 'config/final/main.yaml'
+
+ def test_final_variant_ok(self):
+ # test clean usage of final parent job
+ in_repo_conf = textwrap.dedent(
+ """
+ - project:
+ name: org/project
+ check:
+ jobs:
+ - job-final
+ """)
+
+ 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_final_variant_error(self):
+ # test misuse of final parent job
+ in_repo_conf = textwrap.dedent(
+ """
+ - project:
+ name: org/project
+ check:
+ jobs:
+ - job-final:
+ vars:
+ dont_override_this: bar
+ """)
+ 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 modify final job', A.messages[0])
+
+ def test_final_inheritance(self):
+ # test misuse of final parent job
+ in_repo_conf = textwrap.dedent(
+ """
+ - job:
+ name: project-test
+ parent: job-final
+
+ - project:
+ name: org/project
+ check:
+ jobs:
+ - project-test
+ """)
+
+ in_repo_playbook = textwrap.dedent(
+ """
+ - hosts: all
+ tasks: []
+ """)
+
+ file_dict = {'.zuul.yaml': in_repo_conf,
+ 'playbooks/project-test.yaml': in_repo_playbook}
+ 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 inherit from final job', A.messages[0])
+
+
class TestInRepoConfig(ZuulTestCase):
# A temporary class to hold new tests while others are disabled
diff --git a/zuul/configloader.py b/zuul/configloader.py
index 708a132..86459b0 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -341,6 +341,7 @@
job = {vs.Required('name'): str,
'parent': vs.Any(str, None),
+ 'final': bool,
'failure-message': str,
'success-message': str,
'failure-url': str,
@@ -374,6 +375,7 @@
return vs.Schema(job)
simple_attributes = [
+ 'final',
'timeout',
'workspace',
'voting',
diff --git a/zuul/model.py b/zuul/model.py
index 092c0ed..cf57851 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -917,6 +917,10 @@
if not isinstance(other, Job):
raise Exception("Job unable to inherit from %s" % (other,))
+ if other.final:
+ raise Exception("Unable to inherit from final job %s" %
+ (repr(other),))
+
# copy all attributes
for k in self.inheritable_attributes:
if (other._get(k) is not None):