diff --git a/compose/config.py b/compose/config.py index f87da1d8c15..629189ede1a 100644 --- a/compose/config.py +++ b/compose/config.py @@ -1,6 +1,9 @@ +import json +import jsonschema import os import yaml import six +import sys DOCKER_CONFIG_KEYS = [ @@ -51,9 +54,25 @@ } +def validate_schema(config_data): + config_source_dir = os.path.dirname(os.path.abspath(__file__)) + schema_file = os.path.join(config_source_dir, "schema.json") + + with open(schema_file) as schema_fh: + schema = json.load(schema_fh) + + try: + jsonschema.validate(config_data, schema) + except jsonschema.exceptions.ValidationError as e: + exctype, value = sys.exc_info()[:2] + raise Exception("Validation failed, reason: (%s)" % e.message) + + def load(filename): working_dir = os.path.dirname(filename) - return from_dictionary(load_yaml(filename), working_dir=working_dir, filename=filename) + config_data = load_yaml(filename) + validate_schema(config_data) + return from_dictionary(config_data, working_dir=working_dir, filename=filename) def from_dictionary(dictionary, working_dir=None, filename=None): diff --git a/compose/empty_schema.json b/compose/empty_schema.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/compose/empty_schema.json @@ -0,0 +1 @@ +{} diff --git a/compose/schema.json b/compose/schema.json new file mode 100644 index 00000000000..ac12878e89a --- /dev/null +++ b/compose/schema.json @@ -0,0 +1,137 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + + "type": "object", + + "patternProperties": { + "^[a-zA-Z0-9]+$": { + "$ref": "#/definitions/service" + } + }, + + "additionalProperties": false, + + "definitions": { + "service": { + "type": "object", + "properties": { + "build": { "type": "string" }, + + "image": { "type": "string" }, + + "command": { "$ref": "#/definitions/string_or_stringlist" }, + + "links": { + "$ref": "#/definitions/stringlist" + }, + + "external_links": { + "$ref": "#/definitions/stringlist" + }, + + "expose": { + "$ref": "#/definitions/stringlist" + }, + + "volumes": { + "$ref": "#/definitions/stringlist" + }, + + "volumes_from": { + "$ref": "#/definitions/stringlist" + }, + + "net": { + "type": "string" + }, + + "cap_add": { + "$ref": "#/definitions/stringlist" + }, + + "drop": { + "$ref": "#/definitions/stringlist" + }, + + "working_dir": { "type": "string" }, + + "entrypoint": { "type": "string" }, + + "user": { "type": "string" }, + + "hostname": { "type": "string" }, + + "domainname": { "type": "string" }, + + "mem_limit": { "type": "string" }, + + "privileged": { "type": "string" }, + + "restart": { "type": "string" }, + + "stdin_open": { "type": "string" }, + + "tty": { "type": "string" }, + + "cpu_shares": { "type": "string" }, + + "build": { "type": "string" }, + + "image": { "type": "string" }, + + "ports": { + "$ref": "#/definitions/stringlist" + }, + + "environment": { + "$ref": "#/definitions/dict_or_list" + }, + + "env_file": { "$ref": "#/definitions/string_or_stringlist" }, + + "dns": { "$ref": "#/definitions/string_or_stringlist" }, + + "dns_search": { "$ref": "#/definitions/string_or_stringlist" }, + + "extends": { + "type": "object", + "properties": { + "file": { "type": "string" }, + "service": { "type": "string" } + }, + "required": [ "file", "service" ] + } + }, + + "additionalProperties": false + }, + + "string_or_stringlist": { + "oneOf": [ + { "type": "string" }, + { + "type": "array", + "items": {"type": "string"}, + "uniqueItems": true + } + ] + }, + + "stringlist": { + "type": "array", + "items": {"type": "string"}, + "uniqueItems": true + }, + + "dict_or_list": { + "oneOf": [ + { "type": "object" }, + { + "type": "array", + "items": {"type": "string"}, + "uniqueItems": true + } + ] + } + } +} diff --git a/compose/testconfig/empty_config.yml b/compose/testconfig/empty_config.yml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compose/testconfig/issue117_118.yml b/compose/testconfig/issue117_118.yml new file mode 100644 index 00000000000..eb4309fa44e --- /dev/null +++ b/compose/testconfig/issue117_118.yml @@ -0,0 +1,16 @@ +mongodb: + image: dockerfile/mongodb + ports: + - 27017:27017 + - 27018:27018 + volumes: /data/db:/data/db + +web: + image: foo:dev + links: + - mongodb + ports: + - 3000:3000 + volumes: + - ../../:/home/foo/ + command: bin/webapp diff --git a/compose/testconfig/issue127.yml b/compose/testconfig/issue127.yml new file mode 100644 index 00000000000..b95796d0488 --- /dev/null +++ b/compose/testconfig/issue127.yml @@ -0,0 +1,11 @@ +image: stackbrew/ubuntu +ports: + - 3306:3306 + - 6379:6379 +links: + - mysql + - redis +mysql: + image: orchardup/mysql +redis: + image: orchardup/redis diff --git a/compose/testconfig/top_level_not_dict.yml b/compose/testconfig/top_level_not_dict.yml new file mode 100644 index 00000000000..8cf88b8546e --- /dev/null +++ b/compose/testconfig/top_level_not_dict.yml @@ -0,0 +1,2 @@ +- mysql +- redis diff --git a/requirements.txt b/requirements.txt index 4c4113ab9f2..811dde14987 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ requests==2.2.1 six==1.7.3 texttable==0.8.2 websocket-client==0.11.0 +jsonschema==2.4.0