Remove layoutvalidator
This is no longer used. Instead, the configloader is reponsible for validating each configuration stanza as it is parsed. The error reporting happens as changes are proposed to in-repo configuration files. Most of the good paths are tested by the tests which utilize those features. There are a few tests which deliberately test syntax errors, though they are not as comprehensive. Because a nearly complete running Zuul system is required in order to parse a configuration file now, it is not practical to test configuration syntax errors in the way that was previously done in the layoutvalidator; therefore the tests are removed without a replacement. Change-Id: I774dccbc2460099fc24815ed96f0de7532157d31
This commit is contained in:
parent
14c6c5a3ec
commit
9e784526e4
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,2 +0,0 @@
|
|||
# Pipelines completely missing. At least one is required.
|
||||
pipelines:
|
|
@ -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
|
|
@ -1,7 +0,0 @@
|
|||
pipelines:
|
||||
# name is required for pipelines
|
||||
- noname: check
|
||||
manager: IndependentPipelineManager
|
||||
|
||||
projects:
|
||||
- name: foo
|
|
@ -1,8 +0,0 @@
|
|||
pipelines:
|
||||
- name: check
|
||||
# The manager must be one of IndependentPipelineManager
|
||||
# or DependentPipelineManager
|
||||
manager: NonexistentPipelineManager
|
||||
|
||||
projects:
|
||||
- name: foo
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,6 +0,0 @@
|
|||
pipelines:
|
||||
# The pipeline must have a name.
|
||||
- manager: IndependentPipelineManager
|
||||
|
||||
projects:
|
||||
- name: foo
|
|
@ -1,6 +0,0 @@
|
|||
pipelines:
|
||||
# The pipeline must have a manager
|
||||
- name: check
|
||||
|
||||
projects:
|
||||
- name: foo
|
|
@ -1,9 +0,0 @@
|
|||
pipelines:
|
||||
# Names must be unique.
|
||||
- name: check
|
||||
manager: IndependentPipelineManager
|
||||
- name: check
|
||||
manager: IndependentPipelineManager
|
||||
|
||||
projects:
|
||||
- name: foo
|
|
@ -1,10 +0,0 @@
|
|||
pipelines:
|
||||
- name: check
|
||||
manager: IndependentPipelineManager
|
||||
|
||||
projects:
|
||||
- name: foo
|
||||
# gate pipeline is not defined.
|
||||
gate:
|
||||
- test
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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'
|
|
@ -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'
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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'
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
Loading…
Reference in New Issue