Merge "Remove layoutvalidator" into feature/zuulv3

This commit is contained in:
Jenkins 2017-03-07 23:28:00 +00:00 committed by Gerrit Code Review
commit e7800a008b
30 changed files with 0 additions and 1060 deletions

View File

@ -1,18 +0,0 @@
pipelines:
- name: check
manager: IndependentPipelineManager
trigger:
not_gerrit:
- event: patchset-created
success:
review_gerrit:
verified: 1
failure:
review_gerrit:
verified: -1
projects:
- name: test-org/test
check:
- test-merge
- test-test

View File

@ -1,40 +0,0 @@
pipelines:
- name: check
manager: IndependentPipelineManager
trigger:
review_gerrit:
- event: patchset-created
success:
review_gerrit:
verified: 1
failure:
review_gerrit:
verified: -1
# merge-failure-message needs a string.
merge-failure-message:
- name: gate
manager: DependentPipelineManager
failure-message: Build failed. For information on how to proceed, see http://wiki.example.org/Test_Failures
trigger:
review_gerrit:
- event: comment-added
approval:
- approved: 1
success:
review_gerrit:
verified: 2
submit: true
failure:
review_gerrit:
verified: -2
merge-failure:
start:
review_gerrit:
verified: 0
precedence: high
projects:
- name: org/project
check:
- project-check

View File

@ -1,13 +0,0 @@
pipelines:
- name: 'check'
manager: IndependentPipelineManager
trigger:
review_gerrit:
- event: patchset-created
ref: /some/ref/path
projects:
- name: org/project
merge-mode: cherry-pick
check:
- project-check

View File

@ -1,2 +0,0 @@
# Pipelines completely missing. At least one is required.
pipelines:

View File

@ -1,8 +0,0 @@
pipelines:
- name: check
manager: IndependentPipelineManager
projects:
- name: foo
# merge-mode must be one of merge, merge-resolve, cherry-pick.
merge-mode: foo

View File

@ -1,7 +0,0 @@
pipelines:
# name is required for pipelines
- noname: check
manager: IndependentPipelineManager
projects:
- name: foo

View File

@ -1,8 +0,0 @@
pipelines:
- name: check
# The manager must be one of IndependentPipelineManager
# or DependentPipelineManager
manager: NonexistentPipelineManager
projects:
- name: foo

View File

@ -1,10 +0,0 @@
pipelines:
- name: check
manager: IndependentPipelineManager
trigger:
gerrit:
# non-event is not a valid gerrit event
- event: non-event
projects:
- name: foo

View File

@ -1,11 +0,0 @@
pipelines:
- name: check
manager: IndependentPipelineManager
trigger:
review_gerrit:
# event is a required item but it is missing.
- approval:
- approved: 1
projects:
- name: foo

View File

@ -1,11 +0,0 @@
pipelines:
- name: check
manager: IndependentPipelineManager
trigger:
review_gerrit:
- event: comment-added
# approved is not a valid entry. Should be approval.
approved: 1
projects:
- name: foo

View File

@ -1,6 +0,0 @@
pipelines:
# The pipeline must have a name.
- manager: IndependentPipelineManager
projects:
- name: foo

View File

@ -1,6 +0,0 @@
pipelines:
# The pipeline must have a manager
- name: check
projects:
- name: foo

View File

@ -1,9 +0,0 @@
pipelines:
# Names must be unique.
- name: check
manager: IndependentPipelineManager
- name: check
manager: IndependentPipelineManager
projects:
- name: foo

View File

@ -1,10 +0,0 @@
pipelines:
- name: check
manager: IndependentPipelineManager
projects:
- name: foo
# gate pipeline is not defined.
gate:
- test

View File

@ -1,10 +0,0 @@
pipelines:
- name: check
manager: IndependentPipelineManager
projects:
- name: foo
check:
# Indentation is one level too deep on the last line.
- test
- foo

View File

@ -1,21 +0,0 @@
# Template is going to be called but missing a parameter
pipelines:
- name: 'check'
manager: IndependentPipelineManager
require:
open: True
current-patchset: True
approval:
- verified: [1, 2]
username: jenkins
- workflow: 1
reject:
# Reject only takes 'approval', has no need for open etc..
open: True
approval:
- code-review: [-1, -2]
username: core-person
trigger:
review_gerrit:
- event: patchset-created

View File

@ -1,28 +0,0 @@
pipelines:
- name: check
manager: IndependentPipelineManager
trigger:
review_gerrit:
- event: patchset-created
success:
review_gerrit:
verified: 1
failure:
review_gerrit:
verified: -1
jobs:
- name: ^.*$
swift:
- name: logs
- name: ^.*-merge$
# swift requires a name
swift:
container: merge_assets
failure-message: Unable to merge change
projects:
- name: test-org/test
check:
- test-merge
- test-test

View File

@ -1,20 +0,0 @@
# Template is going to be called but missing a parameter
pipelines:
- name: 'check'
manager: IndependentPipelineManager
trigger:
review_gerrit:
- event: patchset-created
project-templates:
- name: template-generic
check:
# Template uses the 'project' parameter' which must be provided
- '{project}-merge'
projects:
- name: organization/project
template:
- name: template-generic
# Here we 'forgot' to pass 'project'

View File

@ -1,23 +0,0 @@
# Template is going to be called with an extra parameter
pipelines:
- name: 'check'
manager: IndependentPipelineManager
trigger:
review_gerrit:
- event: patchset-created
project-templates:
- name: template-generic
check:
# Template only uses the 'project' parameter'
- '{project}-merge'
projects:
- name: organization/project
template:
- name: template-generic
project: 'MyProjectName'
# Feed an extra parameters which is not going to be used
# by the template. That is an error.
extraparam: 'IShouldNotBeSet'

View File

@ -1,10 +0,0 @@
# Template refers to an unexisting pipeline
project-templates:
- name: template-generic
unexisting-pipeline: # pipeline does not exist
projects:
- name: organization/project
template:
- name: template-generic

View File

@ -1,42 +0,0 @@
[gearman]
server=127.0.0.1
[zuul]
layout_config=layout.yaml
url_pattern=http://logs.example.com/{change.number}/{change.patchset}/{pipeline.name}/{job.name}/{build.number}
job_name_in_report=true
[merger]
git_dir=/tmp/zuul-test/git
git_user_email=zuul@example.com
git_user_name=zuul
zuul_url=http://zuul.example.com/p
[swift]
authurl=https://identity.api.example.org/v2.0/
user=username
key=password
tenant_name=" "
default_container=logs
region_name=EXP
logserver_prefix=http://logs.example.org/server.app/
[connection review_gerrit]
driver=gerrit
server=review.example.com
user=jenkins
sshkey=none
[connection other_gerrit]
driver=gerrit
server=review2.example.com
user=jenkins2
sshkey=none
[connection my_smtp]
driver=smtp
server=localhost
port=25
default_from=zuul@example.com
default_to=you@example.com

View File

@ -1,18 +0,0 @@
pipelines:
- name: check
manager: IndependentPipelineManager
source: review_gerrit
trigger:
review_gerrit:
- event: patchset-created
success:
review_gerrit:
verified: 1
failure:
other_gerrit:
verified: -1
projects:
- name: org/project
check:
- project-check

View File

@ -1,102 +0,0 @@
includes:
- python-file: openstack_functions.py
pipelines:
- name: check
manager: IndependentPipelineManager
require:
open: True
current-patchset: True
trigger:
review_gerrit:
- event: patchset-created
- event: comment-added
require-approval:
- verified: [-1, -2]
username: jenkins
approval:
- workflow: 1
success:
review_gerrit:
verified: 1
failure:
review_gerrit:
verified: -1
- name: post
manager: IndependentPipelineManager
trigger:
review_gerrit:
- event: ref-updated
ref: ^(?!refs/).*$
ignore-deletes: True
- name: gate
manager: DependentPipelineManager
success-message: Your change is awesome.
failure-message: Build failed. For information on how to proceed, see http://wiki.example.org/Test_Failures
require:
open: True
current-patchset: True
approval:
- verified: [1, 2]
username: jenkins
- workflow: 1
reject:
approval:
- code-review: [-1, -2]
trigger:
review_gerrit:
- event: comment-added
approval:
- approved: 1
start:
review_gerrit:
verified: 0
success:
review_gerrit:
verified: 2
code-review: 1
submit: true
failure:
review_gerrit:
verified: -2
workinprogress: true
- name: merge-check
manager: IndependentPipelineManager
source: review_gerrit
ignore-dependencies: true
trigger:
zuul:
- event: project-change-merged
merge-failure:
review_gerrit:
verified: -1
jobs:
- name: ^.*-merge$
failure-message: Unable to merge change
hold-following-changes: true
- name: test-merge
parameter-function: devstack_params
- name: test-test
- name: test-merge2
success-pattern: http://logs.example.com/{change.number}/{change.patchset}/{pipeline.name}/{job.name}/{build.number}/success
failure-pattern: http://logs.example.com/{change.number}/{change.patchset}/{pipeline.name}/{job.name}/{build.number}/fail
- name: project-testfile
files:
- 'tools/.*-requires'
projects:
- name: test-org/test
merge-mode: cherry-pick
check:
- test-merge2:
- test-thing1:
- test-thing2
- test-thing3
gate:
- test-thing
post:
- test-post

View File

@ -1,53 +0,0 @@
pipelines:
- name: check
manager: IndependentPipelineManager
merge-failure-message: "Could not merge the change. Please rebase..."
trigger:
review_gerrit:
- event: patchset-created
success:
review_gerrit:
verified: 1
failure:
review_gerrit:
verified: -1
- name: post
manager: IndependentPipelineManager
trigger:
review_gerrit:
- event: ref-updated
ref: ^(?!refs/).*$
merge-failure:
review_gerrit:
verified: -1
- name: gate
manager: DependentPipelineManager
failure-message: Build failed. For information on how to proceed, see http://wiki.example.org/Test_Failures
trigger:
review_gerrit:
- event: comment-added
approval:
- approved: 1
success:
review_gerrit:
verified: 2
submit: true
failure:
review_gerrit:
verified: -2
merge-failure:
review_gerrit:
verified: -1
my_smtp:
to: you@example.com
start:
review_gerrit:
verified: 0
precedence: high
projects:
- name: org/project
check:
- project-check

View File

@ -1,36 +0,0 @@
includes:
- python-file: custom_functions.py
pipelines:
- name: check
manager: IndependentPipelineManager
trigger:
review_gerrit:
- event: comment-added
require-approval:
- username: jenkins
older-than: 48h
- event: comment-added
require-approval:
- email: jenkins@example.com
newer-than: 48h
- event: comment-added
require-approval:
- approved: 1
- event: comment-added
require-approval:
- approved: 1
username: jenkins
email: jenkins@example.com
success:
review_gerrit:
verified: 1
failure:
review_gerrit:
verified: -1
projects:
- name: org/project
merge-mode: cherry-pick
check:
- project-check

View File

@ -1,32 +0,0 @@
pipelines:
- name: check
manager: IndependentPipelineManager
trigger:
review_gerrit:
- event: patchset-created
success:
review_gerrit:
verified: 1
failure:
review_gerrit:
verified: -1
jobs:
- name: ^.*$
swift:
- name: logs
- name: ^.*-merge$
swift:
- name: assets
container: merge_assets
failure-message: Unable to merge change
- name: test-test
swift:
- name: mostly
container: stash
projects:
- name: test-org/test
check:
- test-merge
- test-test

View File

@ -1,17 +0,0 @@
pipelines:
- name: 'check'
manager: IndependentPipelineManager
trigger:
review_gerrit:
- event: patchset-created
project-templates:
- name: template-generic
check:
- '{project}-merge'
projects:
- name: organization/project
template:
- name: template-generic
project: 'myproject'

View File

@ -1,36 +0,0 @@
[gearman]
server=127.0.0.1
[zuul]
layout_config=layout.yaml
url_pattern=http://logs.example.com/{change.number}/{change.patchset}/{pipeline.name}/{job.name}/{build.number}
job_name_in_report=true
[merger]
git_dir=/tmp/zuul-test/git
git_user_email=zuul@example.com
git_user_name=zuul
zuul_url=http://zuul.example.com/p
[swift]
authurl=https://identity.api.example.org/v2.0/
user=username
key=password
tenant_name=" "
default_container=logs
region_name=EXP
logserver_prefix=http://logs.example.org/server.app/
[connection review_gerrit]
driver=gerrit
server=review.example.com
user=jenkins
sshkey=none
[connection my_smtp]
driver=smtp
server=localhost
port=25
default_from=zuul@example.com
default_to=you@example.com

View File

@ -1,81 +0,0 @@
#!/usr/bin/env python
# Copyright 2013 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from six.moves import configparser as ConfigParser
import os
import re
import testtools
import voluptuous
import yaml
import zuul.layoutvalidator
import zuul.lib.connections
FIXTURE_DIR = os.path.join(os.path.dirname(__file__),
'fixtures')
LAYOUT_RE = re.compile(r'^(good|bad)_.*\.yaml$')
class TestLayoutValidator(testtools.TestCase):
def setUp(self):
self.skip("Disabled for early v3 development")
def test_layouts(self):
"""Test layout file validation"""
print()
errors = []
for fn in os.listdir(os.path.join(FIXTURE_DIR, 'layouts')):
m = LAYOUT_RE.match(fn)
if not m:
continue
print(fn)
# Load any .conf file by the same name but .conf extension.
config_file = ("%s.conf" %
os.path.join(FIXTURE_DIR, 'layouts',
fn.split('.yaml')[0]))
if not os.path.isfile(config_file):
config_file = os.path.join(FIXTURE_DIR, 'layouts',
'zuul_default.conf')
config = ConfigParser.ConfigParser()
config.read(config_file)
connections = zuul.lib.connections.configure_connections(config)
layout = os.path.join(FIXTURE_DIR, 'layouts', fn)
data = yaml.safe_load(open(layout))
validator = zuul.layoutvalidator.LayoutValidator()
if m.group(1) == 'good':
try:
validator.validate(data, connections)
except voluptuous.Invalid as e:
raise Exception(
'Unexpected YAML syntax error in %s:\n %s' %
(fn, str(e)))
else:
try:
validator.validate(data, connections)
raise Exception("Expected a YAML syntax error in %s." %
fn)
except voluptuous.Invalid as e:
error = str(e)
print(' ', error)
if error in errors:
raise Exception("Error has already been tested: %s" %
error)
else:
errors.append(error)
pass

View File

@ -1,372 +0,0 @@
# Copyright 2013 OpenStack Foundation
# Copyright 2013 Antoine "hashar" Musso
# Copyright 2013 Wikimedia Foundation Inc.
# Copyright 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import voluptuous as v
import string
# Several forms accept either a single item or a list, this makes
# specifying that in the schema easy (and explicit).
def toList(x):
return v.Any([x], x)
class ConfigSchema(object):
tenant_source = v.Schema({'repos': [str]})
def validateTenantSources(self, value, path=[]):
if isinstance(value, dict):
for k, val in value.items():
self.validateTenantSource(val, path + [k])
else:
raise v.Invalid("Invalid tenant source", path)
def validateTenantSource(self, value, path=[]):
# TODOv3(jeblair): validate against connections
self.tenant_source(value)
def getSchema(self, data, connections=None):
tenant = {v.Required('name'): str,
'include': toList(str),
'source': self.validateTenantSources}
schema = v.Schema({'tenants': [tenant]})
return schema
class LayoutSchema(object):
manager = v.Any('IndependentPipelineManager',
'DependentPipelineManager')
precedence = v.Any('normal', 'low', 'high')
approval = v.Schema({'username': str,
'email-filter': str,
'email': str,
'older-than': str,
'newer-than': str,
}, extra=True)
require = {'approval': toList(approval),
'open': bool,
'current-patchset': bool,
'status': toList(str)}
reject = {'approval': toList(approval)}
window = v.All(int, v.Range(min=0))
window_floor = v.All(int, v.Range(min=1))
window_type = v.Any('linear', 'exponential')
window_factor = v.All(int, v.Range(min=1))
pipeline = {v.Required('name'): str,
v.Required('manager'): manager,
'source': str,
'precedence': precedence,
'description': str,
'require': require,
'reject': reject,
'success-message': str,
'failure-message': str,
'merge-failure-message': str,
'footer-message': str,
'dequeue-on-new-patchset': bool,
'ignore-dependencies': bool,
'disable-after-consecutive-failures':
v.All(int, v.Range(min=1)),
'window': window,
'window-floor': window_floor,
'window-increase-type': window_type,
'window-increase-factor': window_factor,
'window-decrease-type': window_type,
'window-decrease-factor': window_factor,
}
project_template = {v.Required('name'): str}
project_templates = [project_template]
swift = {v.Required('name'): str,
'container': str,
'expiry': int,
'max_file_size': int,
'max-file-size': int,
'max_file_count': int,
'max-file-count': int,
'logserver_prefix': str,
'logserver-prefix': str,
}
skip_if = {'project': str,
'branch': str,
'all-files-match-any': toList(str),
}
job = {v.Required('name'): str,
'queue-name': str,
'failure-message': str,
'success-message': str,
'failure-pattern': str,
'success-pattern': str,
'hold-following-changes': bool,
'voting': bool,
'attempts': int,
'mutex': str,
'tags': toList(str),
'branch': toList(str),
'files': toList(str),
'swift': toList(swift),
'skip-if': toList(skip_if),
}
jobs = [job]
job_name = v.Schema(v.Match("^\S+$"))
def validateJob(self, value, path=[]):
if isinstance(value, list):
for (i, val) in enumerate(value):
self.validateJob(val, path + [i])
elif isinstance(value, dict):
for k, val in value.items():
self.validateJob(val, path + [k])
else:
self.job_name.schema(value)
def validateTemplateCalls(self, calls):
""" Verify a project pass the parameters required
by a project-template
"""
for call in calls:
schema = self.templates_schemas[call.get('name')]
schema(call)
def collectFormatParam(self, tree):
"""In a nested tree of string, dict and list, find out any named
parameters that might be used by str.format(). This is used to find
out whether projects are passing all the required parameters when
using a project template.
Returns a set() of all the named parameters found.
"""
parameters = set()
if isinstance(tree, str):
# parse() returns a tuple of
# (literal_text, field_name, format_spec, conversion)
# We are just looking for field_name
parameters = set([t[1] for t in string.Formatter().parse(tree)
if t[1] is not None])
elif isinstance(tree, list):
for item in tree:
parameters.update(self.collectFormatParam(item))
elif isinstance(tree, dict):
for item in tree:
parameters.update(self.collectFormatParam(tree[item]))
return parameters
def getDriverSchema(self, dtype, connections):
# TODO(jhesketh): Make the driver discovery dynamic
connection_drivers = {
'trigger': {
'gerrit': 'zuul.trigger.gerrit',
},
'reporter': {
'gerrit': 'zuul.reporter.gerrit',
'smtp': 'zuul.reporter.smtp',
'sql': 'zuul.reporter.sql',
},
}
standard_drivers = {
'trigger': {
'timer': 'zuul.trigger.timer',
'zuul': 'zuul.trigger.zuultrigger',
}
}
schema = {}
# Add the configured connections as available layout options
for connection_name, connection in connections.items():
for dname, dmod in connection_drivers.get(dtype, {}).items():
if connection.driver_name == dname:
schema[connection_name] = toList(__import__(
connection_drivers[dtype][dname],
fromlist=['']).getSchema())
# Standard drivers are always available and don't require a unique
# (connection) name
for dname, dmod in standard_drivers.get(dtype, {}).items():
schema[dname] = toList(__import__(
standard_drivers[dtype][dname], fromlist=['']).getSchema())
return schema
def getSchema(self, data, connections=None):
if not isinstance(data, dict):
raise Exception("Malformed layout configuration: top-level type "
"should be a dictionary")
pipelines = data.get('pipelines')
if not pipelines:
pipelines = []
pipelines = [p['name'] for p in pipelines if 'name' in p]
# Whenever a project uses a template, it better have to exist
project_templates = data.get('project-templates', [])
template_names = [t['name'] for t in project_templates
if 'name' in t]
# A project using a template must pass all parameters to it.
# We first collect each templates parameters and craft a new
# schema for each of the template. That will later be used
# by validateTemplateCalls().
self.templates_schemas = {}
for t_name in template_names:
# Find out the parameters used inside each templates:
template = [t for t in project_templates
if t['name'] == t_name]
template_parameters = self.collectFormatParam(template)
# Craft the templates schemas
schema = {v.Required('name'): v.Any(*template_names)}
for required_param in template_parameters:
# special case 'name' which will be automatically provided
if required_param == 'name':
continue
# add this template parameters as requirements:
schema.update({v.Required(required_param): str})
# Register the schema for validateTemplateCalls()
self.templates_schemas[t_name] = v.Schema(schema)
project = {'name': str,
'merge-mode': v.Any('merge', 'merge-resolve,',
'cherry-pick'),
'template': self.validateTemplateCalls,
}
# And project should refers to existing pipelines
for p in pipelines:
project[p] = self.validateJob
projects = [project]
# Sub schema to validate a project template has existing
# pipelines and jobs.
project_template = {'name': str}
for p in pipelines:
project_template[p] = self.validateJob
project_templates = [project_template]
# TODO(jhesketh): source schema is still defined above as sources
# currently aren't key/value so there is nothing to validate. Need to
# revisit this and figure out how to allow drivers with and without
# params. eg support all:
# source: gerrit
# and
# source:
# gerrit:
# - val
# - val2
# and
# source:
# gerrit: something
# etc...
self.pipeline['trigger'] = v.Required(
self.getDriverSchema('trigger', connections))
for action in ['start', 'success', 'failure', 'merge-failure',
'disabled']:
self.pipeline[action] = self.getDriverSchema('reporter',
connections)
# Gather our sub schemas
schema = v.Schema({'includes': self.includes,
v.Required('pipelines'): [self.pipeline],
'jobs': self.jobs,
'project-templates': project_templates,
v.Required('projects'): projects,
})
return schema
class LayoutValidator(object):
def checkDuplicateNames(self, data, path):
items = []
for i, item in enumerate(data):
if item['name'] in items:
raise v.Invalid("Duplicate name: %s" % item['name'],
path + [i])
items.append(item['name'])
def extraDriverValidation(self, dtype, driver_data, connections=None):
# Some drivers may have extra validation to run on the layout
# TODO(jhesketh): Make the driver discovery dynamic
connection_drivers = {
'trigger': {
'gerrit': 'zuul.trigger.gerrit',
},
'reporter': {
'gerrit': 'zuul.reporter.gerrit',
'smtp': 'zuul.reporter.smtp',
},
}
standard_drivers = {
'trigger': {
'timer': 'zuul.trigger.timer',
'zuul': 'zuul.trigger.zuultrigger',
}
}
for dname, d_conf in driver_data.items():
for connection_name, connection in connections.items():
if connection_name == dname:
if (connection.driver_name in
connection_drivers.get(dtype, {}).keys()):
module = __import__(
connection_drivers[dtype][connection.driver_name],
fromlist=['']
)
if 'validate_conf' in dir(module):
module.validate_conf(d_conf)
break
if dname in standard_drivers.get(dtype, {}).keys():
module = __import__(standard_drivers[dtype][dname],
fromlist=[''])
if 'validate_conf' in dir(module):
module.validate_conf(d_conf)
def validate(self, data, connections=None):
schema = LayoutSchema().getSchema(data, connections)
schema(data)
self.checkDuplicateNames(data['pipelines'], ['pipelines'])
if 'jobs' in data:
self.checkDuplicateNames(data['jobs'], ['jobs'])
self.checkDuplicateNames(data['projects'], ['projects'])
if 'project-templates' in data:
self.checkDuplicateNames(
data['project-templates'], ['project-templates'])
for pipeline in data['pipelines']:
self.extraDriverValidation('trigger', pipeline['trigger'],
connections)
for action in ['start', 'success', 'failure', 'merge-failure']:
if action in pipeline:
self.extraDriverValidation('reporter', pipeline[action],
connections)
class ConfigValidator(object):
def validate(self, data, connections=None):
schema = ConfigSchema().getSchema(data, connections)
schema(data)