From deb49797172e2a44333bd794a0e66f72e3a201bd Mon Sep 17 00:00:00 2001
From: Vincent Pelletier <vincent@nexedi.com>
Date: Wed, 14 Jun 2017 10:51:11 +0900
Subject: [PATCH] Add validate-schema: A slapos descriptor schema validator.

Helps developing valid software release descriptors.
Allows checking instance parameters and published values against a software
release's schema in addition to validating the schemas themselves against
slapos' schema.json and jsonschema's schemas.
---
 validate-schema | 117 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 117 insertions(+)
 create mode 100755 validate-schema

diff --git a/validate-schema b/validate-schema
new file mode 100755
index 000000000..442ca677b
--- /dev/null
+++ b/validate-schema
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+import argparse
+import json
+import os.path
+import urllib
+from urlparse import urlparse, urlunparse, ParseResult
+import jsonschema
+
+# Adapted from slapos.core.git/slapos/slap/util.py
+from lxml import etree
+def xml2dict(infile):
+    result_dict = {}
+    for element in etree.parse(infile).iter(tag=etree.Element):
+        if element.tag == 'parameter':
+            key = element.get('id')
+            value = result_dict.get(key, None)
+            if value is not None:
+                value = value + ' ' + element.text
+            else:
+                value = element.text
+            result_dict[key] = value
+    return result_dict
+
+def jsonInXML2dict(infile):
+    wrapped = xml2dict(infile)
+    if '_' in wrapped:
+        return json.loads(wrapped['_'])
+    return wrapped
+
+class ValidatingRefResolver(jsonschema.RefResolver):
+    def resolve_remote(self, *args, **kw):
+        result = super(ValidatingRefResolver, self).resolve_remote(*args, **kw)
+        jsonschema.Draft4Validator.check_schema(result)
+        return result
+
+DEFAULT_SLAPOS_SCHEMA_PATH = os.path.join(
+    os.path.dirname(__file__),
+    'schema.json',
+)
+
+def main():
+    parser = argparse.ArgumentParser(
+        description='Validates SlapOS Software Release descriptor schemas, '
+        'and optionally validates specific requests and/or responses with '
+        'these schemas.',
+    )
+    parser.add_argument('--software-type', type=str, help='Software type given request/response is intended for')
+    parser.add_argument('--request', type=argparse.FileType('r'), help='File containing request parameters')
+    parser.add_argument('--response', type=argparse.FileType('r'), help='File containing published values')
+    parser.add_argument('--slapos-schema', type=argparse.FileType('r'), help='SlapOS base schema. Default: %s' % (DEFAULT_SLAPOS_SCHEMA_PATH, ))
+    parser.add_argument('software_release', nargs=1, help='URL of the software release')
+    args = parser.parse_args()
+
+    if args.software_type is None and (
+            args.request is not None or
+            args.response is not None
+        ):
+        parser.error(
+            '--software-type is required if --request or --response is provided',
+        )
+
+    slapos_schema_file = args.slapos_schema
+    if slapos_schema_file is None:
+        slapos_schema_file = open(DEFAULT_SLAPOS_SCHEMA_PATH)
+    slapos_schema = json.load(slapos_schema_file)
+    jsonschema.Draft4Validator.check_schema(slapos_schema)
+
+    software_release_url = urlparse(args.software_release[0])
+    if software_release_url.scheme == software_release_url.netloc == '':
+      # Assume software_release_url.path is a relative path for "file" scheme
+      software_release_url = ParseResult(
+        'file',
+        '',
+        os.path.abspath(software_release_url.path),
+        software_release_url.params,
+        software_release_url.query,
+        software_release_url.fragment,
+      )
+    software_release_base_path, software_release_file = software_release_url.path.rsplit('/', 1)
+    def loadurl(relative_url):
+        url = urlunparse((
+            software_release_url.scheme,
+            software_release_url.netloc,
+            software_release_base_path + '/' + relative_url,
+            software_release_url.params,
+            software_release_url.query,
+            software_release_url.fragment,
+        ))
+        return url, json.loads(urllib.urlopen(url).read())
+
+    _, software_release_descriptor = loadurl(software_release_file + '.json')
+    jsonschema.Draft4Validator.check_schema(software_release_descriptor)
+    jsonschema.validate(software_release_descriptor, slapos_schema)
+    load = {
+        'xml': xml2dict,
+        'json-in-xml': jsonInXML2dict,
+    }[software_release_descriptor['serialisation']]
+    for software_type_name, software_type in software_release_descriptor['software-type'].iteritems():
+        for key in ('request', 'response'):
+            relative_url = software_type[key]
+            url, software_release_schema = loadurl(relative_url)
+            jsonschema.Draft4Validator.check_schema(software_release_schema)
+            validator = jsonschema.Draft4Validator(
+                schema=software_release_schema,
+                resolver=ValidatingRefResolver(
+                    base_uri=url,
+                    referrer=software_release_schema,
+                ),
+            )
+            infile = getattr(args, key)
+            if infile is not None and software_type_name == args.software_type:
+                validator.validate(load(infile))
+            else:
+                validator.validate({})
+
+if __name__ == '__main__':
+    main()
-- 
2.30.9