Merge "Support emitting warnings via zuul_return"

This commit is contained in:
Zuul 2021-02-24 17:59:03 +00:00 committed by Gerrit Code Review
commit c51bfeb8cf
12 changed files with 134 additions and 24 deletions

View File

@ -884,6 +884,25 @@ zuul.child_jobs is empty, all jobs will be marked as SKIPPED. Invalid dependent
are stripped and ignored, if only invalid jobs are listed it is the same as are stripped and ignored, if only invalid jobs are listed it is the same as
providing an empty list to zuul.child_jobs. 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 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

@ -6818,3 +6818,24 @@ class TestDefaultAnsibleVersion(AnsibleZuulTestCase):
dict(name='ansible-28', result='SUCCESS', changes='1,1'), dict(name='ansible-28', result='SUCCESS', changes='1,1'),
dict(name='ansible-29', result='SUCCESS', changes='1,1'), dict(name='ansible-29', result='SUCCESS', changes='1,1'),
], ordered=False) ], 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 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): def merge_data(dict_a, dict_b):
""" """
Merge dict_a into dict_b, handling any special cases for zuul variables 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) file_comments = merge_file_comments(dict_a, dict_b)
warnings = merge_zuul_list(dict_a, dict_b, 'warnings')
merge_dict(dict_a, dict_b) merge_dict(dict_a, dict_b)
if artifacts: if artifacts:
dict_b.setdefault('zuul', {})['artifacts'] = artifacts dict_b.setdefault('zuul', {})['artifacts'] = artifacts
if file_comments: if file_comments:
dict_b.setdefault("zuul", {})["file_comments"] = file_comments dict_b.setdefault("zuul", {})["file_comments"] = file_comments
if warnings:
dict_b.setdefault('zuul', {})['warnings'] = warnings
return dict_b 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): def merge_file_comments(dict_a, dict_b):
"""Merge file_comments from both dictionary. """Merge file_comments from both dictionary.

View File

@ -19,8 +19,8 @@ import time
import voluptuous as v import voluptuous as v
from zuul.lib.logutil import get_annotated_logger 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.reporter import BaseReporter
from zuul.lib.artifacts import get_artifacts_from_result_data
class SQLReporter(BaseReporter): class SQLReporter(BaseReporter):

View File

@ -37,6 +37,7 @@ from urllib.parse import urlsplit
from zuul.lib.ansible import AnsibleManager from zuul.lib.ansible import AnsibleManager
from zuul.lib.gearworker import ZuulGearWorker 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.yamlutil import yaml
from zuul.lib.config import get_default from zuul.lib.config import get_default
from zuul.lib.logutil import get_annotated_logger from zuul.lib.logutil import get_annotated_logger
@ -1178,6 +1179,7 @@ class AnsibleJob(object):
data = self.getResultData() data = self.getResultData()
warnings = [] warnings = []
self.mapLines(merger, args, data, item_commit, 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, result_data = json.dumps(dict(result=result,
warnings=warnings, warnings=warnings,
data=data)) data=data))

View File

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

@ -33,7 +33,7 @@ import jsonpath_rw
from zuul import change_matcher from zuul import change_matcher
from zuul.lib.config import get_default 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.logutil import get_annotated_logger
from zuul.lib.capabilities import capabilities_registry from zuul.lib.capabilities import capabilities_registry