diff --git a/schema/defs-config.json b/schema/defs-config.json
index 2faf3c656..ba86cd1df 100644
--- a/schema/defs-config.json
+++ b/schema/defs-config.json
@@ -38,7 +38,10 @@
}
},
"Volumes": {
- "$ref": "defs.json#/definitions/mapStringObject"
+ "oneOf": [
+ {"$ref": "defs.json#/definitions/mapStringObject"},
+ {"type": "null"}
+ ]
},
"WorkingDir": {
"type": "string"
diff --git a/schema/fs.go b/schema/fs.go
index e49730a95..d7c4d000f 100644
--- a/schema/fs.go
+++ b/schema/fs.go
@@ -203,7 +203,7 @@ var _escData = map[string]*_escFile{
"/config-schema.json": {
local: "config-schema.json",
size: 707,
- modtime: 1464139547,
+ modtime: 1464121931,
compressed: `
H4sIAAAJbogA/5SRPW7DMAyF5/oUhpOxjjp0ytoDdOgJVJmKGcCiQDJDUPju1U/c2kvhLobx+L73JOqr
adtuAHGMUZFCd2679wjhjYJaDMBt+vN4aT8iOPTobHE9Z+woboTJZmRUjWdjrkKhr+qJ+GIGtl77l1dT
@@ -216,7 +216,7 @@ T4aE9IoTdGU2V0tnbzoS/xG1dbMbUdPhbgx7GZK9zscuVu4jgy+HBy99HZ/yKxxMUjBgfi1ZdrjJYiL1
"/content-descriptor.json": {
local: "content-descriptor.json",
size: 616,
- modtime: 1464914956,
+ modtime: 1466030773,
compressed: `
H4sIAAAJbogA/4yRMVPDMAyF9/wKXdqR1gxMXWFngI1jcG0lVe9iG1kMhet/x4oTSGGgW/L8vvck+7MB
aD1mx5SEYmh30D4mDPcxiKWADPqFQeBhMkWGp4SOOnJ2JG40Yp3dAQer+EEk7Yw55hg2Vd1G7o1n28nm
@@ -228,23 +228,24 @@ AmOHXKZBD4uOEV+XM+U8dnlDg+1xq8uuyj8F0tRsfnpH6lzhNtPHf5OoBSjA/iSYr5hmvgkqz9QjX/Z5
"/defs-config.json": {
local: "defs-config.json",
- size: 1755,
- modtime: 1464139547,
+ size: 1822,
+ modtime: 1466096871,
compressed: `
-H4sIAAAJbogA/7xUzc7TMBA8N09hBY6BXhAHri1HVKQIOFZusm63xF5rvQEi1HfHSauS/qRKKV8PVeOx
-Z2bXY/t3olRaQigYvSC59INK52DQYTsKymsWLOpKsxJSCw9uRk40OmAVvwyuVe6hQIOF7vjZXvCoEAVb
-jwgW3fLjOCLSeGgNabWFQjpqh3smD9EXQm91xL8E4BOkpxGE0a3T49Qu+8v7BJa4OWe+ZjAtMxYb3m4D
-uVfTXt1TdPL+3S29/Kf2/09z5ut8o/ms5YckP/7yFKD8TCz3qlrt825DF/toruu7H0NpaGbdpFl/CgXs
-eRk38otWA6bCjafY9vO9Z7Z8vulXqmp797EYFeA34u9xyRzH36qk/3/QSplITHjkZpdozBLLiy5ffnsr
-3QAP+o7rf4NBTh+YuzegYNACg8frUEeWTCYRNcRWS5d+JL0RtHA9YF3Lhv7pyTzUs1xdPJuj2GQtDN/Q
-W1SwXppll8oQfUVUgXaDqSTtb5f8CQAA//8Cok052wYAAA==
+H4sIAAAJbogA/7xUzY7TMBA+b57CChwDe0EcuHY5oiJFwAGhyk3G21lijzWeAFGVd8dJS0nTpmop20PV
+eOzvx/7sWSdKpSWEgtELkkvfqfQBDDrsRkF5zYJFXWlWQmruwc3IiUYHrOKXwUeVeyjQYKF7fLYh3DFE
+wk4jFot++W4cK9J46ARp+QSF9NC+7pk8RF0Ig9Wx/ikA71UGHEEY3WO6m2qzv7gPYImbMfIlg+mQ0Wx4
+/RTIvbgf+L5HJ2/fnOLLf2r//zhnvs5Xmkdbvory/S9PAcqPxHIpq9U+7w90vonmOL/7MZWGZtZNmg2n
+UMCObZzIL0pNiAo3nuK2b689s+XtRT9TVdvDa0EO5l2CX/fY15cG22Yj/B97rq6qtB1Mfjvq7gvx98j2
+gOc/zGT4v+VKmUhMuKY5lGjMAsuDg3r+hCrdAE/qnrf/FQbZ71EXH0DBoAUmb+jWR5bc3cWqIbZa+osS
+Qa8ELRx/5LqWFf1T1936WSwPOu9ZaLIWph/5KShYL82iT2UKviSqQLvJVJLu1ya/AwAA//9M4B9rHgcA
+AA==
`,
},
"/defs-image.json": {
local: "defs-image.json",
size: 2528,
- modtime: 1464914958,
+ modtime: 1466030773,
compressed: `
H4sIAAAJbogA/7RWy27bMBC8+ysIJUAOfqjXGkGAoEGBnlIgPTVQgY20kphKpLqkgzqB/r2k3k+nrt0b
ueQOZ4Zjym8LxpwAlU8801wKZ8ucOwy54HamWAakub9LgJiW7D5D8UkKDVwgsS8pRMgeMvR5yH0o2lcl
@@ -263,7 +264,7 @@ Wb7IF38CAAD//wKthPngCQAA
"/defs.json": {
local: "defs.json",
size: 3193,
- modtime: 1464139547,
+ modtime: 1466096269,
compressed: `
H4sIAAAJbogA/7RWTXPaMBC98ys8tEfa2PIX9NYp/cghAzOZnjo9uGYBtSCpstxpmuG/VzLGWPZiMKWH
JPau9r23T6tYzwPHGS4gSyUVinI2fOMMp7CkjJq3zMkzWDhqLXm+WvNc6UdwZgLYO85UQhlI51FASpc0
@@ -282,7 +283,7 @@ MrVJbn8cB+ZnN/gbAAD//0JyEpx5DAAA
"/image-manifest-schema.json": {
local: "image-manifest-schema.json",
size: 1032,
- modtime: 1464914959,
+ modtime: 1466030773,
compressed: `
H4sIAAAJbogA/6RSPU/zMBDe8ytOacc39TswdWViQAxULIjBJOfkqsYOPoNUVf3v+KMujsoAdMyTez7u
8R0qgLpDbi1Njoyu11A/TKhvjXaSNFq4G2WPcC81KWQHjxO2pKiVcfpfoC+5HXCUgTo4N62F2LLRTUJX
@@ -297,7 +298,7 @@ X3p8DwgEAAA=
"/manifest-list-schema.json": {
local: "manifest-list-schema.json",
size: 1010,
- modtime: 1464139547,
+ modtime: 1461966159,
compressed: `
H4sIAAAJbogA/6ySMU/7MBDF93yKU9rxn/o/MHWFBQnEQMWCGExyaa5q7OAzSFXV747tS0qiMIDoUqkv
fu9+7+xjBpBXyKWjzpM1+Rryhw7NtTVek0EHt63eItxrQzWyhzsKP48dllRTqZPlX8xYctlgq6O/8b5b
diff --git a/schema/manifest_backwards_compatibility_test.go b/schema/manifest_backwards_compatibility_test.go
new file mode 100644
index 000000000..9a96f8e5a
--- /dev/null
+++ b/schema/manifest_backwards_compatibility_test.go
@@ -0,0 +1,139 @@
+// Copyright 2016 The Linux Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package schema_test
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/opencontainers/image-spec/schema"
+)
+
+var compatMap = map[string]string{
+ "application/vnd.docker.distribution.manifest.list.v2+json": "application/vnd.oci.image.manifest.list.v1+json",
+ "application/vnd.docker.distribution.manifest.v2+json": "application/vnd.oci.image.manifest.v1+json",
+ "application/vnd.docker.image.rootfs.diff.tar.gzip": "application/vnd.oci.image.rootfs.tar.gzip",
+ "application/vnd.docker.container.image.v1+json": "application/vnd.oci.image.serialization.config.v1+json",
+}
+
+// convertFormats converts Docker v2.2 image format JSON documents to OCI
+// format by simply replacing instances of the strings found in the compatMap
+// found in the input string.
+func convertFormats(input string) string {
+ out := input
+ for k, v := range compatMap {
+ out = strings.Replace(out, v, k, -1)
+ }
+ return out
+}
+
+func TestBackwardsCompatibilityManifest(t *testing.T) {
+ for i, tt := range []struct {
+ manifest string
+ digest string
+ fail bool
+ }{
+ // manifest pulled from docker hub using hash value
+ //
+ // curl -L -H "Authorization: Bearer ..." -H \
+ // "Accept: application/vnd.docker.distribution.manifest.v2+json" \
+ // https://registry-1.docker.io/v2/library/docker/manifests/sha256:888206c77cd2811ec47e752ba291e5b7734e3ef137dfd222daadaca39a9f17bc
+ {
+ digest: "sha256:888206c77cd2811ec47e752ba291e5b7734e3ef137dfd222daadaca39a9f17bc",
+ manifest: `{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
+ "config": {
+ "mediaType": "application/octet-stream",
+ "size": 3210,
+ "digest": "sha256:5359a4f250650c20227055957e353e8f8a74152f35fe36f00b6b1f9fc19c8861"
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
+ "size": 2310272,
+ "digest": "sha256:fae91920dcd4542f97c9350b3157139a5d901362c2abec284de5ebd1b45b4957"
+ },
+ {
+ "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
+ "size": 913022,
+ "digest": "sha256:f384f6ab36adad485192f09379c0b58dc612a3cde82c551e082a7c29a87c95da"
+ },
+ {
+ "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
+ "size": 9861668,
+ "digest": "sha256:ed0d2dd5e1a0e5e650a330a864c8a122e9aa91fa6ba9ac6f0bd1882e59df55e7"
+ },
+ {
+ "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
+ "size": 465,
+ "digest": "sha256:ec4d00b58417c45f7ddcfde7bcad8c9d62a7d6d5d17cdc1f7d79bcb2e22c1491"
+ }
+ ]
+}`,
+ fail: false,
+ },
+ } {
+ sum := sha256.Sum256([]byte(tt.manifest))
+ got := fmt.Sprintf("sha256:%s", hex.EncodeToString(sum[:]))
+ if tt.digest != got {
+ t.Errorf("test %d: expected digest %s but got %s", i, tt.digest, got)
+ }
+
+ manifest := convertFormats(tt.manifest)
+ r := strings.NewReader(manifest)
+ err := schema.MediaTypeManifest.Validate(r)
+
+ if got := err != nil; tt.fail != got {
+ t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err)
+ }
+ }
+}
+
+func TestBackwardsCompatibilityConfig(t *testing.T) {
+ for i, tt := range []struct {
+ manifest string
+ digest string
+ fail bool
+ }{
+ // manifest pulled from docker hub blob store
+ //
+ // curl -L -H "Authorization: Bearer ..." -H \
+ // -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
+ // https://registry-1.docker.io/v2/library/docker/blobs/sha256:5359a4f250650c20227055957e353e8f8a74152f35fe36f00b6b1f9fc19c8861
+ {
+ digest: "sha256:5359a4f250650c20227055957e353e8f8a74152f35fe36f00b6b1f9fc19c8861",
+ manifest: `{"architecture":"amd64","config":{"Hostname":"e5e5b3910a57","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","DOCKER_BUCKET=get.docker.com","DOCKER_VERSION=1.10.3","DOCKER_SHA256=d0df512afa109006a450f41873634951e19ddabf8c7bd419caeb5a526032d86d"],"Cmd":["sh"],"ArgsEscaped":true,"Image":"sha256:bda352ba7ab5823b7dc74b380c5ad1699edee278a6d2ebbe451129b108778742","Volumes":null,"WorkingDir":"","Entrypoint":["docker-entrypoint.sh"],"OnBuild":[],"Labels":{}},"container":"881be788b4387039b52fa195da9fe26f264385aa497ce650cfdcf3806c2d2021","container_config":{"Hostname":"e5e5b3910a57","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","DOCKER_BUCKET=get.docker.com","DOCKER_VERSION=1.10.3","DOCKER_SHA256=d0df512afa109006a450f41873634951e19ddabf8c7bd419caeb5a526032d86d"],"Cmd":["/bin/sh","-c","#(nop) CMD [\"sh\"]"],"ArgsEscaped":true,"Image":"sha256:bda352ba7ab5823b7dc74b380c5ad1699edee278a6d2ebbe451129b108778742","Volumes":null,"WorkingDir":"","Entrypoint":["docker-entrypoint.sh"],"OnBuild":[],"Labels":{}},"created":"2016-06-08T00:52:29.30472774Z","docker_version":"1.10.3","history":[{"created":"2016-06-08T00:48:01.932532048Z","created_by":"/bin/sh -c #(nop) ADD file:bca92e550bd2ce926584aef2032464b6ebf543ce69133b6602c781866165d703 in /"},{"created":"2016-06-08T00:52:10.503417531Z","created_by":"/bin/sh -c apk add --no-cache \t\tca-certificates \t\tcurl \t\topenssl"},{"created":"2016-06-08T00:52:10.700704697Z","created_by":"/bin/sh -c #(nop) ENV DOCKER_BUCKET=get.docker.com","empty_layer":true},{"created":"2016-06-08T00:52:25.746175479Z","created_by":"/bin/sh -c #(nop) ENV DOCKER_VERSION=1.10.3","empty_layer":true},{"created":"2016-06-08T00:52:25.954613633Z","created_by":"/bin/sh -c #(nop) ENV DOCKER_SHA256=d0df512afa109006a450f41873634951e19ddabf8c7bd419caeb5a526032d86d","empty_layer":true},{"created":"2016-06-08T00:52:28.173693898Z","created_by":"/bin/sh -c curl -fSL \"https://${DOCKER_BUCKET}/builds/Linux/x86_64/docker-$DOCKER_VERSION\" -o /usr/local/bin/docker \t\u0026\u0026 echo \"${DOCKER_SHA256} /usr/local/bin/docker\" | sha256sum -c - \t\u0026\u0026 chmod +x /usr/local/bin/docker"},{"created":"2016-06-08T00:52:28.924486515Z","created_by":"/bin/sh -c #(nop) COPY file:50006c902e7677711aeffe4ab7b7042d649618b96dec760f322a8566dd83ab25 in /usr/local/bin/"},{"created":"2016-06-08T00:52:29.121963047Z","created_by":"/bin/sh -c #(nop) ENTRYPOINT \u0026{[\"docker-entrypoint.sh\"]}","empty_layer":true},{"created":"2016-06-08T00:52:29.30472774Z","created_by":"/bin/sh -c #(nop) CMD [\"sh\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:77f08abee8bf9334407f52d104e1891283018450b3c196118ddfe31505126b87","sha256:707d16737060172b977d5f7eaaddfcfaae1008472193d7e8e5a01111a5f8dd5c","sha256:44da042e7b2458ee0b3877f2321cdf4fd45a49b9b51e00492c2ba68055573eff","sha256:1bc2be83dce13b9bac9476c9c1d2ca6e0db3e07b443f7298fc5a1af75b2cb4ef"]}}`,
+ fail: false,
+ },
+ } {
+ sum := sha256.Sum256([]byte(tt.manifest))
+ got := fmt.Sprintf("sha256:%s", hex.EncodeToString(sum[:]))
+ if tt.digest != got {
+ t.Errorf("test %d: expected digest %s but got %s", i, tt.digest, got)
+ }
+
+ manifest := convertFormats(tt.manifest)
+ r := strings.NewReader(manifest)
+ err := schema.MediaTypeImageSerializationConfig.Validate(r)
+
+ if got := err != nil; tt.fail != got {
+ t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err)
+ }
+ }
+}
diff --git a/serialization.md b/serialization.md
index b06a66a8e..0f24bdf69 100644
--- a/serialization.md
+++ b/serialization.md
@@ -297,7 +297,7 @@ Note: whitespace has been added to this example for clarity. Whitespace is OPTIO
Volumes struct
If a file or folder exists within the image with the same path as a data volume, that file or folder is replaced with the data volume and is never merged.