Browse code

Add v3.1 schema and support validating multiple version.

Signed-off-by: Daniel Nephin <dnephin@docker.com>
(cherry picked from commit 65374488f92512cf34667cb71ea6d62985310f65)
Signed-off-by: Victor Vieux <vieux@docker.com>

Daniel Nephin authored on 2017/01/11 07:10:53
Showing 5 changed files
... ...
@@ -62,16 +62,11 @@ func Load(configDetails types.ConfigDetails) (*types.Config, error) {
62 62
 		}
63 63
 	}
64 64
 
65
-	if err := schema.Validate(configDict); err != nil {
65
+	if err := schema.Validate(configDict, schema.Version(configDict)); err != nil {
66 66
 		return nil, err
67 67
 	}
68 68
 
69 69
 	cfg := types.Config{}
70
-	version := configDict["version"].(string)
71
-	if version != "3" && version != "3.0" {
72
-		return nil, fmt.Errorf(`Unsupported Compose file version: %#v. The only version supported is "3" (or "3.0")`, version)
73
-	}
74
-
75 70
 	if services, ok := configDict["services"]; ok {
76 71
 		servicesConfig, err := interpolation.Interpolate(services.(types.Dict), "service", os.LookupEnv)
77 72
 		if err != nil {
... ...
@@ -1,6 +1,7 @@
1 1
 // Code generated by go-bindata.
2 2
 // sources:
3 3
 // data/config_schema_v3.0.json
4
+// data/config_schema_v3.1.json
4 5
 // DO NOT EDIT!
5 6
 
6 7
 package schema
... ...
@@ -88,6 +89,26 @@ func dataConfig_schema_v30Json() (*asset, error) {
88 88
 	return a, nil
89 89
 }
90 90
 
91
+var _dataConfig_schema_v31Json = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xec\x1a\x4d\x93\xdb\xa8\xf2\xee\x5f\xa1\x52\x72\x8b\x67\x26\xaf\x5e\xea\x55\xbd\xdc\xde\xf1\x9d\x76\xcf\x3b\xe5\xa8\xb0\xd4\x96\xc9\x20\x20\x80\x9c\x71\x52\xfe\xef\x5b\xe8\xcb\x80\x41\x60\x5b\xd9\xa4\x6a\xf7\x34\x63\xd1\xdd\xf4\x77\x37\x0d\xdf\x57\x59\x96\xbf\x95\xe5\x1e\x1a\x94\x7f\xcc\xf2\xbd\x52\xfc\xe3\xd3\xd3\x67\xc9\xe8\x43\xff\xf5\x91\x89\xfa\xa9\x12\x68\xa7\x1e\xde\x7f\x78\xea\xbf\xbd\xc9\xd7\x1a\x0f\x57\x1a\xa5\x64\x74\x87\xeb\xa2\x5f\x29\x0e\xff\x7e\xfc\xd7\xa3\x46\xef\x41\xd4\x91\x83\x06\x62\xdb\xcf\x50\xaa\xfe\x9b\x80\x2f\x2d\x16\xa0\x91\x9f\xf3\x03\x08\x89\x19\xcd\x37\xeb\x95\x5e\xe3\x82\x71\x10\x0a\x83\xcc\x3f\x66\x9a\xb9\x2c\x9b\x40\xc6\x0f\x06\x59\xa9\x04\xa6\x75\xde\x7d\x3e\x75\x14\xb2\x2c\x97\x20\x0e\xb8\x34\x28\x4c\xac\xbe\x79\x3a\xd3\x7f\x9a\xc0\xd6\x2e\x55\x83\xd9\xee\x3b\x47\x4a\x81\xa0\xbf\x5f\xf2\xd6\x2d\x7f\x7a\x46\x0f\xdf\xfe\xf7\xf0\xc7\xfb\x87\xff\x3e\x16\x0f\x9b\x77\x6f\xad\x65\xad\x5f\x01\xbb\x7e\xfb\x0a\x76\x98\x62\x85\x19\x9d\xf6\xcf\x27\xc8\xd3\xf0\xdf\x69\xda\x18\x55\x55\x07\x8c\x88\xb5\xf7\x0e\x11\x09\xb6\xcc\x14\xd4\x57\x26\x5e\x62\x32\x4f\x60\x3f\x49\xe6\x61\x7f\x8f\xcc\xb6\x38\x07\x46\xda\x26\x6a\xc1\x11\xea\x27\x09\xd3\x6f\xbf\x8c\xfd\x24\x94\x02\x54\xdc\x65\x7b\xa8\x9f\xe6\xb1\x7a\xfb\xfb\x04\x5e\x8d\x42\xcf\xc2\xf6\x10\xc6\xde\x1d\x83\x56\x78\xfb\x54\xe5\x0b\xaf\xb0\xae\x26\x65\x05\xb4\x54\x01\x27\xec\xa8\xbf\x05\xf4\xd1\x03\x34\x40\x55\x3e\xa9\x20\xcb\xf2\x6d\x8b\x49\xe5\x6a\x94\x51\xf8\x4d\x93\x78\x36\x3e\x66\xd9\x77\x37\x93\x19\x74\xba\x75\xeb\x57\xd8\xe0\xd3\x7a\x40\x96\x69\xbd\x64\x54\xc1\xab\xea\x84\x9a\xdf\xba\x57\x01\x2b\x5f\x40\xec\x30\x81\x54\x0c\x24\x6a\x39\xa3\x32\x82\xa5\x2a\x98\x28\x2a\x5c\xaa\xfc\xe4\xa0\x5f\xd0\x8b\xfb\xd3\x84\x6a\xfc\xda\xac\x3c\x04\xf3\x12\xf1\x02\x55\x95\x25\x07\x12\x02\x1d\xf3\x75\x96\x63\x05\x8d\xf4\x8b\x98\xe5\x2d\xc5\x5f\x5a\xf8\xff\x00\xa2\x44\x0b\x2e\xdd\x4a\x30\xbe\x3c\xe1\x5a\xb0\x96\x17\x1c\x09\xed\x60\xf3\xea\xcf\x4b\xd6\x34\x88\x2e\xe5\x75\xd7\xc8\x91\xa0\x79\x46\x15\xc2\x14\x44\x41\x51\x13\x73\x24\x1d\x75\x40\x2b\x59\xf4\x05\x7f\xd6\x8d\x76\x45\x8f\x2f\x1d\x02\x53\xf5\x5f\xd4\x1e\x15\x9d\x73\xec\x9e\x8c\x76\x6d\xcd\x5b\xee\x20\x16\x12\x90\x28\xf7\x37\xe2\xb3\x06\x61\x9a\xa2\x3b\xa0\x4a\x1c\x39\xc3\xbd\xbf\xfc\x72\x8e\x00\xf4\x50\x4c\xb9\xe4\x6a\x35\x00\x3d\x60\xc1\x68\x33\x46\x43\x4a\x82\x99\x92\xbc\xc6\x7f\xe5\x4c\x82\xab\x18\x47\x40\x73\x69\x12\xd5\xd2\xc9\x88\xf1\x3c\x0a\xbe\xce\x72\xda\x36\x5b\x10\xba\x87\xb5\x20\x77\x4c\x34\x48\x33\x3b\xee\x6d\x2c\x5b\x9a\xf6\x78\x9e\xa9\x40\x53\x06\x5d\xd6\x11\x29\x08\xa6\x2f\xcb\xbb\x38\xbc\x2a\x81\x8a\x3d\x93\x2a\x3d\x87\x1b\xe8\x7b\x40\x44\xed\xcb\x3d\x94\x2f\x33\xe8\x26\x94\x85\xcd\xa4\x4a\x71\x72\xdc\xa0\x3a\x0e\xc4\xcb\x18\x08\x41\x5b\x20\x37\xc9\xb9\xa8\xf2\x0d\xb2\xac\xae\x35\x68\xc8\xe3\x2e\x3a\x97\x61\x39\x56\xf3\x2b\x81\x0f\x20\x52\x0b\x38\xe3\xe7\x86\xcb\x5d\x8c\x37\x20\x59\xbc\xfb\xb4\x40\x3f\x3d\xf6\xcd\xe7\x4c\x54\x75\xff\x11\x92\x6f\xdc\x76\x21\x73\xea\xbe\xef\x8b\x23\x61\x5a\x43\x61\x59\xa5\x41\xa5\xee\x1b\x04\xc8\x80\x5d\xcf\xa0\xc3\xe9\xa6\x68\x58\x15\x72\xd0\x0b\x60\x57\x37\xc1\x4c\x7d\x75\x21\xcc\x6e\xea\x1f\x93\x4c\x17\x3d\x40\x44\xa4\x09\xb1\x97\xca\xe6\x99\xdd\xb8\x8b\x75\x70\x88\x60\x24\x21\x1e\xec\x41\x45\x5a\xd4\x30\x3f\x7c\x48\xf4\x09\x1f\xee\x7f\x66\x71\x03\xa8\x41\x9a\xe9\x3d\x72\x84\xd4\x99\x95\x2e\xdc\x7c\x8c\x6c\x22\xd1\xf6\x83\x5b\x78\x8e\xab\x70\xae\xe8\x32\x84\x19\x60\x9c\x09\x75\x11\x5d\x7f\x4d\xb9\xef\xb7\xbe\xbb\xda\x73\x81\x0f\x98\x40\x0d\xf6\xa9\x65\xcb\x18\x01\x44\xad\xd4\x23\x00\x55\x05\xa3\xe4\x98\x00\x29\x15\x12\xd1\x03\x85\x84\xb2\x15\x58\x1d\x0b\xc6\xd5\xe2\x7d\x86\xdc\x37\x85\xc4\xdf\xc0\xb6\xe6\x39\xdf\x0f\x84\x36\x0e\x43\xce\x84\xe4\x46\x83\x86\x52\x52\x3c\x8c\x3d\x89\x30\x9a\xa8\xe2\x29\x2a\x97\xac\x15\x65\xea\x01\x5b\xef\x89\x44\x0d\xa9\x47\x78\xed\x6e\x76\xd8\xcc\x03\xd7\xd7\x00\x5f\x14\xba\xc1\x84\xb1\xaa\xec\xfe\x36\xf3\xca\xc9\x1b\xfa\xf2\x28\x4b\x75\x5b\xb7\x26\x55\x85\x69\xc1\x38\xd0\x68\x6c\x48\xc5\x78\x21\x71\x4d\x11\x89\xc6\x87\x06\xad\x05\x2a\xa1\xe0\x20\x30\xf3\x6a\x6d\x6d\x26\x85\xaa\x15\x48\xb3\x6a\x91\x51\x0d\xdf\xdd\x78\xac\x54\x2a\x1e\xec\x2d\xc1\x0d\x0e\x07\x8d\xc7\x6b\x13\x3a\x80\xbe\xfa\xfb\x8b\xfe\x4c\xc1\x3f\x73\x8a\xa9\x82\x5a\xbb\xc9\xa5\x53\xcd\xf4\x9c\xf3\x2d\x67\x42\xaf\xb9\x47\xc2\xb6\xd2\x0c\x1f\x59\x1f\x98\x3b\xe5\x47\xf0\x75\xa2\x5e\xbe\xac\xbb\x8e\x8e\xde\x7a\x60\x64\xe3\x85\xbf\xaa\x98\xbb\x6c\x6c\x82\xf5\xd4\x1f\x54\xad\x8c\x1e\x0b\x3a\x18\x2a\xe7\x5a\xda\x09\xd4\x18\xda\x2f\x5a\x2d\x74\x9b\xac\x83\xa0\xc2\x7e\x6e\x57\x8e\x64\x57\x8c\xdd\x9d\x13\xeb\x48\xc0\x37\x4f\x36\x41\xdd\x99\xf2\xf3\xe4\x9b\x63\x27\x72\x9e\xc4\x07\x86\xcb\xda\x95\xc4\xc1\xca\x33\x3e\x9d\x2a\xdc\x00\x6b\x55\x04\x4a\x80\x12\xd8\xd1\xfc\x98\x8a\x4d\x62\x20\x7f\xcd\xc1\x50\x85\x25\xda\x3a\x33\xe6\x29\x9d\xdd\x64\xde\xec\x3c\xc0\x1f\x07\x46\x73\xc6\x35\x20\x17\xb0\x6d\x4a\xb0\x08\xe0\x04\x97\x48\xc6\x12\xd2\x1d\x63\x8a\x96\x57\x48\x41\xd1\xdf\xcf\x5e\x55\x02\x66\x72\x3f\x47\x02\x11\x02\x04\xcb\x26\x25\x97\xe6\x15\x10\x74\xbc\xa9\x36\x76\xe8\x3b\x84\x49\x2b\xa0\x40\xa5\x1a\xae\x80\x23\x9e\x99\x37\x8c\x62\xc5\xbc\x99\x22\x6d\xcb\x06\xbd\x16\xe3\xb6\x1d\x48\xac\xc3\xb1\x9b\xfb\xd4\x09\x83\xe1\x09\x7d\x03\x78\x5d\x95\x9e\x31\xd1\xb9\xe6\x07\x3c\x66\xdc\xf1\x42\x74\x01\x52\x27\xa5\x69\x00\x14\xc5\x8f\x96\x98\xe1\xb4\x51\x70\x46\x70\x79\x5c\x4a\xc2\x92\xd1\x5e\xc9\x29\x0e\x71\xa7\x07\x6a\x77\xd0\x2d\x51\xc3\x55\x34\x58\x3b\x84\xaf\x98\x56\xec\xeb\x15\x1b\x2e\xe7\x4a\x9c\xa0\x12\x9c\x7c\x77\xaf\xa2\xa5\x12\x08\x53\x75\x75\x59\xbf\x57\xac\x3b\xaa\xfa\xe4\x9f\x91\xac\x3f\xc1\xc5\xef\xd3\x03\x99\xbe\xe4\x6d\x74\x2a\xd8\x40\xc3\x84\xd7\x01\x17\x78\xf0\x11\x13\x71\x04\x5b\xa0\xaa\x25\x8d\x91\x07\xa8\x82\xf1\xe5\x4f\x1d\xf1\x51\xf1\x26\x9e\x90\x30\x47\xcd\x52\xd1\x91\x3c\x58\xcf\xbd\x35\x38\x9b\x9f\x5f\x64\xe1\x19\x46\x8c\xeb\x38\xef\x03\x84\x6c\xb7\x34\x30\x4a\xb8\x3c\x6d\xf8\x6e\xfb\xd3\x8f\x2b\xa7\xf0\xe1\xe4\xbe\xa4\x37\xde\x89\x05\xac\xfa\x3c\x75\x92\xeb\x49\x57\x9b\x64\x13\x07\x2f\xa4\x96\xe3\xff\xca\x06\xef\x8e\x9c\x31\x3c\x58\x8a\xa4\x8c\x01\xea\x9f\x8c\xf1\xcb\xf8\xd7\x4c\x51\xbc\xf1\x74\x70\xf5\xcb\xb4\x98\xd3\x0c\x50\x37\x17\xd2\x84\x27\x46\x7f\x7b\x43\xd8\xa3\x40\xc3\x20\x97\x67\xf8\x39\x3d\x26\xdf\x80\x0d\x18\x1b\x9b\x0d\x17\xcc\xf3\x2a\xd7\xae\x65\x73\xa3\x9f\x11\x24\x70\x23\xe2\x6c\x3a\x28\x6f\x5e\xf2\x05\xf3\xc7\xe3\xbb\x99\x8a\x3d\x77\x53\xfd\x83\x4a\xdd\x02\x63\x35\xbf\x4d\x9d\x36\x7f\xd4\xee\xe5\x4b\xcb\x40\xf4\x1b\xf8\x17\xef\x2e\xb5\x9c\xf4\x78\x31\x63\xfa\x6e\x8f\x46\xfb\x37\x93\x1b\x4b\x3f\x0e\x48\xff\xee\xc3\x48\xd8\x1b\xf3\xe4\x13\x32\xa3\xf7\x35\xa6\x3b\x98\x1d\x5f\x45\x06\xee\x21\x56\xe6\xdf\xee\x05\xeb\xea\xb4\xfa\x33\x00\x00\xff\xff\xb7\x14\xdd\xc9\x3a\x2f\x00\x00")
92
+
93
+func dataConfig_schema_v31JsonBytes() ([]byte, error) {
94
+	return bindataRead(
95
+		_dataConfig_schema_v31Json,
96
+		"data/config_schema_v3.1.json",
97
+	)
98
+}
99
+
100
+func dataConfig_schema_v31Json() (*asset, error) {
101
+	bytes, err := dataConfig_schema_v31JsonBytes()
102
+	if err != nil {
103
+		return nil, err
104
+	}
105
+
106
+	info := bindataFileInfo{name: "data/config_schema_v3.1.json", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
107
+	a := &asset{bytes: bytes, info: info}
108
+	return a, nil
109
+}
110
+
91 111
 // Asset loads and returns the asset for the given name.
92 112
 // It returns an error if the asset could not be found or
93 113
 // could not be loaded.
... ...
@@ -141,6 +162,7 @@ func AssetNames() []string {
141 141
 // _bindata is a table, holding each asset generator, mapped to its name.
142 142
 var _bindata = map[string]func() (*asset, error){
143 143
 	"data/config_schema_v3.0.json": dataConfig_schema_v30Json,
144
+	"data/config_schema_v3.1.json": dataConfig_schema_v31Json,
144 145
 }
145 146
 
146 147
 // AssetDir returns the file names below a certain
... ...
@@ -183,8 +205,9 @@ type bintree struct {
183 183
 	Children map[string]*bintree
184 184
 }
185 185
 var _bintree = &bintree{nil, map[string]*bintree{
186
-	"data": &bintree{nil, map[string]*bintree{
187
-		"config_schema_v3.0.json": &bintree{dataConfig_schema_v30Json, map[string]*bintree{}},
186
+	"data": {nil, map[string]*bintree{
187
+		"config_schema_v3.0.json": {dataConfig_schema_v30Json, map[string]*bintree{}},
188
+		"config_schema_v3.1.json": {dataConfig_schema_v31Json, map[string]*bintree{}},
188 189
 	}},
189 190
 }}
190 191
 
191 192
new file mode 100644
... ...
@@ -0,0 +1,426 @@
0
+{
1
+  "$schema": "http://json-schema.org/draft-04/schema#",
2
+  "id": "config_schema_v3.1.json",
3
+  "type": "object",
4
+  "required": ["version"],
5
+
6
+  "properties": {
7
+    "version": {
8
+      "type": "string"
9
+    },
10
+
11
+    "services": {
12
+      "id": "#/properties/services",
13
+      "type": "object",
14
+      "patternProperties": {
15
+        "^[a-zA-Z0-9._-]+$": {
16
+          "$ref": "#/definitions/service"
17
+        }
18
+      },
19
+      "additionalProperties": false
20
+    },
21
+
22
+    "networks": {
23
+      "id": "#/properties/networks",
24
+      "type": "object",
25
+      "patternProperties": {
26
+        "^[a-zA-Z0-9._-]+$": {
27
+          "$ref": "#/definitions/network"
28
+        }
29
+      }
30
+    },
31
+
32
+    "volumes": {
33
+      "id": "#/properties/volumes",
34
+      "type": "object",
35
+      "patternProperties": {
36
+        "^[a-zA-Z0-9._-]+$": {
37
+          "$ref": "#/definitions/volume"
38
+        }
39
+      },
40
+      "additionalProperties": false
41
+    },
42
+
43
+    "secrets": {
44
+      "id": "#/properties/secrets",
45
+      "type": "object",
46
+      "patternProperties": {
47
+        "^[a-zA-Z0-9._-]+$": {
48
+          "$ref": "#/definitions/secret"
49
+        }
50
+      },
51
+      "additionalProperties": false
52
+    }
53
+  },
54
+
55
+  "additionalProperties": false,
56
+
57
+  "definitions": {
58
+
59
+    "service": {
60
+      "id": "#/definitions/service",
61
+      "type": "object",
62
+
63
+      "properties": {
64
+        "deploy": {"$ref": "#/definitions/deployment"},
65
+        "build": {
66
+          "oneOf": [
67
+            {"type": "string"},
68
+            {
69
+              "type": "object",
70
+              "properties": {
71
+                "context": {"type": "string"},
72
+                "dockerfile": {"type": "string"},
73
+                "args": {"$ref": "#/definitions/list_or_dict"}
74
+              },
75
+              "additionalProperties": false
76
+            }
77
+          ]
78
+        },
79
+        "cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
80
+        "cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
81
+        "cgroup_parent": {"type": "string"},
82
+        "command": {
83
+          "oneOf": [
84
+            {"type": "string"},
85
+            {"type": "array", "items": {"type": "string"}}
86
+          ]
87
+        },
88
+        "container_name": {"type": "string"},
89
+        "depends_on": {"$ref": "#/definitions/list_of_strings"},
90
+        "devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
91
+        "dns": {"$ref": "#/definitions/string_or_list"},
92
+        "dns_search": {"$ref": "#/definitions/string_or_list"},
93
+        "domainname": {"type": "string"},
94
+        "entrypoint": {
95
+          "oneOf": [
96
+            {"type": "string"},
97
+            {"type": "array", "items": {"type": "string"}}
98
+          ]
99
+        },
100
+        "env_file": {"$ref": "#/definitions/string_or_list"},
101
+        "environment": {"$ref": "#/definitions/list_or_dict"},
102
+
103
+        "expose": {
104
+          "type": "array",
105
+          "items": {
106
+            "type": ["string", "number"],
107
+            "format": "expose"
108
+          },
109
+          "uniqueItems": true
110
+        },
111
+
112
+        "external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
113
+        "extra_hosts": {"$ref": "#/definitions/list_or_dict"},
114
+        "healthcheck": {"$ref": "#/definitions/healthcheck"},
115
+        "hostname": {"type": "string"},
116
+        "image": {"type": "string"},
117
+        "ipc": {"type": "string"},
118
+        "labels": {"$ref": "#/definitions/list_or_dict"},
119
+        "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
120
+
121
+        "logging": {
122
+            "type": "object",
123
+
124
+            "properties": {
125
+                "driver": {"type": "string"},
126
+                "options": {
127
+                  "type": "object",
128
+                  "patternProperties": {
129
+                    "^.+$": {"type": ["string", "number", "null"]}
130
+                  }
131
+                }
132
+            },
133
+            "additionalProperties": false
134
+        },
135
+
136
+        "mac_address": {"type": "string"},
137
+        "network_mode": {"type": "string"},
138
+
139
+        "networks": {
140
+          "oneOf": [
141
+            {"$ref": "#/definitions/list_of_strings"},
142
+            {
143
+              "type": "object",
144
+              "patternProperties": {
145
+                "^[a-zA-Z0-9._-]+$": {
146
+                  "oneOf": [
147
+                    {
148
+                      "type": "object",
149
+                      "properties": {
150
+                        "aliases": {"$ref": "#/definitions/list_of_strings"},
151
+                        "ipv4_address": {"type": "string"},
152
+                        "ipv6_address": {"type": "string"}
153
+                      },
154
+                      "additionalProperties": false
155
+                    },
156
+                    {"type": "null"}
157
+                  ]
158
+                }
159
+              },
160
+              "additionalProperties": false
161
+            }
162
+          ]
163
+        },
164
+        "pid": {"type": ["string", "null"]},
165
+
166
+        "ports": {
167
+          "type": "array",
168
+          "items": {
169
+            "type": ["string", "number"],
170
+            "format": "ports"
171
+          },
172
+          "uniqueItems": true
173
+        },
174
+
175
+        "privileged": {"type": "boolean"},
176
+        "read_only": {"type": "boolean"},
177
+        "restart": {"type": "string"},
178
+        "security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
179
+        "shm_size": {"type": ["number", "string"]},
180
+        "secrets": {
181
+          "type": "array",
182
+          "items": {
183
+            "oneOf": [
184
+              {"type": "string"},
185
+              {
186
+                "type": "object",
187
+                "properties": {
188
+                  "source": {"type": "string"},
189
+                  "target": {"type": "string"},
190
+                  "uid": {"type": "string"},
191
+                  "gid": {"type": "string"},
192
+                  "mode": {"type": "number"}
193
+                }
194
+              }
195
+            ]
196
+          }
197
+        },
198
+        "sysctls": {"$ref": "#/definitions/list_or_dict"},
199
+        "stdin_open": {"type": "boolean"},
200
+        "stop_signal": {"type": "string"},
201
+        "stop_grace_period": {"type": "string", "format": "duration"},
202
+        "tmpfs": {"$ref": "#/definitions/string_or_list"},
203
+        "tty": {"type": "boolean"},
204
+        "ulimits": {
205
+          "type": "object",
206
+          "patternProperties": {
207
+            "^[a-z]+$": {
208
+              "oneOf": [
209
+                {"type": "integer"},
210
+                {
211
+                  "type":"object",
212
+                  "properties": {
213
+                    "hard": {"type": "integer"},
214
+                    "soft": {"type": "integer"}
215
+                  },
216
+                  "required": ["soft", "hard"],
217
+                  "additionalProperties": false
218
+                }
219
+              ]
220
+            }
221
+          }
222
+        },
223
+        "user": {"type": "string"},
224
+        "userns_mode": {"type": "string"},
225
+        "volumes": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
226
+        "working_dir": {"type": "string"}
227
+      },
228
+      "additionalProperties": false
229
+    },
230
+
231
+    "healthcheck": {
232
+      "id": "#/definitions/healthcheck",
233
+      "type": ["object", "null"],
234
+      "properties": {
235
+        "interval": {"type":"string"},
236
+        "timeout": {"type":"string"},
237
+        "retries": {"type": "number"},
238
+        "test": {
239
+          "oneOf": [
240
+            {"type": "string"},
241
+            {"type": "array", "items": {"type": "string"}}
242
+          ]
243
+        },
244
+        "disable": {"type": "boolean"}
245
+      },
246
+      "additionalProperties": false
247
+    },
248
+    "deployment": {
249
+      "id": "#/definitions/deployment",
250
+      "type": ["object", "null"],
251
+      "properties": {
252
+        "mode": {"type": "string"},
253
+        "replicas": {"type": "integer"},
254
+        "labels": {"$ref": "#/definitions/list_or_dict"},
255
+        "update_config": {
256
+          "type": "object",
257
+          "properties": {
258
+            "parallelism": {"type": "integer"},
259
+            "delay": {"type": "string", "format": "duration"},
260
+            "failure_action": {"type": "string"},
261
+            "monitor": {"type": "string", "format": "duration"},
262
+            "max_failure_ratio": {"type": "number"}
263
+          },
264
+          "additionalProperties": false
265
+        },
266
+        "resources": {
267
+          "type": "object",
268
+          "properties": {
269
+            "limits": {"$ref": "#/definitions/resource"},
270
+            "reservations": {"$ref": "#/definitions/resource"}
271
+          }
272
+        },
273
+        "restart_policy": {
274
+          "type": "object",
275
+          "properties": {
276
+            "condition": {"type": "string"},
277
+            "delay": {"type": "string", "format": "duration"},
278
+            "max_attempts": {"type": "integer"},
279
+            "window": {"type": "string", "format": "duration"}
280
+          },
281
+          "additionalProperties": false
282
+        },
283
+        "placement": {
284
+          "type": "object",
285
+          "properties": {
286
+            "constraints": {"type": "array", "items": {"type": "string"}}
287
+          },
288
+          "additionalProperties": false
289
+        }
290
+      },
291
+      "additionalProperties": false
292
+    },
293
+
294
+    "resource": {
295
+      "id": "#/definitions/resource",
296
+      "type": "object",
297
+      "properties": {
298
+        "cpus": {"type": "string"},
299
+        "memory": {"type": "string"}
300
+      },
301
+      "additionalProperties": false
302
+    },
303
+
304
+    "network": {
305
+      "id": "#/definitions/network",
306
+      "type": ["object", "null"],
307
+      "properties": {
308
+        "driver": {"type": "string"},
309
+        "driver_opts": {
310
+          "type": "object",
311
+          "patternProperties": {
312
+            "^.+$": {"type": ["string", "number"]}
313
+          }
314
+        },
315
+        "ipam": {
316
+          "type": "object",
317
+          "properties": {
318
+            "driver": {"type": "string"},
319
+            "config": {
320
+              "type": "array",
321
+              "items": {
322
+                "type": "object",
323
+                "properties": {
324
+                  "subnet": {"type": "string"}
325
+                },
326
+                "additionalProperties": false
327
+              }
328
+            }
329
+          },
330
+          "additionalProperties": false
331
+        },
332
+        "external": {
333
+          "type": ["boolean", "object"],
334
+          "properties": {
335
+            "name": {"type": "string"}
336
+          },
337
+          "additionalProperties": false
338
+        },
339
+        "labels": {"$ref": "#/definitions/list_or_dict"}
340
+      },
341
+      "additionalProperties": false
342
+    },
343
+
344
+    "volume": {
345
+      "id": "#/definitions/volume",
346
+      "type": ["object", "null"],
347
+      "properties": {
348
+        "driver": {"type": "string"},
349
+        "driver_opts": {
350
+          "type": "object",
351
+          "patternProperties": {
352
+            "^.+$": {"type": ["string", "number"]}
353
+          }
354
+        },
355
+        "external": {
356
+          "type": ["boolean", "object"],
357
+          "properties": {
358
+            "name": {"type": "string"}
359
+          }
360
+        }
361
+      },
362
+      "labels": {"$ref": "#/definitions/list_or_dict"},
363
+      "additionalProperties": false
364
+    },
365
+
366
+    "secret": {
367
+      "id": "#/definitions/secret",
368
+      "type": "object",
369
+      "properties": {
370
+        "file": {"type": "string"},
371
+        "external": {
372
+          "type": ["boolean", "object"],
373
+          "properties": {
374
+            "name": {"type": "string"}
375
+          }
376
+        }
377
+      },
378
+      "labels": {"$ref": "#/definitions/list_or_dict"},
379
+      "additionalProperties": false
380
+    },
381
+
382
+    "string_or_list": {
383
+      "oneOf": [
384
+        {"type": "string"},
385
+        {"$ref": "#/definitions/list_of_strings"}
386
+      ]
387
+    },
388
+
389
+    "list_of_strings": {
390
+      "type": "array",
391
+      "items": {"type": "string"},
392
+      "uniqueItems": true
393
+    },
394
+
395
+    "list_or_dict": {
396
+      "oneOf": [
397
+        {
398
+          "type": "object",
399
+          "patternProperties": {
400
+            ".+": {
401
+              "type": ["string", "number", "null"]
402
+            }
403
+          },
404
+          "additionalProperties": false
405
+        },
406
+        {"type": "array", "items": {"type": "string"}, "uniqueItems": true}
407
+      ]
408
+    },
409
+
410
+    "constraints": {
411
+      "service": {
412
+        "id": "#/definitions/constraints/service",
413
+        "anyOf": [
414
+          {"required": ["build"]},
415
+          {"required": ["image"]}
416
+        ],
417
+        "properties": {
418
+          "build": {
419
+            "required": ["context"]
420
+          }
421
+        }
422
+      }
423
+    }
424
+  }
425
+}
... ...
@@ -7,9 +7,15 @@ import (
7 7
 	"strings"
8 8
 	"time"
9 9
 
10
+	"github.com/pkg/errors"
10 11
 	"github.com/xeipuuv/gojsonschema"
11 12
 )
12 13
 
14
+const (
15
+	defaultVersion = "1.0"
16
+	versionField   = "version"
17
+)
18
+
13 19
 type portsFormatChecker struct{}
14 20
 
15 21
 func (checker portsFormatChecker) IsFormat(input string) bool {
... ...
@@ -30,11 +36,29 @@ func init() {
30 30
 	gojsonschema.FormatCheckers.Add("duration", durationFormatChecker{})
31 31
 }
32 32
 
33
+// Version returns the version of the config, defaulting to version 1.0
34
+func Version(config map[string]interface{}) string {
35
+	version, ok := config[versionField]
36
+	if !ok {
37
+		return defaultVersion
38
+	}
39
+	return normalizeVersion(fmt.Sprintf("%v", version))
40
+}
41
+
42
+func normalizeVersion(version string) string {
43
+	switch version {
44
+	case "3":
45
+		return "3.0"
46
+	default:
47
+		return version
48
+	}
49
+}
50
+
33 51
 // Validate uses the jsonschema to validate the configuration
34
-func Validate(config map[string]interface{}) error {
35
-	schemaData, err := Asset("data/config_schema_v3.0.json")
52
+func Validate(config map[string]interface{}, version string) error {
53
+	schemaData, err := Asset(fmt.Sprintf("data/config_schema_v%s.json", version))
36 54
 	if err != nil {
37
-		return err
55
+		return errors.Errorf("unsupported Compose file version: %s", version)
38 56
 	}
39 57
 
40 58
 	schemaLoader := gojsonschema.NewStringLoader(string(schemaData))
... ...
@@ -8,9 +8,9 @@ import (
8 8
 
9 9
 type dict map[string]interface{}
10 10
 
11
-func TestValid(t *testing.T) {
11
+func TestValidate(t *testing.T) {
12 12
 	config := dict{
13
-		"version": "2.1",
13
+		"version": "3.0",
14 14
 		"services": dict{
15 15
 			"foo": dict{
16 16
 				"image": "busybox",
... ...
@@ -18,12 +18,12 @@ func TestValid(t *testing.T) {
18 18
 		},
19 19
 	}
20 20
 
21
-	assert.NoError(t, Validate(config))
21
+	assert.NoError(t, Validate(config, "3.0"))
22 22
 }
23 23
 
24
-func TestUndefinedTopLevelOption(t *testing.T) {
24
+func TestValidateUndefinedTopLevelOption(t *testing.T) {
25 25
 	config := dict{
26
-		"version": "2.1",
26
+		"version": "3.0",
27 27
 		"helicopters": dict{
28 28
 			"foo": dict{
29 29
 				"image": "busybox",
... ...
@@ -31,5 +31,22 @@ func TestUndefinedTopLevelOption(t *testing.T) {
31 31
 		},
32 32
 	}
33 33
 
34
-	assert.Error(t, Validate(config))
34
+	err := Validate(config, "3.0")
35
+	assert.Error(t, err)
36
+	assert.Contains(t, err.Error(), "Additional property helicopters is not allowed")
37
+}
38
+
39
+func TestValidateInvalidVersion(t *testing.T) {
40
+	config := dict{
41
+		"version": "2.1",
42
+		"services": dict{
43
+			"foo": dict{
44
+				"image": "busybox",
45
+			},
46
+		},
47
+	}
48
+
49
+	err := Validate(config, "2.1")
50
+	assert.Error(t, err)
51
+	assert.Contains(t, err.Error(), "unsupported Compose file version: 2.1")
35 52
 }