Merge "Add support for marking module results uninteresting" into feature/zuulv3
diff --git a/.zuul.yaml b/.zuul.yaml
index d3be81b..e8b070f 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -7,8 +7,3 @@
- tox-linters
- tox-py35
- tox-tarball
- - zuul-tox-docs
- - zuul-tox-cover
- - zuul-tox-linters
- - zuul-tox-py35
- - zuul-tox-tarball
diff --git a/doc/source/admin/components.rst b/doc/source/admin/components.rst
index b91e7e7..a24b833 100644
--- a/doc/source/admin/components.rst
+++ b/doc/source/admin/components.rst
@@ -54,18 +54,14 @@
zookeeper
"""""""""
+.. NOTE: this is a white lie at this point, since only the scheduler
+ uses this, however, we expect other components to use it later, so
+ it's reasonable for admins to plan for this now.
+
**hosts**
A list of zookeeper hosts for Zuul to use when communicating with
Nodepool. ``hosts=zk1.example.com,zk2.example.com,zk3.example.com``
-zuul
-""""
-
-**status_url**
- URL that will be posted in Zuul comments made to changes when
- starting jobs for a change. Used by zuul-scheduler only.
- ``status_url=https://zuul.example.com/status``
-
Scheduler
---------
@@ -126,6 +122,11 @@
optional value and ``1`` is used by default.
``status_expiry=1``
+**status_url**
+ URL that will be posted in Zuul comments made to changes when
+ starting jobs for a change. Used by zuul-scheduler only.
+ ``status_url=https://zuul.example.com/status``
+
scheduler
"""""""""
diff --git a/doc/source/admin/drivers/gerrit.rst b/doc/source/admin/drivers/gerrit.rst
index 470b4e8..29e136b 100644
--- a/doc/source/admin/drivers/gerrit.rst
+++ b/doc/source/admin/drivers/gerrit.rst
@@ -35,12 +35,12 @@
**canonical_hostname**
The canonical hostname associated with the git repos on the Gerrit
server. Defaults to the value of **server**. This is used to
- identify repos from this connection by name and in preparing repos
- on the filesystem for use by jobs. This only needs to be set in the
- case where the canonical public location of the git repos is not the
- same as the Gerrit server and it would be incorrect to refer to
- those repos in configuration and build scripts using the Gerrit
- server hostname.
+ identify projects from this connection by name and in preparing
+ repos on the filesystem for use by jobs. Note that Zuul will still
+ only communicate with the Gerrit server identified by **server**;
+ this option is useful if users customarily use a different hostname
+ to clone or pull git repos so that when Zuul places them in the
+ job's working directory, they appear under this directory name.
``canonical_hostname=git.example.com``
**port**
diff --git a/doc/source/admin/drivers/github.rst b/doc/source/admin/drivers/github.rst
index 0cbf895..9740292 100644
--- a/doc/source/admin/drivers/github.rst
+++ b/doc/source/admin/drivers/github.rst
@@ -41,20 +41,20 @@
Path to SSH key to use when cloning github repositories.
``sshkey=/home/zuul/.ssh/id_rsa``
-**git_host**
+**server**
Optional: Hostname of the github install (such as a GitHub Enterprise)
If not specified, defaults to ``github.com``
- ``git_host=github.myenterprise.com``
+ ``server=github.myenterprise.com``
**canonical_hostname**
The canonical hostname associated with the git repos on the GitHub
- server. Defaults to the value of **git_host**. This is used to
- identify repos from this connection by name and in preparing repos
- on the filesystem for use by jobs. This only needs to be set in the
- case where the canonical public location of the git repos is not the
- same as the GitHub server and it would be incorrect to refer to
- those repos in configuration and build scripts using the GitHub
- server hostname.
+ server. Defaults to the value of **server**. This is used to
+ identify projects from this connection by name and in preparing
+ repos on the filesystem for use by jobs. Note that Zuul will still
+ only communicate with the GitHub server identified by **server**;
+ this option is useful if users customarily use a different hostname
+ to clone or pull git repos so that when Zuul places them in the
+ job's working directory, they appear under this directory name.
``canonical_hostname=git.example.com``
Trigger Configuration
diff --git a/doc/source/user/config.rst b/doc/source/user/config.rst
index e7226e9..0b2b5d4 100644
--- a/doc/source/user/config.rst
+++ b/doc/source/user/config.rst
@@ -99,7 +99,7 @@
+1, or if at least one of them fails, a -1::
- pipeline:
- name: check
+ name: check
manager: independent
trigger:
my_gerrit:
@@ -164,6 +164,17 @@
For more detail on the theory and operation of Zuul's dependent
pipeline manager, see: :doc:`gating`.
+**allow-secrets**
+ This is a boolean which can be used to prevent jobs which require
+ secrets from running in this pipeline. Some pipelines run on
+ proposed changes and therefore execute code which has not yet been
+ reviewed. In such a case, allowing a job to use a secret could
+ result in that secret being exposed. The default is False, meaning
+ that in order to run jobs with secrets, this must be explicitly
+ enabled on each Pipeline where that is safe.
+
+ For more information, see :ref:`secret`.
+
**description**
This field may be used to provide a textual description of the
pipeline. It may appear in the status page or in documentation.
diff --git a/tests/base.py b/tests/base.py
index 617169a..921fcd1 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -554,7 +554,7 @@
def __init__(self, github, number, project, branch,
subject, upstream_root, files=[], number_of_commits=1,
- writers=[], body=''):
+ writers=[], body=None):
"""Creates a new PR with several commits.
Sends an event about opened PR."""
self.github = github
@@ -880,7 +880,7 @@
self.reports = []
def openFakePullRequest(self, project, branch, subject, files=[],
- body=''):
+ body=None):
self.pr_number += 1
pull_request = FakeGithubPullRequest(
self, self.pr_number, project, branch, subject, self.upstream_root,
@@ -922,7 +922,7 @@
'http://localhost:%s/connection/%s/payload'
% (port, self.connection_name),
data=payload, headers=headers)
- urllib.request.urlopen(req)
+ return urllib.request.urlopen(req)
def getPull(self, project, number):
pr = self.pull_requests[number - 1]
@@ -1048,10 +1048,14 @@
def _getNeededByFromPR(self, change):
prs = []
pattern = re.compile(r"Depends-On.*https://%s/%s/pull/%s" %
- (self.git_host, change.project.name,
+ (self.server, change.project.name,
change.number))
for pr in self.pull_requests:
- if pattern.search(pr.body):
+ if not pr.body:
+ body = ''
+ else:
+ body = pr.body
+ if pattern.search(body):
# Get our version of a pull so that it's a dict
pull = self.getPull(pr.project, pr.number)
prs.append(pull)
diff --git a/tests/fixtures/config/ansible/git/common-config/playbooks/post-broken.yaml b/tests/fixtures/config/ansible/git/common-config/playbooks/post-broken.yaml
new file mode 100644
index 0000000..cf61187
--- /dev/null
+++ b/tests/fixtures/config/ansible/git/common-config/playbooks/post-broken.yaml
@@ -0,0 +1,5 @@
+- hosts: all
+ tasks:
+ - shell: |+
+ echo "I am broken"
+ exit 1
diff --git a/tests/fixtures/config/ansible/git/common-config/zuul.yaml b/tests/fixtures/config/ansible/git/common-config/zuul.yaml
index fd3fc6d..aa57d08 100644
--- a/tests/fixtures/config/ansible/git/common-config/zuul.yaml
+++ b/tests/fixtures/config/ansible/git/common-config/zuul.yaml
@@ -48,8 +48,13 @@
Z3QSO1NjbBxWnaHKZYT7nkrJm8AMCgZU0ZArFLpaufKCeiK5ECSsDxic4FIsY1OkWT42qEUfL0Wd
+150AKGNZpPJnnP3QYY4W/MWcKH/zdO400+zWN52WevbSqZy90tqKDJrBkMl1ydqbuw1E4ZHvIs=
+- job:
+ name: base-urls
+ success-url: https://success.example.com/zuul-logs/{build.uuid}/
+ failure-url: https://failure.example.com/zuul-logs/{build.uuid}/
- job:
+ parent: base-urls
name: python27
pre-run: playbooks/pre
post-run: playbooks/post
@@ -74,5 +79,11 @@
label: ubuntu-xenial
- job:
+ parent: base-urls
name: hello
post-run: playbooks/hello-post
+
+- job:
+ parent: python27
+ name: failpost
+ post-run: playbooks/post-broken
diff --git a/tests/fixtures/config/ansible/git/org_project/.zuul.yaml b/tests/fixtures/config/ansible/git/org_project/.zuul.yaml
index ca734c5..e87d988 100644
--- a/tests/fixtures/config/ansible/git/org_project/.zuul.yaml
+++ b/tests/fixtures/config/ansible/git/org_project/.zuul.yaml
@@ -15,3 +15,4 @@
- check-vars
- timeout
- hello-world
+ - failpost
diff --git a/tests/fixtures/zuul-connections-merger.conf b/tests/fixtures/zuul-connections-merger.conf
index 4499493..df465d5 100644
--- a/tests/fixtures/zuul-connections-merger.conf
+++ b/tests/fixtures/zuul-connections-merger.conf
@@ -1,7 +1,7 @@
[gearman]
server=127.0.0.1
-[zuul]
+[webapp]
status_url=http://zuul.example.com/status
[merger]
diff --git a/tests/fixtures/zuul-github-driver.conf b/tests/fixtures/zuul-github-driver.conf
index dc28f98..3d61ab6 100644
--- a/tests/fixtures/zuul-github-driver.conf
+++ b/tests/fixtures/zuul-github-driver.conf
@@ -1,7 +1,7 @@
[gearman]
server=127.0.0.1
-[zuul]
+[webapp]
status_url=http://zuul.example.com/status/#{change.number},{change.patchset}
[merger]
@@ -23,4 +23,4 @@
[connection github_ent]
driver=github
sshkey=/home/zuul/.ssh/id_rsa
-git_host=github.enterprise.io
+server=github.enterprise.io
diff --git a/tests/fixtures/zuul-push-reqs.conf b/tests/fixtures/zuul-push-reqs.conf
index c5272aa..4faac13 100644
--- a/tests/fixtures/zuul-push-reqs.conf
+++ b/tests/fixtures/zuul-push-reqs.conf
@@ -1,7 +1,7 @@
[gearman]
server=127.0.0.1
-[zuul]
+[webapp]
status_url=http://zuul.example.com/status
[merger]
diff --git a/tests/unit/test_bubblewrap.py b/tests/unit/test_bubblewrap.py
index 675221e..d94b3f2 100644
--- a/tests/unit/test_bubblewrap.py
+++ b/tests/unit/test_bubblewrap.py
@@ -15,10 +15,12 @@
import subprocess
import tempfile
import testtools
+import time
import os
from zuul.driver import bubblewrap
from zuul.executor.server import SshAgent
+from tests.base import iterate_timeout
class TestBubblewrap(testtools.TestCase):
@@ -61,12 +63,15 @@
po = bwrap.getPopen(work_dir=work_dir,
ansible_dir=ansible_dir,
ssh_auth_sock=ssh_agent.env['SSH_AUTH_SOCK'])
- leak_time = 7
+ leak_time = 60
# Use hexadecimal notation to avoid false-positive
true_proc = po(['bash', '-c', 'sleep 0x%X & disown' % leak_time])
self.assertEqual(0, true_proc.wait())
cmdline = "sleep\x000x%X\x00" % leak_time
- sleep_proc = [pid for pid in os.listdir("/proc") if
- os.path.isfile("/proc/%s/cmdline" % pid) and
- open("/proc/%s/cmdline" % pid).read() == cmdline]
- self.assertEqual(len(sleep_proc), 0, "Processes leaked")
+ for x in iterate_timeout(30, "process to exit"):
+ sleep_proc = [pid for pid in os.listdir("/proc") if
+ os.path.isfile("/proc/%s/cmdline" % pid) and
+ open("/proc/%s/cmdline" % pid).read() == cmdline]
+ if not sleep_proc:
+ break
+ time.sleep(1)
diff --git a/tests/unit/test_github_driver.py b/tests/unit/test_github_driver.py
index a19073c..f360866 100644
--- a/tests/unit/test_github_driver.py
+++ b/tests/unit/test_github_driver.py
@@ -14,6 +14,7 @@
import re
from testtools.matchers import MatchesRegex, StartsWith
+import urllib
import time
from tests.base import ZuulTestCase, simple_layout, random_sha1
@@ -584,3 +585,18 @@
new = self.sched.tenant_last_reconfigured.get('tenant-one', 0)
# New timestamp should be greater than the old timestamp
self.assertLess(old, new)
+
+ @simple_layout('layouts/basic-github.yaml', driver='github')
+ def test_ping_event(self):
+ # Test valid ping
+ pevent = {'repository': {'full_name': 'org/project'}}
+ req = self.fake_github.emitEvent(('ping', pevent))
+ self.assertEqual(req.status, 200, "Ping event didn't succeed")
+
+ # Test invalid ping
+ pevent = {'repository': {'full_name': 'unknown-project'}}
+ self.assertRaises(
+ urllib.error.HTTPError,
+ self.fake_github.emitEvent,
+ ('ping', pevent),
+ )
diff --git a/tests/unit/test_github_requirements.py b/tests/unit/test_github_requirements.py
index 135f7ab..f125d1e 100644
--- a/tests/unit/test_github_requirements.py
+++ b/tests/unit/test_github_requirements.py
@@ -240,13 +240,10 @@
# The first negative review from derp should not cause it to be
# enqueued
- for i in range(1, 4):
- submitted_at = time.time() - 72 * 60 * 60
- A.addReview('derp', 'CHANGES_REQUESTED',
- submitted_at)
- self.fake_github.emitEvent(comment)
- self.waitUntilSettled()
- self.assertEqual(len(self.history), 0)
+ A.addReview('derp', 'CHANGES_REQUESTED')
+ self.fake_github.emitEvent(comment)
+ self.waitUntilSettled()
+ self.assertEqual(len(self.history), 0)
# A positive review from derp should cause it to be enqueued
A.addReview('derp', 'APPROVED')
@@ -256,6 +253,37 @@
self.assertEqual(self.history[0].name, 'project5-reviewuserstate')
@simple_layout('layouts/requirements-github.yaml', driver='github')
+ def test_pipeline_require_review_comment_masked(self):
+ "Test pipeline requirement: review comments on top of votes"
+
+ A = self.fake_github.openFakePullRequest('org/project5', 'master', 'A')
+ # Add derp to writers
+ A.writers.append('derp')
+ # A comment event that we will keep submitting to trigger
+ comment = A.getCommentAddedEvent('test me')
+ self.fake_github.emitEvent(comment)
+ self.waitUntilSettled()
+ # No positive review from derp so should not be enqueued
+ self.assertEqual(len(self.history), 0)
+
+ # The first negative review from derp should not cause it to be
+ # enqueued
+ A.addReview('derp', 'CHANGES_REQUESTED')
+ self.fake_github.emitEvent(comment)
+ self.waitUntilSettled()
+ self.assertEqual(len(self.history), 0)
+
+ # A positive review is required, so provide it
+ A.addReview('derp', 'APPROVED')
+
+ # Add a comment review on top to make sure we can still enqueue
+ A.addReview('derp', 'COMMENTED')
+ self.fake_github.emitEvent(comment)
+ self.waitUntilSettled()
+ self.assertEqual(len(self.history), 1)
+ self.assertEqual(self.history[0].name, 'project5-reviewuserstate')
+
+ @simple_layout('layouts/requirements-github.yaml', driver='github')
def test_require_review_newer_than(self):
A = self.fake_github.openFakePullRequest('org/project6', 'master', 'A')
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index 7c5fa70..327f745 100644
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -496,39 +496,52 @@
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
- build = self.getJobFromHistory('timeout')
- self.assertEqual(build.result, 'TIMED_OUT')
- build = self.getJobFromHistory('faillocal')
- self.assertEqual(build.result, 'FAILURE')
- build = self.getJobFromHistory('check-vars')
- self.assertEqual(build.result, 'SUCCESS')
- build = self.getJobFromHistory('hello-world')
- self.assertEqual(build.result, 'SUCCESS')
- build = self.getJobFromHistory('python27')
- self.assertEqual(build.result, 'SUCCESS')
- flag_path = os.path.join(self.test_root, build.uuid + '.flag')
+ build_timeout = self.getJobFromHistory('timeout')
+ self.assertEqual(build_timeout.result, 'TIMED_OUT')
+ build_faillocal = self.getJobFromHistory('faillocal')
+ self.assertEqual(build_faillocal.result, 'FAILURE')
+ build_failpost = self.getJobFromHistory('failpost')
+ self.assertEqual(build_failpost.result, 'POST_FAILURE')
+ build_check_vars = self.getJobFromHistory('check-vars')
+ self.assertEqual(build_check_vars.result, 'SUCCESS')
+ build_hello = self.getJobFromHistory('hello-world')
+ self.assertEqual(build_hello.result, 'SUCCESS')
+ build_python27 = self.getJobFromHistory('python27')
+ self.assertEqual(build_python27.result, 'SUCCESS')
+ flag_path = os.path.join(self.test_root, build_python27.uuid + '.flag')
self.assertTrue(os.path.exists(flag_path))
- copied_path = os.path.join(self.test_root, build.uuid +
+ copied_path = os.path.join(self.test_root, build_python27.uuid +
'.copied')
self.assertTrue(os.path.exists(copied_path))
- failed_path = os.path.join(self.test_root, build.uuid +
+ failed_path = os.path.join(self.test_root, build_python27.uuid +
'.failed')
self.assertFalse(os.path.exists(failed_path))
- pre_flag_path = os.path.join(self.test_root, build.uuid +
+ pre_flag_path = os.path.join(self.test_root, build_python27.uuid +
'.pre.flag')
self.assertTrue(os.path.exists(pre_flag_path))
- post_flag_path = os.path.join(self.test_root, build.uuid +
+ post_flag_path = os.path.join(self.test_root, build_python27.uuid +
'.post.flag')
self.assertTrue(os.path.exists(post_flag_path))
bare_role_flag_path = os.path.join(self.test_root,
- build.uuid + '.bare-role.flag')
+ build_python27.uuid +
+ '.bare-role.flag')
self.assertTrue(os.path.exists(bare_role_flag_path))
secrets_path = os.path.join(self.test_root,
- build.uuid + '.secrets')
+ build_python27.uuid + '.secrets')
with open(secrets_path) as f:
self.assertEqual(f.read(), "test-username test-password")
+ msg = A.messages[0]
+ success = "{} https://success.example.com/zuul-logs/{}"
+ fail = "{} https://failure.example.com/zuul-logs/{}"
+ self.assertIn(success.format("python27", build_python27.uuid), msg)
+ self.assertIn(fail.format("faillocal", build_faillocal.uuid), msg)
+ self.assertIn(success.format("check-vars", build_check_vars.uuid), msg)
+ self.assertIn(success.format("hello-world", build_hello.uuid), msg)
+ self.assertIn(fail.format("timeout", build_timeout.uuid), msg)
+ self.assertIn(fail.format("failpost", build_failpost.uuid), msg)
+
class TestPrePlaybooks(AnsibleZuulTestCase):
# A temporary class to hold new tests while others are disabled
diff --git a/tools/encrypt_secret.py b/tools/encrypt_secret.py
old mode 100644
new mode 100755
index e36b24e..72429e9
--- a/tools/encrypt_secret.py
+++ b/tools/encrypt_secret.py
@@ -13,11 +13,19 @@
# under the License.
import argparse
+import base64
import os
import subprocess
import sys
import tempfile
-import urllib
+
+# we to import Request and urlopen differently for python 2 and 3
+try:
+ from urllib.request import Request
+ from urllib.request import urlopen
+except ImportError:
+ from urllib2 import Request
+ from urllib2 import urlopen
DESCRIPTION = """Encrypt a secret for Zuul.
@@ -50,9 +58,9 @@
"to standard output.")
args = parser.parse_args()
- req = urllib.request.Request("%s/keys/%s/%s.pub" % (
+ req = Request("%s/keys/%s/%s.pub" % (
args.url, args.source, args.project))
- pubkey = urllib.request.urlopen(req)
+ pubkey = urlopen(req)
if args.infile:
with open(args.infile) as f:
@@ -70,18 +78,18 @@
pubkey_file.name],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
- (stdout, stderr) = p.communicate(plaintext)
+ (stdout, stderr) = p.communicate(plaintext.encode("utf-8"))
if p.returncode != 0:
raise Exception("Return code %s from openssl" % p.returncode)
- ciphertext = stdout.encode('base64')
+ ciphertext = base64.b64encode(stdout)
finally:
os.unlink(pubkey_file.name)
if args.outfile:
- with open(args.outfile, "w") as f:
+ with open(args.outfile, "wb") as f:
f.write(ciphertext)
else:
- print(ciphertext)
+ print(ciphertext.decode("utf-8"))
if __name__ == '__main__':
diff --git a/zuul/configloader.py b/zuul/configloader.py
index 84227f8..4246206 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -1134,7 +1134,7 @@
job = merger.getFiles(
project.source.connection.connection_name,
project.name, branch,
- files=['.zuul.yaml'])
+ files=['zuul.yaml', '.zuul.yaml'])
job.source_context = model.SourceContext(
project, branch, '', False)
jobs.append(job)
@@ -1324,15 +1324,16 @@
def _loadDynamicProjectData(self, config, project, files, trusted):
if trusted:
branches = ['master']
- fn = 'zuul.yaml'
else:
branches = project.source.getProjectBranches(project)
- fn = '.zuul.yaml'
for branch in branches:
incdata = None
- data = files.getFile(project.source.connection.connection_name,
- project.name, branch, fn)
+ for fn in ['zuul.yaml', '.zuul.yaml']:
+ data = files.getFile(project.source.connection.connection_name,
+ project.name, branch, fn)
+ if data:
+ break
if data:
source_context = model.SourceContext(project, branch,
fn, trusted)
diff --git a/zuul/driver/bubblewrap/__init__.py b/zuul/driver/bubblewrap/__init__.py
index 95b09e0..5ec2448 100644
--- a/zuul/driver/bubblewrap/__init__.py
+++ b/zuul/driver/bubblewrap/__init__.py
@@ -70,34 +70,11 @@
name = 'bubblewrap'
log = logging.getLogger("zuul.BubblewrapDriver")
- bwrap_command = [
- 'bwrap',
- '--dir', '/tmp',
- '--tmpfs', '/tmp',
- '--dir', '/var',
- '--dir', '/var/tmp',
- '--dir', '/run/user/{uid}',
- '--ro-bind', '/usr', '/usr',
- '--ro-bind', '/lib', '/lib',
- '--ro-bind', '/lib64', '/lib64',
- '--ro-bind', '/bin', '/bin',
- '--ro-bind', '/sbin', '/sbin',
- '--ro-bind', '/etc/resolv.conf', '/etc/resolv.conf',
- '--ro-bind', '{ssh_auth_sock}', '{ssh_auth_sock}',
- '--dir', '{work_dir}',
- '--bind', '{work_dir}', '{work_dir}',
- '--dev', '/dev',
- '--chdir', '{work_dir}',
- '--unshare-all',
- '--share-net',
- '--die-with-parent',
- '--uid', '{uid}',
- '--gid', '{gid}',
- '--file', '{uid_fd}', '/etc/passwd',
- '--file', '{gid_fd}', '/etc/group',
- ]
mounts_map = {'rw': [], 'ro': []}
+ def __init__(self):
+ self.bwrap_command = self._bwrap_command()
+
def reconfigure(self, tenant):
pass
@@ -160,6 +137,38 @@
return wrapped_popen
+ def _bwrap_command(self):
+ bwrap_command = [
+ 'bwrap',
+ '--dir', '/tmp',
+ '--tmpfs', '/tmp',
+ '--dir', '/var',
+ '--dir', '/var/tmp',
+ '--dir', '/run/user/{uid}',
+ '--ro-bind', '/usr', '/usr',
+ '--ro-bind', '/lib', '/lib',
+ '--ro-bind', '/bin', '/bin',
+ '--ro-bind', '/sbin', '/sbin',
+ '--ro-bind', '/etc/resolv.conf', '/etc/resolv.conf',
+ '--ro-bind', '{ssh_auth_sock}', '{ssh_auth_sock}',
+ '--dir', '{work_dir}',
+ '--bind', '{work_dir}', '{work_dir}',
+ '--dev', '/dev',
+ '--chdir', '{work_dir}',
+ '--unshare-all',
+ '--share-net',
+ '--die-with-parent',
+ '--uid', '{uid}',
+ '--gid', '{gid}',
+ '--file', '{uid_fd}', '/etc/passwd',
+ '--file', '{gid_fd}', '/etc/group',
+ ]
+
+ if os.path.isdir('/lib64'):
+ bwrap_command.extend(['--ro-bind', '/lib64', '/lib64'])
+
+ return bwrap_command
+
def main(args=None):
logging.basicConfig(level=logging.DEBUG)
diff --git a/zuul/driver/github/githubconnection.py b/zuul/driver/github/githubconnection.py
index 838cba5..1a9e37b 100644
--- a/zuul/driver/github/githubconnection.py
+++ b/zuul/driver/github/githubconnection.py
@@ -75,6 +75,8 @@
try:
self.__dispatch_event(request)
+ except webob.exc.HTTPNotFound:
+ raise
except:
self.log.exception("Exception handling Github event:")
@@ -92,7 +94,8 @@
except AttributeError:
message = "Unhandled X-Github-Event: {0}".format(event)
self.log.debug(message)
- raise webob.exc.HTTPBadRequest(message)
+ # Returns empty 200 on unhandled events
+ raise webob.exc.HTTPOk()
try:
json_body = request.json_body
@@ -117,6 +120,8 @@
try:
event = method(json_body)
+ except webob.exc.HTTPNotFound:
+ raise
except:
self.log.exception('Exception when handling event:')
event = None
@@ -219,6 +224,14 @@
event.action = body.get('action')
return event
+ def _event_ping(self, body):
+ project_name = body['repository']['full_name']
+ if not self.connection.getProject(project_name):
+ self.log.warning("Ping received for unknown project %s" %
+ project_name)
+ raise webob.exc.HTTPNotFound("Sorry, this project is not "
+ "registered")
+
def _event_status(self, body):
action = body.get('action')
if action == 'pending':
@@ -340,9 +353,9 @@
self._change_cache = {}
self.projects = {}
self.git_ssh_key = self.connection_config.get('sshkey')
- self.git_host = self.connection_config.get('git_host', 'github.com')
+ self.server = self.connection_config.get('server', 'github.com')
self.canonical_hostname = self.connection_config.get(
- 'canonical_hostname', self.git_host)
+ 'canonical_hostname', self.server)
self.source = driver.getSource(self)
self._github = None
@@ -362,7 +375,7 @@
# The regex is based on the connection host. We do not yet support
# cross-connection dependency gathering
self.depends_on_re = re.compile(
- r"^Depends-On: https://%s/.+/.+/pull/[0-9]+$" % self.git_host,
+ r"^Depends-On: https://%s/.+/.+/pull/[0-9]+$" % self.server,
re.MULTILINE | re.IGNORECASE)
def onLoad(self):
@@ -375,8 +388,8 @@
self.unregisterHttpHandler(self.payload_path)
def _createGithubClient(self):
- if self.git_host != 'github.com':
- url = 'https://%s/' % self.git_host
+ if self.server != 'github.com':
+ url = 'https://%s/' % self.server
github = github3.GitHubEnterprise(url)
else:
github = github3.GitHub()
@@ -551,7 +564,7 @@
# This leaves off the protocol, but looks for the specific GitHub
# hostname, the org/project, and the pull request number.
- pattern = 'Depends-On %s/%s/pull/%s' % (self.git_host,
+ pattern = 'Depends-On %s/%s/pull/%s' % (self.server,
change.project.name,
change.number)
query = '%s type:pr is:open in:body' % pattern
@@ -595,6 +608,9 @@
change.number)
change.labels = change.pr.get('labels')
change.body = change.pr.get('body')
+ # ensure body is at least an empty string
+ if not change.body:
+ change.body = ''
if history is None:
history = []
@@ -639,18 +655,18 @@
def getGitUrl(self, project):
if self.git_ssh_key:
- return 'ssh://git@%s/%s.git' % (self.git_host, project)
+ return 'ssh://git@%s/%s.git' % (self.server, project)
if self.app_id:
installation_key = self._get_installation_key(project)
return 'https://x-access-token:%s@%s/%s' % (installation_key,
- self.git_host,
+ self.server,
project)
- return 'https://%s/%s' % (self.git_host, project)
+ return 'https://%s/%s' % (self.server, project)
def getGitwebUrl(self, project, sha=None):
- url = 'https://%s/%s' % (self.git_host, project)
+ url = 'https://%s/%s' % (self.server, project)
if sha is not None:
url += '/commit/%s' % sha
return url
@@ -763,8 +779,19 @@
# if there are multiple reviews per user, keep the newest
# note that this breaks the ability to set the 'older-than'
# option on a review requirement.
+ # BUT do not keep the latest if it's a 'commented' type and the
+ # previous review was 'approved' or 'changes_requested', as
+ # the GitHub model does not change the vote if a comment is
+ # added after the fact. THANKS GITHUB!
if review['grantedOn'] > reviews[user]['grantedOn']:
- reviews[user] = review
+ if (review['type'] == 'commented' and reviews[user]['type']
+ in ('approved', 'changes_requested')):
+ self.log.debug("Discarding comment review %s due to "
+ "an existing vote %s" % (review,
+ reviews[user]))
+ pass
+ else:
+ reviews[user] = review
return reviews.values()
@@ -785,7 +812,7 @@
return GithubUser(self.getGithubClient(), login)
def getUserUri(self, login):
- return 'https://%s/%s' % (self.git_host, login)
+ return 'https://%s/%s' % (self.server, login)
def getRepoPermission(self, project, login):
github = self.getGithubClient(project)
diff --git a/zuul/driver/github/githubreporter.py b/zuul/driver/github/githubreporter.py
index 72087bf..ea41ccd 100644
--- a/zuul/driver/github/githubreporter.py
+++ b/zuul/driver/github/githubreporter.py
@@ -85,8 +85,8 @@
url_pattern = self.config.get('status-url')
if not url_pattern:
sched_config = self.connection.sched.config
- if sched_config.has_option('zuul', 'status_url'):
- url_pattern = sched_config.get('zuul', 'status_url')
+ if sched_config.has_option('webapp', 'status_url'):
+ url_pattern = sched_config.get('webapp', 'status_url')
url = item.formatUrlPattern(url_pattern) if url_pattern else ''
description = ''
diff --git a/zuul/executor/client.py b/zuul/executor/client.py
index aaef34e..3612eae 100644
--- a/zuul/executor/client.py
+++ b/zuul/executor/client.py
@@ -321,9 +321,9 @@
make_project_dict(project,
job_project.override_branch))
projects.add(project)
- for item in all_items:
- if item.change.project not in projects:
- project = item.change.project
+ for i in all_items:
+ if i.change.project not in projects:
+ project = i.change.project
params['projects'].append(make_project_dict(project))
projects.add(project)
@@ -340,12 +340,6 @@
build.__gearman_worker = None
self.builds[uuid] = build
- # NOTE(pabelanger): Rather then looping forever, check to see if job
- # has passed attempts limit.
- if item.current_build_set.getTries(job.name) > job.attempts:
- self.onBuildCompleted(gearman_job, 'RETRY_LIMIT')
- return build
-
if pipeline.precedence == zuul.model.PRECEDENCE_NORMAL:
precedence = gear.PRECEDENCE_NORMAL
elif pipeline.precedence == zuul.model.PRECEDENCE_HIGH:
@@ -420,7 +414,11 @@
if result is None:
result = data.get('result')
if result is None:
- build.retry = True
+ if (build.build_set.getTries(build.job.name) >=
+ build.job.attempts):
+ result = 'RETRY_LIMIT'
+ else:
+ build.retry = True
self.log.info("Build %s complete, result %s" %
(job, result))
self.sched.onBuildCompleted(build, result)
diff --git a/zuul/executor/server.py b/zuul/executor/server.py
index c5d292a..6c390db 100644
--- a/zuul/executor/server.py
+++ b/zuul/executor/server.py
@@ -739,12 +739,11 @@
self.log.exception("Error stopping SSH agent:")
def _execute(self):
- self.log.debug("Job %s: beginning" % (self.job.unique,))
- self.log.debug("Job %s: args: %s" % (self.job.unique,
- self.job.arguments,))
- self.log.debug("Job %s: job root at %s" %
- (self.job.unique, self.jobdir.root))
args = json.loads(self.job.arguments)
+ self.log.debug("Beginning job %s for ref %s" %
+ (self.job.name, args['vars']['zuul']['ref']))
+ self.log.debug("Args: %s" % (self.job.arguments,))
+ self.log.debug("Job root: %s" % (self.jobdir.root,))
tasks = []
projects = set()
diff --git a/zuul/model.py b/zuul/model.py
index dc04e59..436a9c8 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -1620,7 +1620,7 @@
result = job.success_message
if job.success_url:
pattern = job.success_url
- elif result == 'FAILURE':
+ else:
if job.failure_message:
result = job.failure_message
if job.failure_url:
diff --git a/zuul/reporter/__init__.py b/zuul/reporter/__init__.py
index 0ac5766..95b9208 100644
--- a/zuul/reporter/__init__.py
+++ b/zuul/reporter/__init__.py
@@ -14,6 +14,7 @@
import abc
import logging
+from zuul.lib.config import get_default
class BaseReporter(object, metaclass=abc.ABCMeta):
@@ -69,10 +70,8 @@
return ret
def _formatItemReportStart(self, item, with_jobs=True):
- status_url = ''
- if self.connection.sched.config.has_option('zuul', 'status_url'):
- status_url = self.connection.sched.config.get('zuul',
- 'status_url')
+ status_url = get_default(self.connection.sched.config,
+ 'webapp', 'status_url', '')
return item.pipeline.start_message.format(pipeline=item.pipeline,
status_url=status_url)
diff --git a/zuul/scheduler.py b/zuul/scheduler.py
index a0a93ca..fe6a673 100644
--- a/zuul/scheduler.py
+++ b/zuul/scheduler.py
@@ -547,6 +547,8 @@
else:
items_to_remove.append(item)
for item in items_to_remove:
+ self.log.warning(
+ "Removing item %s during reconfiguration" % (item,))
for build in item.current_build_set.getBuilds():
builds_to_cancel.append(build)
for build in builds_to_cancel: