Support emitting warnings via zuul_return

We already have the infrastructure in place for adding warnings to the
reporting. Plumb that through to zuul_return so jobs can do that on
purpose as well. An example could be a post playbook that analyzes
performance statistics and emits a warning about inefficient usage of
the build node resources.

Change-Id: I4c3b85dc8f4c69c55cbc6168b8a66afce8b50a97
This commit is contained in:
Tobias Henkel 2019-04-10 14:49:38 +02:00
parent ebb0b222ca
commit 9879cab0d1
No known key found for this signature in database
GPG Key ID: 03750DEC158E5FA2
12 changed files with 134 additions and 24 deletions

View File

@ -865,6 +865,25 @@ zuul.child_jobs is empty, all jobs will be marked as SKIPPED. Invalid child jobs
are stripped and ignored, if only invalid jobs are listed it is the same as
providing an empty list to zuul.child_jobs.
Leaving warnings
~~~~~~~~~~~~~~~~
A job can leave warnings that will be appended to the comment zuul leaves on
the change. Use *zuul_return* to add a list of warnings. For example:
.. code-block:: yaml
tasks:
- zuul_return:
data:
zuul:
warnings:
- This warning will be posted on the change.
If *zuul_return* is invoked multiple times (e.g., via multiple
playbooks), then the elements of **zuul.warnings** from each
invocation will be appended.
Leaving file comments
~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,5 @@
---
features:
- |
A job now can leave warning messages on the change using zuul_return.
See :ref:`return_values` for examples.

View File

@ -0,0 +1,17 @@
- pipeline:
name: check
manager: independent
post-review: true
trigger:
gerrit:
- event: patchset-created
success:
gerrit:
Verified: 1
failure:
gerrit:
Verified: -1
- job:
name: base
parent: null

View File

@ -0,0 +1,14 @@
- hosts: localhost
tasks:
- name: Emit first warning
zuul_return:
data:
zuul:
warnings:
- This is the first warning
- name: Emit second warning
zuul_return:
data:
zuul:
warnings:
- This is the second warning

View File

@ -0,0 +1,8 @@
- job:
name: emit-warnings
run: playbooks/warnings.yaml
- project:
check:
jobs:
- emit-warnings

View File

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

View File

@ -6622,3 +6622,24 @@ class TestDefaultAnsibleVersion(AnsibleZuulTestCase):
dict(name='ansible-28', result='SUCCESS', changes='1,1'),
dict(name='ansible-29', result='SUCCESS', changes='1,1'),
], ordered=False)
class TestReturnWarnings(AnsibleZuulTestCase):
tenant_config_file = 'config/return-warnings/main.yaml'
def test_return_warnings(self):
"""
Tests that jobs can emit custom warnings that get reported.
"""
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertHistory([
dict(name='emit-warnings', result='SUCCESS', changes='1,1'),
])
self.assertTrue(A.reported)
self.assertIn('This is the first warning', A.messages[0])
self.assertIn('This is the second warning', A.messages[0])

View File

@ -39,36 +39,33 @@ def merge_dict(dict_a, dict_b):
return dict_b
def merge_zuul_list(dict_a, dict_b, key):
value_a = dict_a.get('zuul', {}).get(key, [])
value_b = dict_b.get('zuul', {}).get(key, [])
if not isinstance(value_a, list):
value_a = []
if not isinstance(value_b, list):
value_b = []
return value_a + value_b
def merge_data(dict_a, dict_b):
"""
Merge dict_a into dict_b, handling any special cases for zuul variables
"""
artifacts = merge_artifacts(dict_a, dict_b)
artifacts = merge_zuul_list(dict_a, dict_b, 'artifacts')
file_comments = merge_file_comments(dict_a, dict_b)
warnings = merge_zuul_list(dict_a, dict_b, 'warnings')
merge_dict(dict_a, dict_b)
if artifacts:
dict_b.setdefault('zuul', {})['artifacts'] = artifacts
if file_comments:
dict_b.setdefault("zuul", {})["file_comments"] = file_comments
if warnings:
dict_b.setdefault('zuul', {})['warnings'] = warnings
return dict_b
def merge_artifacts(dict_a, dict_b):
"""Merge artifacts from both dictionary
The artifacts from both dictionaries will be merged (additive) into a new
list.
"""
artifacts_a = dict_a.get('zuul', {}).get("artifacts", [])
if not isinstance(artifacts_a, list):
artifacts_a = []
artifacts_b = dict_b.get('zuul', {}).get("artifacts", [])
if not isinstance(artifacts_b, list):
artifacts_b = []
artifacts = artifacts_a + artifacts_b
return artifacts
def merge_file_comments(dict_a, dict_b):
"""Merge file_comments from both dictionary.

View File

@ -19,8 +19,8 @@ import time
import voluptuous as v
from zuul.lib.logutil import get_annotated_logger
from zuul.lib.result_data import get_artifacts_from_result_data
from zuul.reporter import BaseReporter
from zuul.lib.artifacts import get_artifacts_from_result_data
class SQLReporter(BaseReporter):

View File

@ -37,6 +37,7 @@ from urllib.parse import urlsplit
from zuul.lib.ansible import AnsibleManager
from zuul.lib.gearworker import ZuulGearWorker
from zuul.lib.result_data import get_warnings_from_result_data
from zuul.lib.yamlutil import yaml
from zuul.lib.config import get_default
from zuul.lib.logutil import get_annotated_logger
@ -1175,6 +1176,7 @@ class AnsibleJob(object):
data = self.getResultData()
warnings = []
self.mapLines(merger, args, data, item_commit, warnings)
warnings.extend(get_warnings_from_result_data(data, logger=self.log))
result_data = json.dumps(dict(result=result,
warnings=warnings,
data=data))

View File

@ -21,7 +21,7 @@ artifact = {
'metadata': dict,
}
zuul_data = {
artifact_data = {
'zuul': {
'log_url': str,
'artifacts': [artifact],
@ -30,12 +30,22 @@ zuul_data = {
v.Extra: object,
}
artifact_schema = v.Schema(zuul_data)
warning_data = {
'zuul': {
'log_url': str,
'warnings': [str],
v.Extra: object,
},
v.Extra: object,
}
artifact_schema = v.Schema(artifact_data)
warning_schema = v.Schema(warning_data)
def validate_artifact_schema(data):
def validate_schema(data, schema):
try:
artifact_schema(data)
schema(data)
except Exception:
return False
return True
@ -43,7 +53,7 @@ def validate_artifact_schema(data):
def get_artifacts_from_result_data(result_data, logger=None):
ret = []
if validate_artifact_schema(result_data):
if validate_schema(result_data, artifact_schema):
artifacts = result_data.get('zuul', {}).get(
'artifacts', [])
default_url = result_data.get('zuul', {}).get(
@ -71,3 +81,12 @@ def get_artifacts_from_result_data(result_data, logger=None):
logger.debug("Result data did not pass artifact schema "
"validation: %s", result_data)
return ret
def get_warnings_from_result_data(result_data, logger=None):
if validate_schema(result_data, warning_schema):
return result_data.get('zuul', {}).get('warnings', [])
else:
if logger:
logger.debug("Result data did not pass warnings schema "
"validation: %s", result_data)

View File

@ -31,7 +31,7 @@ import jsonpath_rw
from zuul import change_matcher
from zuul.lib.config import get_default
from zuul.lib.artifacts import get_artifacts_from_result_data
from zuul.lib.result_data import get_artifacts_from_result_data
from zuul.lib.logutil import get_annotated_logger
from zuul.lib.capabilities import capabilities_registry