From 185f59068c79bcd51fd38f2baf8aba163585236e Mon Sep 17 00:00:00 2001 From: Fabien Boucher Date: Thu, 12 Jul 2018 19:47:46 +0200 Subject: [PATCH] Add tenant yaml validation option to zuul client This patch adds a new command 'tenant-conf-check' to the Zuul client command. This option runs a tenant_file validation by running the schema valiation of the file. The command exits -1 if errors have been detected. The command does not use RPC call but instead expects to find the tenant_file on the local filesystem. Change-Id: I6582bbc37706971085dac5c3ca3b4c690c515f9e --- doc/source/admin/client.rst | 11 ++++ ...-check-tenant-config-4b86bfd5bf3572cb.yaml | 5 ++ .../config/tenant-parser/invalid.yaml | 10 +++ tests/unit/test_client.py | 63 +++++++++++++++++++ zuul/cmd/client.py | 27 ++++++++ 5 files changed, 116 insertions(+) create mode 100644 releasenotes/notes/client-check-tenant-config-4b86bfd5bf3572cb.yaml create mode 100644 tests/fixtures/config/tenant-parser/invalid.yaml create mode 100644 tests/unit/test_client.py diff --git a/doc/source/admin/client.rst b/doc/source/admin/client.rst index 8ee6b6fbbd..7dbbfaf486 100644 --- a/doc/source/admin/client.rst +++ b/doc/source/admin/client.rst @@ -113,3 +113,14 @@ Show Example:: zuul show running-jobs + +tenant-conf-check +^^^^^^^^^^^^^^^^^ +.. program-output:: zuul tenant-conf-check --help + +Example:: + + zuul tenant-conf-check + +This command validates the tenant configuration schema. It exits '-1' in +case of errors detected. diff --git a/releasenotes/notes/client-check-tenant-config-4b86bfd5bf3572cb.yaml b/releasenotes/notes/client-check-tenant-config-4b86bfd5bf3572cb.yaml new file mode 100644 index 0000000000..af70b52f77 --- /dev/null +++ b/releasenotes/notes/client-check-tenant-config-4b86bfd5bf3572cb.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Zuul client got a new command 'tenant-conf-check'. This command validates the + schema of the tenant configuration and exits -1 if errors have been detected. diff --git a/tests/fixtures/config/tenant-parser/invalid.yaml b/tests/fixtures/config/tenant-parser/invalid.yaml new file mode 100644 index 0000000000..f5b21b1c29 --- /dev/null +++ b/tests/fixtures/config/tenant-parser/invalid.yaml @@ -0,0 +1,10 @@ +- tenant: + name: tenant-one + source: + gerrit: + config-projects: + - common-config + untrusted-projects: + - invalid: [] + - org/project1 + - org/project2 diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py new file mode 100644 index 0000000000..bc794f272b --- /dev/null +++ b/tests/unit/test_client.py @@ -0,0 +1,63 @@ +# Copyright 2018 Red Hat, Inc. +# +# 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 os +import sys +import subprocess + +import configparser +import fixtures + +from tests.base import BaseTestCase +from tests.base import FIXTURE_DIR + + +class TestTenantValidationClient(BaseTestCase): + config_file = 'zuul.conf' + + def setUp(self): + super(TestTenantValidationClient, self).setUp() + self.test_root = self.useFixture(fixtures.TempDir( + rootdir=os.environ.get("ZUUL_TEST_ROOT"))).path + self.config = configparser.ConfigParser() + self.config.read(os.path.join(FIXTURE_DIR, self.config_file)) + + def test_client_tenant_conf_check(self): + + self.config.set( + 'scheduler', 'tenant_config', + os.path.join(FIXTURE_DIR, 'config/tenant-parser/simple.yaml')) + self.config.write( + open(os.path.join(self.test_root, 'tenant_ok.conf'), 'w')) + p = subprocess.Popen( + [os.path.join(sys.prefix, 'bin/zuul'), + '-c', os.path.join(self.test_root, 'tenant_ok.conf'), + 'tenant-conf-check'], stdout=subprocess.PIPE) + p.communicate() + self.assertEqual(p.returncode, 0, 'The command must exit 0') + + self.config.set( + 'scheduler', 'tenant_config', + os.path.join(FIXTURE_DIR, 'config/tenant-parser/invalid.yaml')) + self.config.write( + open(os.path.join(self.test_root, 'tenant_ko.conf'), 'w')) + p = subprocess.Popen( + [os.path.join(sys.prefix, 'bin/zuul'), + '-c', os.path.join(self.test_root, 'tenant_ko.conf'), + 'tenant-conf-check'], stdout=subprocess.PIPE) + out, _ = p.communicate() + self.assertEqual(p.returncode, 1, "The command must exit 1") + self.assertIn( + b"expected a dictionary for dictionary", out, + "Expected error message not found") diff --git a/zuul/cmd/client.py b/zuul/cmd/client.py index 310c14ee63..fa3dbf3e55 100755 --- a/zuul/cmd/client.py +++ b/zuul/cmd/client.py @@ -140,6 +140,11 @@ class Client(zuul.cmd.ZuulApp): # TODO: add filters such as queue, project, changeid etc show_running_jobs.set_defaults(func=self.show_running_jobs) + cmd_conf_check = subparsers.add_parser( + 'tenant-conf-check', + help='validate the tenant configuration') + cmd_conf_check.set_defaults(func=self.validate) + return parser def parseArguments(self, args=None): @@ -387,6 +392,28 @@ class Client(zuul.cmd.ZuulApp): }, } + def validate(self): + from zuul import scheduler + from zuul import configloader + sched = scheduler.Scheduler(self.config, testonly=True) + self.configure_connections(source_only=True) + sched.registerConnections(self.connections, load=False) + loader = configloader.ConfigLoader( + sched.connections, sched, None) + tenant_config, script = sched._checkTenantSourceConf(self.config) + unparsed_abide = loader.readConfig(tenant_config, from_script=script) + try: + for conf_tenant in unparsed_abide.tenants: + loader.tenant_parser.getSchema()(conf_tenant) + print("Tenants config validated with success") + err_code = 0 + except Exception as e: + print("Error when validating tenants config") + print(e) + err_code = 1 + finally: + sys.exit(err_code) + def main(): Client().main()