From d1763fca7e7cb282ca47d6b3850985beda8cc0fb Mon Sep 17 00:00:00 2001 From: Qixiang Wan Date: Fri, 24 Mar 2017 13:19:31 -0600 Subject: [PATCH] checks.py: support 'append' option If 'append' is defined for a property, append the values from append options to the property. Note: The property must support to be a list of values. For example: with schema: schema = { "$schema": "http://json-schema.org/draft-04/schema#", "title": "Pungi Configuration", "type": "object", "definitions": { "list_of_strings": { "type": "array", "items": {"type": "string"}, }, "strings": { "anyOf": [ {"type": "string"}, {"$ref": "#/definitions/list_of_strings"}, ] }, }, "properties": { "release_name": {"type": "string"}, "repo": {"$ref": "#/definitions/strings", "append": "repo_from"} }, "additionalProperties": False, } and config: repo = "http://url/to/repo" repo_from = "Server" config will be updated to: repo = ["http://url/to/repo", "Server"] It supports multiple append options too, like: "repo": { "$ref": "#/definitions/strings", "append": ["repo_from", "source_repo_from"], } Signed-off-by: Qixiang Wan --- pungi/checks.py | 27 +++++++++- tests/test_checks.py | 124 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 2 deletions(-) diff --git a/pungi/checks.py b/pungi/checks.py index a9acfb39..1b24c76d 100644 --- a/pungi/checks.py +++ b/pungi/checks.py @@ -40,6 +40,7 @@ import os.path import platform import jsonschema import re +from kobo.shortcuts import force_list from . import util @@ -263,12 +264,34 @@ def _extend_with_default_and_alias(validator_class): "In:\n%s" % (subschema['alias'], property, property, instance) errors.append(ConfigOptionWarning(msg)) if property in instance: - msg = "ERROR: Config option '%s' is an alias of '%s', only one can be used. In:\n%s" \ - % (subschema['alias'], property, instance) + msg = "ERROR: Config option '%s' is an alias of '%s', only one can be used." \ + % (subschema['alias'], property) errors.append(ConfigOptionError(msg)) instance.pop(subschema['alias']) else: instance.setdefault(property, instance.pop(subschema['alias'])) + # update instance for append option + # If append is defined in schema, append values from append options to property. If property + # is not present in instance, set it to empty list, and append the values from append options. + # Note: property's schema must support a list of values. + if "append" in subschema: + appends = force_list(subschema['append']) + for append in appends: + if append in instance: + msg = "WARNING: Config option '%s' is deprecated, its value will be appended to option '%s'. " \ + "In:\n%s" % (append, property, instance) + errors.append(ConfigOptionWarning(msg)) + if property in instance: + msg = "WARNING: Value from config option '%s' is now appended to option '%s'." \ + % (append, property) + errors.append(ConfigOptionWarning(msg)) + instance[property] = force_list(instance[property]) + instance[property].extend(force_list(instance.pop(append))) + else: + msg = "WARNING: Config option '%s' is not found, but '%s' is specified, value from '%s' " \ + "is now added as '%s'." % (property, append, append, property) + errors.append(ConfigOptionWarning(msg)) + instance[property] = instance.pop(append) yield errors def _set_defaults(validator, properties, instance, schema): diff --git a/tests/test_checks.py b/tests/test_checks.py index dd844a50..cd4b009d 100644 --- a/tests/test_checks.py +++ b/tests/test_checks.py @@ -357,6 +357,130 @@ class TestSchemaValidator(unittest.TestCase): self.assertEqual(config.get("release_name", None), "dummy product") self.assertEqual(config.get("foophase", {}).get("repo", None), "http://www.exampe.com/os") + @mock.patch('pungi.checks._make_schema') + def test_append_option(self, make_schema): + schema = { + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Pungi Configuration", + "type": "object", + "definitions": { + "list_of_strings": { + "type": "array", + "items": {"type": "string"}, + }, + "strings": { + "anyOf": [ + {"type": "string"}, + {"$ref": "#/definitions/list_of_strings"}, + ] + }, + }, + "properties": { + "release_name": {"type": "string"}, + "repo": {"$ref": "#/definitions/strings", "append": "repo_from"} + }, + "additionalProperties": False, + } + make_schema.return_value = schema + + string = """ + release_name = "dummy product" + repo = "http://url/to/repo" + repo_from = 'Server' + """ + config = self._load_conf_from_string(string) + errors, warnings = checks.validate(config) + self.assertEqual(len(errors), 0) + self.assertEqual(len(warnings), 2) + self.assertRegexpMatches(warnings[0], r"^WARNING: Config option 'repo_from' is deprecated, its value will be appended to option 'repo'.*") + self.assertRegexpMatches(warnings[1], r"^WARNING: Value from config option 'repo_from' is now appended to option 'repo'") + self.assertEqual(config.get("release_name", None), "dummy product") + self.assertEqual(config.get("repo", None), ["http://url/to/repo", "Server"]) + + @mock.patch('pungi.checks._make_schema') + def test_append_to_nonexist_option(self, make_schema): + schema = { + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Pungi Configuration", + "type": "object", + "definitions": { + "list_of_strings": { + "type": "array", + "items": {"type": "string"}, + }, + "strings": { + "anyOf": [ + {"type": "string"}, + {"$ref": "#/definitions/list_of_strings"}, + ] + }, + }, + "properties": { + "release_name": {"type": "string"}, + "repo": {"$ref": "#/definitions/strings", "append": "repo_from"} + }, + "additionalProperties": False, + } + make_schema.return_value = schema + + string = """ + release_name = "dummy product" + repo_from = ['http://url/to/repo', 'Server'] + """ + config = self._load_conf_from_string(string) + errors, warnings = checks.validate(config) + self.assertEqual(len(errors), 0) + self.assertEqual(len(warnings), 2) + self.assertRegexpMatches(warnings[0], r"^WARNING: Config option 'repo_from' is deprecated, its value will be appended to option 'repo'.*") + self.assertRegexpMatches(warnings[1], r"^WARNING: Config option 'repo' is not found, but 'repo_from' is specified,") + self.assertEqual(config.get("release_name", None), "dummy product") + self.assertEqual(config.get("repo", None), ["http://url/to/repo", "Server"]) + + @mock.patch('pungi.checks._make_schema') + def test_multiple_appends(self, make_schema): + schema = { + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Pungi Configuration", + "type": "object", + "definitions": { + "list_of_strings": { + "type": "array", + "items": {"type": "string"}, + }, + "strings": { + "anyOf": [ + {"type": "string"}, + {"$ref": "#/definitions/list_of_strings"}, + ] + }, + }, + "properties": { + "release_name": {"type": "string"}, + "repo": { + "$ref": "#/definitions/strings", + "append": ["repo_from", "source_repo_from"] + } + }, + "additionalProperties": False, + } + make_schema.return_value = schema + + string = """ + release_name = "dummy product" + repo_from = ['http://url/to/repo', 'Server'] + source_repo_from = 'Client' + """ + config = self._load_conf_from_string(string) + errors, warnings = checks.validate(config) + self.assertEqual(len(errors), 0) + self.assertEqual(len(warnings), 4) + self.assertRegexpMatches(warnings[0], r"^WARNING: Config option 'repo_from' is deprecated, its value will be appended to option 'repo'.*") + self.assertRegexpMatches(warnings[1], r"^WARNING: Config option 'repo' is not found, but 'repo_from' is specified,") + self.assertRegexpMatches(warnings[2], r"^WARNING: Config option 'source_repo_from' is deprecated, its value will be appended to option 'repo'") + self.assertRegexpMatches(warnings[3], r"^WARNING: Value from config option 'source_repo_from' is now appended to option 'repo'.") + self.assertEqual(config.get("release_name", None), "dummy product") + self.assertEqual(config.get("repo", None), ["http://url/to/repo", "Server", "Client"]) + class TestUmask(unittest.TestCase): def setUp(self):