Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

129 validate compose yml #1808

Merged
merged 17 commits into from
Aug 12, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ include requirements.txt
include requirements-dev.txt
include tox.ini
include *.md
include compose/config/schema.json
recursive-include contrib/completion *
recursive-include tests *
global-exclude *.pyc
Expand Down
71 changes: 10 additions & 61 deletions compose/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import sys
import yaml
from collections import namedtuple

import six

from compose.cli.utils import find_candidates_in_parent_dirs
Expand All @@ -14,6 +13,7 @@
CircularReference,
ComposeFileNotFound,
)
from .validation import validate_against_schema


DOCKER_CONFIG_KEYS = [
Expand Down Expand Up @@ -65,22 +65,6 @@
'name',
]

DOCKER_CONFIG_HINTS = {
'cpu_share': 'cpu_shares',
'add_host': 'extra_hosts',
'hosts': 'extra_hosts',
'extra_host': 'extra_hosts',
'device': 'devices',
'link': 'links',
'memory_swap': 'memswap_limit',
'port': 'ports',
'privilege': 'privileged',
'priviliged': 'privileged',
'privilige': 'privileged',
'volume': 'volumes',
'workdir': 'working_dir',
}


SUPPORTED_FILENAMES = [
'docker-compose.yml',
Expand Down Expand Up @@ -132,12 +116,18 @@ def get_config_path(base_dir):


def load(config_details):
dictionary, working_dir, filename = config_details
dictionary = interpolate_environment_variables(dictionary)
config, working_dir, filename = config_details
if not isinstance(config, dict):
raise ConfigurationError(
"Top level object needs to be a dictionary. Check your .yml file that you have defined a service at the top level."
)

config = interpolate_environment_variables(config)
validate_against_schema(config)

service_dicts = []

for service_name, service_dict in list(dictionary.items()):
for service_name, service_dict in list(config.items()):
loader = ServiceLoader(working_dir=working_dir, filename=filename)
service_dict = loader.make_service_dict(service_name, service_dict)
validate_paths(service_dict)
Expand Down Expand Up @@ -210,25 +200,11 @@ def signature(self, name):
def validate_extends_options(self, service_name, extends_options):
error_prefix = "Invalid 'extends' configuration for %s:" % service_name

if not isinstance(extends_options, dict):
raise ConfigurationError("%s must be a dictionary" % error_prefix)

if 'service' not in extends_options:
raise ConfigurationError(
"%s you need to specify a service, e.g. 'service: web'" % error_prefix
)

if 'file' not in extends_options and self.filename is None:
raise ConfigurationError(
"%s you need to specify a 'file', e.g. 'file: something.yml'" % error_prefix
)

for k, _ in extends_options.items():
if k not in ['file', 'service']:
raise ConfigurationError(
"%s unsupported configuration option '%s'" % (error_prefix, k)
)

return extends_options


Expand All @@ -247,18 +223,8 @@ def validate_extended_service_dict(service_dict, filename, service):


def process_container_options(service_dict, working_dir=None):
for k in service_dict:
if k not in ALLOWED_KEYS:
msg = "Unsupported config option for %s service: '%s'" % (service_dict['name'], k)
if k in DOCKER_CONFIG_HINTS:
msg += " (did you mean '%s'?)" % DOCKER_CONFIG_HINTS[k]
raise ConfigurationError(msg)

service_dict = service_dict.copy()

if 'memswap_limit' in service_dict and 'mem_limit' not in service_dict:
raise ConfigurationError("Invalid 'memswap_limit' configuration for %s service: when defining 'memswap_limit' you must set 'mem_limit' as well" % service_dict['name'])

if 'volumes' in service_dict and service_dict.get('volume_driver') is None:
service_dict['volumes'] = resolve_volume_paths(service_dict['volumes'], working_dir=working_dir)

Expand Down Expand Up @@ -328,18 +294,6 @@ def merge_environment(base, override):
return env


def parse_links(links):
return dict(parse_link(l) for l in links)


def parse_link(link):
if ':' in link:
source, alias = link.split(':', 1)
return (alias, source)
else:
return (link, link)


def get_env_files(options, working_dir=None):
if 'env_file' not in options:
return {}
Expand Down Expand Up @@ -500,11 +454,6 @@ def parse_labels(labels):
if isinstance(labels, dict):
return labels

raise ConfigurationError(
"labels \"%s\" must be a list or mapping" %
labels
)


def split_label(label):
if '=' in label:
Expand Down
151 changes: 151 additions & 0 deletions compose/config/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",

"type": "object",

"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/service"
}
},

"definitions": {
"service": {
"type": "object",

"properties": {
"build": {"type": "string"},
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"command": {"$ref": "#/definitions/string_or_list"},
"container_name": {"type": "string"},
"cpu_shares": {"type": "string"},
"cpuset": {"type": "string"},
"detach": {"type": "boolean"},
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"dns": {"$ref": "#/definitions/string_or_list"},
"dns_search": {"$ref": "#/definitions/string_or_list"},
"dockerfile": {"type": "string"},
"domainname": {"type": "string"},
"entrypoint": {"type": "string"},
"env_file": {"$ref": "#/definitions/string_or_list"},

"environment": {
"oneOf": [
{"type": "object"},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},

"expose": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},

"extends": {
"type": "object",

"properties": {
"service": {"type": "string"},
"file": {"type": "string"}
},
"required": ["service"],
"additionalProperties": false
},

"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"hostname": {"type": "string"},
"image": {"type": "string"},
"labels": {"$ref": "#/definitions/list_or_dict"},
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"log_driver": {"type": "string"},

"log_opt": {
"type": "object",

"properties": {
"address": {"type": "string"}
},
"required": ["address"]
},

"mac_address": {"type": "string"},
"mem_limit": {"type": "number"},
"memswap_limit": {"type": "number"},
"name": {"type": "string"},
"net": {"type": "string"},
"pid": {"type": "string"},

"ports": {
"oneOf": [
{
"type": "array",
"items": {"type": "string"},
"uniqueItems": true,
"format": "ports"
},
{
"type": "string",
"format": "ports"
},
{
"type": "number",
"format": "ports"
}
]
},

"privileged": {"type": "string"},
"read_only": {"type": "boolean"},
"restart": {"type": "string"},
"security_opt": {"type": "string"},
"stdin_open": {"type": "string"},
"tty": {"type": "string"},
"user": {"type": "string"},
"volumes": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"volumes_from": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"working_dir": {"type": "string"}
},

"anyOf": [
{
"required": ["build"],
"not": {"required": ["image"]}
},
{
"required": ["image"],
"not": {"required": ["build"]}
},
{
"required": ["extends"],
"not": {"required": ["build", "image"]}
}
],

"dependencies": {
"memswap_limit": ["mem_limit"]
},
"additionalProperties": false
},

"string_or_list": {
"oneOf": [
{"type": "string"},
{"$ref": "#/definitions/list_of_strings"}
]
},

"list_of_strings": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},

"list_or_dict": {
"oneOf": [
{"type": "array", "items": {"type": "string"}, "uniqueItems": true},
{"type": "object"}
]
}

},
"additionalProperties": false
}
Loading