summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 18:10:12 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 18:10:12 +0000
commitaaf261d1e385f1b1c1292ead4ccbd631f6b26dc9 (patch)
tree1063057bfb2f9e946f939f5b18347295988cfb99
parentInitial commit. (diff)
downloadgolang-github-opencontainers-image-spec-aaf261d1e385f1b1c1292ead4ccbd631f6b26dc9.tar.xz
golang-github-opencontainers-image-spec-aaf261d1e385f1b1c1292ead4ccbd631f6b26dc9.zip
Adding upstream version 1.1.0~rc5.upstream/1.1.0_rc5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.github/PULL_REQUEST_TEMPLATE/maintainer_nomination.md23
-rw-r--r--.github/workflows/docs-and-linting.yml45
-rw-r--r--.gitignore3
-rw-r--r--.golangci.yml23
-rw-r--r--.header14
-rw-r--r--.markdownlint.yml18
-rwxr-xr-x.tool/check-license16
-rw-r--r--.tool/genheader.go54
-rw-r--r--CODEOWNERS3
-rw-r--r--EMERITUS.md11
-rw-r--r--GOVERNANCE.md70
-rw-r--r--HACKING.md105
-rw-r--r--LICENSE191
-rw-r--r--MAINTAINERS8
-rw-r--r--Makefile138
-rw-r--r--README.md162
-rw-r--r--RELEASES.md98
-rw-r--r--annotations.md83
-rw-r--r--artifacts-guidance.md6
-rw-r--r--config.md320
-rw-r--r--considerations.md147
-rw-r--r--conversion.md130
-rw-r--r--descriptor.md221
-rw-r--r--go.mod18
-rw-r--r--go.sum25
-rw-r--r--identity/chainid.go67
-rw-r--r--identity/chainid_test.go95
-rw-r--r--identity/helpers.go40
-rw-r--r--image-index.md187
-rw-r--r--image-layout.md208
-rw-r--r--img/build-diagram.pngbin0 -> 43326 bytes
-rw-r--r--img/media-types.dot17
-rw-r--r--img/media-types.pngbin0 -> 46040 bytes
-rw-r--r--img/run-diagram.pngbin0 -> 9640 bytes
-rw-r--r--implementations.md26
-rw-r--r--layer.md344
-rw-r--r--manifest.md262
-rw-r--r--media-types.md90
-rw-r--r--project.md6
-rw-r--r--schema/backwards_compatibility_test.go223
-rw-r--r--schema/config-schema.json155
-rw-r--r--schema/config_test.go260
-rw-r--r--schema/content-descriptor.json41
-rw-r--r--schema/defs-descriptor.json26
-rw-r--r--schema/defs.json97
-rw-r--r--schema/descriptor_test.go354
-rw-r--r--schema/doc.go16
-rw-r--r--schema/error.go57
-rw-r--r--schema/image-index-schema.json100
-rw-r--r--schema/image-layout-schema.json18
-rw-r--r--schema/image-manifest-schema.json45
-rw-r--r--schema/imageindex_test.go316
-rw-r--r--schema/imagelayout_test.go56
-rw-r--r--schema/loader.go125
-rw-r--r--schema/manifest_test.go347
-rw-r--r--schema/schema.go78
-rw-r--r--schema/spec_test.go216
-rw-r--r--schema/validator.go253
-rw-r--r--spec.md70
-rw-r--r--specs-go/v1/annotations.go62
-rw-r--r--specs-go/v1/config.go111
-rw-r--r--specs-go/v1/descriptor.go80
-rw-r--r--specs-go/v1/index.go38
-rw-r--r--specs-go/v1/layout.go32
-rw-r--r--specs-go/v1/manifest.go41
-rw-r--r--specs-go/v1/mediatype.go75
-rw-r--r--specs-go/version.go32
-rw-r--r--specs-go/versioned.go23
68 files changed, 6621 insertions, 0 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE/maintainer_nomination.md b/.github/PULL_REQUEST_TEMPLATE/maintainer_nomination.md
new file mode 100644
index 0000000..82a1b4a
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE/maintainer_nomination.md
@@ -0,0 +1,23 @@
+# Nomination for a New Maintainer
+
+## Nominating Maintainer
+
+Name of the existing OCI maintainer with GitHub username
+
+## New Maintainer
+
+Name of the new maintainer with GitHub username
+
+## Justification
+
+Highlight any work contributed by the new maintainer. Examples of contributions may be:
+
+- Community involvement in mailing lists and meetings
+- Involvement in any OCI working groups
+- Contributions to any of the OCI git repositories
+
+Other considerations may be:
+
+- Diversity of organizations
+- Time involved in the community
+- Personal experience working with the new maintainer
diff --git a/.github/workflows/docs-and-linting.yml b/.github/workflows/docs-and-linting.yml
new file mode 100644
index 0000000..248791c
--- /dev/null
+++ b/.github/workflows/docs-and-linting.yml
@@ -0,0 +1,45 @@
+name: Render and Lint Documentation
+
+on:
+ pull_request:
+ branches_ignore: []
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ go: ['1.18', '1.19', '1.20']
+
+ name: Documentation and Linting
+ steps:
+
+ - uses: actions/checkout@v3
+ with:
+ path: go/src/github.com/opencontainers/image-spec
+
+ - uses: actions/setup-go@v3
+ with:
+ go-version: ${{ matrix.go }}
+
+ - name: Render and Lint
+ env:
+ GOPATH: /home/runner/work/image-spec/image-spec/go
+ run: |
+ export PATH=$GOPATH/bin:$PATH
+ cd go/src/github.com/opencontainers/image-spec
+ make install.tools
+ go get -t -d ./...
+ ls ../
+ make
+ make .gitvalidation
+ make lint
+ make check-license
+ make test
+ make docs
+
+ - name: documentation artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: oci-docs
+ path: go/src/github.com/opencontainers/image-spec/output
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dcc5d55
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/oci-validate-examples
+output
+header.html
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000..a566f84
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,23 @@
+run:
+ timeout: 10m
+
+linters:
+ disable-all: true
+ enable:
+ - dupl
+ - gofmt
+ - goimports
+ - gosimple
+ - govet
+ - ineffassign
+ - misspell
+ - nakedret
+ - revive
+ - unused
+ - staticcheck
+
+linters-settings:
+ gofmt:
+ simplify: true
+ dupl:
+ threshold: 400
diff --git a/.header b/.header
new file mode 100644
index 0000000..868470b
--- /dev/null
+++ b/.header
@@ -0,0 +1,14 @@
+// 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.
+
diff --git a/.markdownlint.yml b/.markdownlint.yml
new file mode 100644
index 0000000..6135518
--- /dev/null
+++ b/.markdownlint.yml
@@ -0,0 +1,18 @@
+# all lists use a `-`
+MD004:
+ style: dash
+
+# allow tabs in code blocks (for Go)
+MD010:
+ code_blocks: false
+
+# disable line length, prefer one sentence per line for PRs
+MD013: false
+
+# emphasis with underscore (`_emphasis_`)
+MD049:
+ style: "underscore"
+
+# bold with asterisk (`**bold**`)
+MD050:
+ style: "asterisk"
diff --git a/.tool/check-license b/.tool/check-license
new file mode 100755
index 0000000..11baafc
--- /dev/null
+++ b/.tool/check-license
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+set -o errexit
+set -o nounset
+set -o pipefail
+
+ret=0
+
+for file in $(find . -type f -iname '*.go' ! -path './vendor/*'); do
+ if ! head -n3 "${file}" | grep -Eq "(Copyright|generated|GENERATED)"; then
+ echo "${file}:missing license header"
+ ret=1
+ fi
+done
+
+exit $ret
diff --git a/.tool/genheader.go b/.tool/genheader.go
new file mode 100644
index 0000000..6d1e176
--- /dev/null
+++ b/.tool/genheader.go
@@ -0,0 +1,54 @@
+// Copyright 2017 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 main
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+ "text/template"
+
+ specs "github.com/opencontainers/image-spec/specs-go"
+)
+
+var headerTemplate = template.Must(template.New("gen").Parse(`<title>image-spec {{.Version}}</title>
+<base href="https://raw.githubusercontent.com/opencontainers/image-spec/{{.Branch}}/">`))
+
+type Obj struct {
+ Version string
+ Branch string
+}
+
+func main() {
+ obj := Obj{
+ Version: specs.Version,
+ Branch: specs.Version,
+ }
+ if strings.HasSuffix(specs.Version, "-dev") {
+ cmd := exec.Command("git", "log", "-1", `--pretty=%H`, "HEAD")
+ var out bytes.Buffer
+ cmd.Stdout = &out
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+
+ obj.Branch = strings.Trim(out.String(), " \n\r")
+ }
+ headerTemplate.Execute(os.Stdout, obj)
+}
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 0000000..26c4127
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1,3 @@
+
+* @jonjohnsonjr @jonboulle @stevvooe @sudo-bmitch @sajayantony @tianon @vbatts @cyphar
+
diff --git a/EMERITUS.md b/EMERITUS.md
new file mode 100644
index 0000000..452ff0b
--- /dev/null
+++ b/EMERITUS.md
@@ -0,0 +1,11 @@
+# Emeritus
+
+We would like to acknowledge previous OCI image spec maintainers and their huge contributions to our collective success:
+
+- Brandon Philips (@philips)
+- Brendan Burns (@brendandburns)
+- Jason Bouzane (@jbouzane)
+- John Starks (@jstarks)
+- Keyang Xie (@xiekeyang)
+
+We thank these members for their service to the OCI community.
diff --git a/GOVERNANCE.md b/GOVERNANCE.md
new file mode 100644
index 0000000..7fab7b3
--- /dev/null
+++ b/GOVERNANCE.md
@@ -0,0 +1,70 @@
+# Project governance
+
+The [OCI charter][charter] §5.b.viii tasks an OCI Project's maintainers (listed in the repository's MAINTAINERS file and sometimes referred to as "the TDC", [§5.e][charter]) with:
+
+> Creating, maintaining and enforcing governance guidelines for the TDC, approved by the maintainers, and which shall be posted visibly for the TDC.
+
+This section describes generic rules and procedures for fulfilling that mandate.
+
+## Proposing a motion
+
+A maintainer SHOULD propose a motion on the <dev@opencontainers.org> mailing list (except [security issues](#security-issues)) with another maintainer as a co-sponsor.
+
+## Voting
+
+Voting on a proposed motion SHOULD happen on the <dev@opencontainers.org> mailing list (except [security issues](#security-issues)) with maintainers posting LGTM or REJECT.
+Maintainers MAY also explicitly not vote by posting ABSTAIN (which is useful to revert a previous vote).
+Maintainers MAY post multiple times (e.g. as they revise their position based on feedback), but only their final post counts in the tally.
+A proposed motion is adopted if two-thirds of votes cast, a quorum having voted, are in favor of the release.
+
+Voting SHOULD remain open for a week to collect feedback from the wider community and allow the maintainers to digest the proposed motion.
+Under exceptional conditions (e.g. non-major security fix releases) proposals which reach quorum with unanimous support MAY be adopted earlier.
+
+A maintainer MAY choose to reply with REJECT.
+A maintainer posting a REJECT MUST include a list of concerns or links to written documentation for those concerns (e.g. GitHub issues or mailing-list threads).
+The maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM.
+However, a motion MAY be adopted with REJECTs, as outlined in the previous paragraphs.
+
+## Quorum
+
+A quorum is established when at least two-thirds of maintainers have voted.
+
+For projects that are not specifications, a [motion to release](#proposing-a-motion) MAY be adopted if the tally is at least three LGTMs and no REJECTs, even if three votes does not meet the usual two-thirds quorum.
+
+## Security issues
+
+Motions with sensitive security implications MUST be proposed on the <security@opencontainers.org> mailing list instead of <dev@opencontainers.org>, but should otherwise follow the standard [proposal](#proposing-a-motion) process.
+The <security@opencontainers.org> mailing list includes all members of the TOB.
+The TOB will contact the project maintainers and provide a channel for discussing and voting on the motion, but voting will otherwise follow the standard [voting](#voting) and [quorum](#quorum) rules.
+The TOB and project maintainers will work together to notify affected parties before making an adopted motion public.
+
+## Amendments
+
+The [project governance](#project-governance) rules and procedures MAY be amended or replaced using the procedures themselves.
+The MAINTAINERS of this project governance document is the total set of MAINTAINERS from all Open Containers projects (runC, runtime-spec, and image-spec).
+
+## Subject templates
+
+Maintainers are busy and get lots of email.
+To make project proposals recognizable, proposed motions SHOULD use the following subject templates.
+
+### Proposing a motion template
+
+> [{project} VOTE]: {motion description} (closes {end of voting window})
+
+For example:
+
+> [image-spec VOTE]: Tag 0647920 as 1.0.0-rc (closes 2016-06-03 20:00 UTC)
+
+### Tallying results template
+
+After voting closes, a maintainer SHOULD post a tally to the motion thread with a subject template like:
+
+> [{project} {status}]: {motion description} (+{LGTMs} -{REJECTs} #{ABSTAINs})
+
+Where `{status}` is either `adopted` or `rejected`.
+For example:
+
+> [image-spec adopted]: Tag 0647920 as 1.0.0-rc (+6 -0 #3)
+
+[charter]: https://github.com/opencontainers/tob/blob/main/CHARTER.md
diff --git a/HACKING.md b/HACKING.md
new file mode 100644
index 0000000..15ad62f
--- /dev/null
+++ b/HACKING.md
@@ -0,0 +1,105 @@
+# Hacking Guide
+
+## Overview
+
+This guide contains instructions for building artifacts contained in this repository.
+
+### Go
+
+This spec includes several Go packages, and a command line tool considered to be a reference implementation of the OCI image specification.
+
+Prerequisites:
+
+- Go - current release only, earlier releases are not supported
+- make
+
+The following make targets are relevant for any work involving the Go packages.
+
+### Linting
+
+The included Go source code is being examined for any linting violations not included in the standard Go compiler.
+Linting is done using [golangci-lint][golangci-lint].
+
+Invocation:
+
+```shell
+make lint
+```
+
+### Tests
+
+This target executes all Go based tests.
+
+Invocation:
+
+```shell
+make test
+make validate-examples
+```
+
+### JSON schema formatting
+
+This target auto-formats all JSON files in the `schema` directory using the `jq` tool.
+
+Prerequisites:
+
+- [jq][jq] >=1.5
+
+Invocation:
+
+```shell
+make fmt
+```
+
+### OCI image specification PDF/HTML documentation files
+
+This target generates a PDF/HTML version of the OCI image specification.
+
+Prerequisites:
+
+- [Docker][docker]
+
+Invocation:
+
+```shell
+make docs
+```
+
+### License header check
+
+This target checks if the source code includes necessary headers.
+
+Invocation:
+
+```shell
+make check-license
+```
+
+### Clean build artifacts
+
+This target cleans all generated/compiled artifacts.
+
+Invocation:
+
+```shell
+make clean
+```
+
+### Create PNG images from dot files
+
+This target generates PNG image files from DOT source files in the `img` directory.
+
+Prerequisites:
+
+- [graphviz][graphviz]
+
+Invocation:
+
+```shell
+make img/media-types.png
+```
+
+[docker]: https://www.docker.com/
+[golangci-lint]: https://github.com/golangci/golangci-lint
+[graphviz]: https://www.graphviz.org/
+[jq]: https://stedolan.github.io/jq/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9fdc20f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,191 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ 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.
diff --git a/MAINTAINERS b/MAINTAINERS
new file mode 100644
index 0000000..2f35a92
--- /dev/null
+++ b/MAINTAINERS
@@ -0,0 +1,8 @@
+Brandon Mitchell <git@bmitch.net> (@sudo-bmitch)
+Jon Johnson <jon.johnson@chainguard.dev> (@jonjohnsonjr)
+Jonathan Boulle <jonathanboulle@gmail.com> (@jonboulle)
+Sajay Antony <sajaya@microsoft.com> (@sajayantony)
+Stephen Day <stevvooe@gmail.com> (@stevvooe)
+Tianon Gravi <admwiggin@gmail.com> (@tianon)
+Vincent Batts <vbatts@hashbangbash.com> (@vbatts)
+Aleksa Sarai <asarai@suse.de> (@cyphar)
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..828b2a1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,138 @@
+EPOCH_TEST_COMMIT ?= v0.2.0
+
+DOCKER ?= $(shell command -v docker 2>/dev/null)
+PANDOC ?= $(shell command -v pandoc 2>/dev/null)
+
+GOPATH:=$(shell go env GOPATH)
+
+OUTPUT_DIRNAME ?= output
+DOC_FILENAME ?= oci-image-spec
+
+PANDOC_CONTAINER ?= ghcr.io/opencontainers/pandoc:2.9.2.1-8.fc33.x86_64@sha256:5d81ff930a043295a557be8b003ece2a33d14e91b28c50d368413b83372f8d28
+ifeq "$(strip $(PANDOC))" ''
+ ifneq "$(strip $(DOCKER))" ''
+ PANDOC = $(DOCKER) run \
+ --rm \
+ -v $(shell pwd)/:/input/:ro \
+ -v $(shell pwd)/$(OUTPUT_DIRNAME)/:/$(OUTPUT_DIRNAME)/ \
+ -u $(shell id -u) \
+ --workdir /input \
+ $(PANDOC_CONTAINER)
+ PANDOC_SRC := /input/
+ PANDOC_DST := /
+ endif
+endif
+
+# These docs are in an order that determines how they show up in the PDF/HTML docs.
+DOC_FILES := \
+ spec.md \
+ media-types.md \
+ descriptor.md \
+ image-layout.md \
+ manifest.md \
+ image-index.md \
+ layer.md \
+ config.md \
+ annotations.md \
+ conversion.md \
+ considerations.md \
+ implementations.md
+
+FIGURE_FILES := \
+ img/media-types.png
+
+MARKDOWN_LINT_VER?=v0.8.1
+
+TOOLS := gitvalidation
+
+default: check-license lint test
+
+.PHONY: fmt
+fmt: ## format the json with indentation
+ for i in schema/*.json ; do jq --indent 2 -M . "$${i}" > xx && cat xx > "$${i}" && rm xx ; done
+
+.PHONY: docs
+docs: $(OUTPUT_DIRNAME)/$(DOC_FILENAME).pdf $(OUTPUT_DIRNAME)/$(DOC_FILENAME).html ## generate a PDF/HTML version of the OCI image specification
+
+ifeq "$(strip $(PANDOC))" ''
+$(OUTPUT_DIRNAME)/$(DOC_FILENAME).pdf: $(DOC_FILES) $(FIGURE_FILES)
+ $(error cannot build $@ without either pandoc or docker)
+else
+$(OUTPUT_DIRNAME)/$(DOC_FILENAME).pdf: $(DOC_FILES) $(FIGURE_FILES)
+ @mkdir -p $(OUTPUT_DIRNAME)/ && \
+ $(PANDOC) -f gfm -t latex --pdf-engine=xelatex -V geometry:margin=0.5in,bottom=0.8in -V block-headings -o $(PANDOC_DST)$@ $(patsubst %,$(PANDOC_SRC)%,$(DOC_FILES))
+ ls -sh $(realpath $@)
+
+$(OUTPUT_DIRNAME)/$(DOC_FILENAME).html: header.html $(DOC_FILES) $(FIGURE_FILES)
+ @mkdir -p $(OUTPUT_DIRNAME)/ && \
+ cp -ap img/ $(shell pwd)/$(OUTPUT_DIRNAME)/&& \
+ $(PANDOC) -f gfm -t html5 -H $(PANDOC_SRC)header.html --standalone -o $(PANDOC_DST)$@ $(patsubst %,$(PANDOC_SRC)%,$(DOC_FILES))
+ ls -sh $(realpath $@)
+endif
+
+header.html: .tool/genheader.go specs-go/version.go
+ go run .tool/genheader.go > $@
+
+.PHONY: validate-examples
+validate-examples: schema/schema.go ## validate examples in the specification markdown files
+ go test -run TestValidate ./schema
+
+.PHONY: check-license
+check-license: ## check license headers in source files
+ @echo "checking license headers"
+ @./.tool/check-license
+
+.PHONY: lint
+
+.PHONY: lint
+lint: lint-go lint-md ## Run all linters
+
+.PHONY: lint-go
+lint-go: .install.lint ## lint check of Go files using golangci-lint
+ @echo "checking Go lint"
+ @GO111MODULE=on $(GOPATH)/bin/golangci-lint run
+
+.PHONY: lint-md
+lint-md: ## Run linting for markdown
+ docker run --rm -v "$(PWD):/workdir:ro" docker.io/davidanson/markdownlint-cli2:$(MARKDOWN_LINT_VER) \
+ "**/*.md" "#vendor"
+
+.PHONY: test
+test: ## run the unit tests
+ go test -race -cover $(shell go list ./... | grep -v /vendor/)
+
+img/%.png: img/%.dot ## generate PNG from dot file
+ dot -Tpng $^ > $@
+
+# When this is running in GitHub, it will only check the commit range
+.PHONY: .gitvalidation
+.gitvalidation:
+ @which git-validation > /dev/null 2>/dev/null || (echo "ERROR: git-validation not found. Consider 'make install.tools' target" && false)
+ifdef GITHUB_SHA
+ $(GOPATH)/bin/git-validation -q -run DCO,short-subject,dangling-whitespace -range $(GITHUB_SHA)..HEAD
+else
+ $(GOPATH)/bin/git-validation -v -run DCO,short-subject,dangling-whitespace -range $(EPOCH_TEST_COMMIT)..HEAD
+endif
+
+.PHONY: .install.tools
+install.tools: $(TOOLS:%=.install.%)
+
+.PHONY: .install.lint
+.install.lint:
+ case "$$(go env GOVERSION)" in \
+ go1.18.*) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.47.3;; \
+ go1.19.*) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.1;; \
+ *) go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest;; \
+ esac
+
+.PHONY: .install.gitvalidation
+.install.gitvalidation:
+ go install github.com/vbatts/git-validation@latest
+
+.PHONY: clean
+clean: ## clean all generated and compiled artifacts
+ rm -rf *~ $(OUTPUT_DIRNAME) header.html
+
+.PHONY: help
+help: # Display help
+ @awk -F ':|##' '/^[^\t].+?:.*?##/ { printf "\033[36m%-30s\033[0m %s\n", $$1, $$NF }' $(MAKEFILE_LIST)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..548510f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,162 @@
+# OCI Image Format Specification
+
+![GitHub Actions for Docs and Linting](https://img.shields.io/github/actions/workflow/status/opencontainers/image-spec/docs-and-linting.yml?branch=main&label=GHA%20docs%20and%20linting)
+![License](https://img.shields.io/github/license/opencontainers/image-spec)
+[![Go Reference](https://pkg.go.dev/badge/github.com/opencontainers/image-spec.svg)](https://pkg.go.dev/github.com/opencontainers/image-spec)
+
+The OCI Image Format project creates and maintains the software shipping container image format spec (OCI Image Format).
+
+**[The specification can be found here](spec.md).**
+
+This repository also provides [Go types](specs-go), [intra-blob validation tooling, and JSON Schema](schema).
+The Go types and validation should be compatible with the current Go release; earlier Go releases are not supported.
+
+Additional documentation about how this group operates:
+
+- [Code of Conduct][code-of-conduct]
+- [Roadmap](#roadmap)
+- [Releases](RELEASES.md)
+- [Project Documentation](project.md)
+
+## Running an OCI Image
+
+The OCI Image Format partner project is the [OCI Runtime Spec project](https://github.com/opencontainers/runtime-spec).
+The Runtime Specification outlines how to run a "[filesystem bundle](https://github.com/opencontainers/runtime-spec/blob/master/bundle.md)" that is unpacked on disk.
+At a high-level an OCI implementation would download an OCI Image then unpack that image into an OCI Runtime filesystem bundle.
+At this point the OCI Runtime Bundle would be run by an OCI Runtime.
+
+This entire workflow supports the UX that users have come to expect from container engines like Docker and rkt: primarily, the ability to run an image with no additional arguments:
+
+- docker run example.com/org/app:v1.0.0
+- rkt run example.com/org/app,version=v1.0.0
+
+To support this UX the OCI Image Format contains sufficient information to launch the application on the target platform (e.g. command, arguments, environment variables, etc).
+
+## Distributing an OCI Image
+
+The [OCI Distribution Spec Project](https://github.com/opencontainers/distribution-spec/) defines an API protocol to facilitate and standardize the distribution of content.
+This API includes support for pushing and pulling OCI images to an OCI conformant registry.
+
+## FAQ
+
+**Q: What happens to AppC or Docker Image Formats?**
+
+A: Existing formats can continue to be a proving ground for technologies, as needed.
+The OCI Image Format project strives to provide a dependable open specification that can be shared between different tools and be evolved for years or decades of compatibility; as the deb and rpm format have.
+
+Find more [FAQ on the OCI site](https://www.opencontainers.org/faq).
+
+## Roadmap
+
+The [GitHub milestones](https://github.com/opencontainers/image-spec/milestones) lay out the path to the future improvements.
+
+## Contributing
+
+Development happens on GitHub for the spec.
+Issues are used for bugs and actionable items and longer discussions can happen on the [mailing list](#mailing-list).
+
+The specification and code is licensed under the Apache 2.0 license found in the `LICENSE` file of this repository.
+
+### Discuss your design
+
+The project welcomes submissions, but please let everyone know what you are working on.
+
+Before undertaking a nontrivial change to this specification, send mail to the [mailing list](#mailing-list) to discuss what you plan to do.
+This gives everyone a chance to validate the design, helps prevent duplication of effort, and ensures that the idea fits.
+It also guarantees that the design is sound before code is written; a GitHub pull-request is not the place for high-level discussions.
+
+Typos and grammatical errors can go straight to a pull-request.
+When in doubt, start on the [mailing-list](#mailing-list).
+
+### Meetings
+
+Please see the [OCI org repository README](https://github.com/opencontainers/org#meetings) for the most up-to-date information on OCI contributor and maintainer meeting schedules.
+You can also find links to meeting agendas and minutes for all prior meetings.
+
+### Mailing List
+
+You can subscribe and join the mailing list on [Google Groups](https://groups.google.com/a/opencontainers.org/forum/#!forum/dev).
+
+### IRC
+
+OCI discussion happens on #opencontainers on Freenode ([logs][irc-logs]).
+
+### Markdown style
+
+To keep consistency throughout the Markdown files in the Open Container spec all files should be formatted one sentence per line.
+This fixes two things: it makes diffing easier with git and it resolves fights about line wrapping length.
+For example, this paragraph will span three lines in the Markdown source.
+
+### Git commit
+
+#### Sign your work
+
+The sign-off is a simple line at the end of the explanation for the patch, which certifies that you wrote it or otherwise have the right to pass it on as an open-source patch.
+The rules are pretty simple: if you can certify the below (from [developercertificate.org](https://developercertificate.org/)):
+
+```text
+Developer Certificate of Origin
+Version 1.1
+
+Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
+660 York Street, Suite 102,
+San Francisco, CA 94110 USA
+
+Everyone is permitted to copy and distribute verbatim copies of this
+license document, but changing it is not allowed.
+
+
+Developer's Certificate of Origin 1.1
+
+By making a contribution to this project, I certify that:
+
+(a) The contribution was created in whole or in part by me and I
+ have the right to submit it under the open source license
+ indicated in the file; or
+
+(b) The contribution is based upon previous work that, to the best
+ of my knowledge, is covered under an appropriate open source
+ license and I have the right under that license to submit that
+ work with modifications, whether created in whole or in part
+ by me, under the same open source license (unless I am
+ permitted to submit under a different license), as indicated
+ in the file; or
+
+(c) The contribution was provided directly to me by some other
+ person who certified (a), (b) or (c) and I have not modified
+ it.
+
+(d) I understand and agree that this project and the contribution
+ are public and that a record of the contribution (including all
+ personal information I submit with it, including my sign-off) is
+ maintained indefinitely and may be redistributed consistent with
+ this project or the open source license(s) involved.
+```
+
+then you just add a line to every git commit message:
+
+```text
+Signed-off-by: Joe Smith <joe@gmail.com>
+```
+
+using your real name (sorry, no pseudonyms or anonymous contributions.)
+
+You can add the sign off when creating the git commit via `git commit -s`.
+
+### Commit Style
+
+Simple house-keeping for clean git history.
+Read more on [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) or the Discussion section of [`git-commit(1)`](https://git-scm.com/docs/git-commit).
+
+1. Separate the subject from body with a blank line
+2. Limit the subject line to 50 characters
+3. Capitalize the subject line
+4. Do not end the subject line with a period
+5. Use the imperative mood in the subject line
+6. Wrap the body at 72 characters
+7. Use the body to explain what and why vs. how
+ - If there was important/useful/essential conversation or information, copy or include a reference
+8. When possible, one keyword to scope the change in the subject (i.e. "README: ...", "runtime: ...")
+
+[code-of-conduct]: https://github.com/opencontainers/org/blob/master/CODE_OF_CONDUCT.md
+[irc-logs]: http://ircbot.wl.linuxfoundation.org/eavesdrop/%23opencontainers/
diff --git a/RELEASES.md b/RELEASES.md
new file mode 100644
index 0000000..009a43a
--- /dev/null
+++ b/RELEASES.md
@@ -0,0 +1,98 @@
+# Releases
+
+The release process hopes to encourage early, consistent consensus-building during project development.
+The mechanisms used are regular community communication on the mailing list about progress, scheduled meetings for issue resolution and release triage, and regularly paced and communicated releases.
+Releases are proposed and adopted or rejected using the usual [project governance](GOVERNANCE.md) rules and procedures.
+
+An anti-pattern that we want to avoid is heavy development or discussions "late cycle" around major releases.
+We want to build a community that is involved and communicates consistently through all releases instead of relying on "silent periods" as a judge of stability.
+
+## Parallel releases
+
+A single project MAY consider several motions to release in parallel.
+However each motion to release after the initial 0.1.0 MUST be based on a previous release that has already landed.
+
+For example, runtime-spec maintainers may propose a v1.0.0-rc2 on the 1st of the month and a v0.9.1 bugfix on the 2nd of the month.
+They may not propose a v1.0.0-rc3 until the v1.0.0-rc2 is accepted (on the 7th if the vote initiated on the 1st passes).
+
+## Specifications
+
+The OCI maintains three categories of projects: specifications, applications, and conformance-testing tools.
+However, specification releases have special restrictions in the [OCI charter][charter]:
+
+- They are the target of backwards compatibility (§7.g), and
+- They are subject to the OFWa patent grant (§8.d and e).
+
+To avoid unfortunate side effects (onerous backwards compatibility requirements or Member resignations), the following additional procedures apply to specification releases:
+
+### Planning a release
+
+Every OCI specification project SHOULD hold meetings that involve maintainers reviewing pull requests, debating outstanding issues, and planning releases.
+This meeting MUST be advertised on the project README and MAY happen on a phone call, video conference, or on IRC.
+Maintainers MUST send updates to the <dev@opencontainers.org> with results of these meetings.
+
+Before the specification reaches v1.0.0, the meetings SHOULD be weekly.
+Once a specification has reached v1.0.0, the maintainers may alter the cadence, but a meeting MUST be held within four weeks of the previous meeting.
+
+The release plans, corresponding milestones and estimated due dates MUST be published on GitHub (e.g. <https://github.com/opencontainers/runtime-spec/milestones>).
+GitHub milestones and issues are only used for community organization and all releases MUST follow the [project governance](GOVERNANCE.md) rules and procedures.
+
+### Timelines
+
+Specifications have a variety of different timelines in their lifecycle.
+
+- Pre-v1.0.0 specifications SHOULD release on a monthly cadence to garner feedback.
+- Major specification releases MUST release at least three release candidates spaced a minimum of one week apart.
+ This means a major release like a v1.0.0 or v2.0.0 release will take 1 month at minimum: one week for rc1, one week for rc2, one week for rc3, and one week for the major release itself.
+ Maintainers SHOULD strive to make zero breaking changes during this cycle of release candidates and SHOULD restart the three-candidate count when a breaking change is introduced.
+ For example if a breaking change is introduced in v1.0.0-rc2 then the series would end with v1.0.0-rc4 and v1.0.0.
+- Minor and patch releases SHOULD be made on an as-needed basis.
+
+[charter]: https://github.com/opencontainers/tob/blob/main/CHARTER.md
+
+## Checklist
+
+Releases usually follow a few steps:
+
+- [ ] prepare a pull-request for the release
+ - [ ] a commit updating `./ChangeLog`
+ - [ ] `git log --oneline --no-merges --decorate --name-status v1.0.1..HEAD | vim -`
+ - [ ] `:% s/(pr\/\(\d*\))\(.*\)/\2 (#\1)/` to move the PR to the end of line and match previous formatting
+ - [ ] review `(^M|^A|^D)` for impact of the commit
+ - [ ] group commits to `Additions:`, `Minor fixes and documentation:`, `Breaking changes:`
+ - [ ] delete the `(^M|^A|^D)` lines, `:%!grep -vE '(^M|^A|^D)'`
+ - [ ] merge multi-commit PRs (so each line has a `(#num)` suffix)
+ - [ ] drop hash and indent, `:'<,'> s/^\w* /^I* /`
+ - [ ] a commit bumping `./specs-go/version.go` to next version and empty the `VersionDev` variable
+ - [ ] a commit adding back the "+dev" to `VersionDev`
+- [ ] send email to <dev@opencontainers.org>
+ - [ ] copy the exact commit hash for bumping the version from the pull-request (since master always stays as "-dev")
+ - [ ] count the PRs since last release (that this version is tracking, in the cases of multiple branching), like `git log --pretty=oneline --no-merges --decorate $priorTag..$versionBumpCommit | grep \(pr\/ | wc -l`
+ - [ ] get the date for a week from now, like `TZ=UTC date --date='next week'`
+ - [ ] OPTIONAL find a cute animal gif to attach to the email, and subsequently the release description
+ - [ ] subject line like `[runtime-spec VOTE] tag $versionBumpCommit as $version (closes $dateWeekFromNowUTC)`
+ - [ ] email body like
+
+```text
+Hey everyone,
+
+There have been $numPRs PRs merged since $priorTag release (https://github.com/opencontainers/image-spec/compare/$priorTag...$versionBumpCommit).
+
+$linkToPullRequest
+
+Please respond LGTM or REJECT (with reasoning).
+
+$sig
+```
+
+- [ ] edit/update the pull-request to link to the VOTE thread, from <https://groups.google.com/a/opencontainers.org/forum/#!forum/dev>
+- [ ] a week later, if the vote passes, merge the PR
+ - [ ] `git tag -s $version $versionBumpCommit`
+ - [ ] `git push --tags`
+- [ ] produce release documents
+ - [ ] git checkout the release tag, like `git checkout $version`
+ - [ ] `make docs`
+ - [ ] rename the output PDF and HTML file to include version, like `mv output/oci-runtime-spec.pdf output/oci-runtime-spec-$version.pdf``
+ - [ ] attach these docs to the release on <https://github.com/opencontainers/runtime-spec/releases>
+ - [ ] link to the the VOTE thread and include the passing vote count
+ - [ ] link to the pull request that merged the release
diff --git a/annotations.md b/annotations.md
new file mode 100644
index 0000000..396da2f
--- /dev/null
+++ b/annotations.md
@@ -0,0 +1,83 @@
+# Annotations
+
+Several components of the specification, like [Image Manifests](manifest.md) and [Descriptors](descriptor.md), feature an optional annotations property, whose format is common and defined in this section.
+
+This property contains arbitrary metadata.
+
+## Rules
+
+- Annotations MUST be a key-value map where both the key and value MUST be strings.
+- While the value MUST be present, it MAY be an empty string.
+- Keys MUST be unique within this map, and best practice is to namespace the keys.
+- Keys SHOULD be named using a reverse domain notation - e.g. `com.example.myKey`.
+- The prefix `org.opencontainers` is reserved for keys defined in Open Container Initiative (OCI) specifications and MUST NOT be used by other specifications and extensions.
+- Keys using the `org.opencontainers.image` namespace are reserved for use in the OCI Image Specification and MUST NOT be used by other specifications and extensions, including other OCI specifications.
+- If there are no annotations then this property MUST either be absent or be an empty map.
+- Consumers MUST NOT generate an error if they encounter an unknown annotation key.
+
+## Pre-Defined Annotation Keys
+
+This specification defines the following annotation keys, intended for but not limited to [image index](image-index.md), image [manifest](manifest.md), and [descriptor](descriptor.md) authors.
+
+- **org.opencontainers.image.created** date and time on which the image was built, conforming to [RFC 3339][rfc3339].
+- **org.opencontainers.image.authors** contact details of the people or organization responsible for the image (freeform string)
+- **org.opencontainers.image.url** URL to find more information on the image (string)
+- **org.opencontainers.image.documentation** URL to get documentation on the image (string)
+- **org.opencontainers.image.source** URL to get source code for building the image (string)
+- **org.opencontainers.image.version** version of the packaged software
+ - The version MAY match a label or tag in the source code repository
+ - version MAY be [Semantic versioning-compatible](https://semver.org/)
+- **org.opencontainers.image.revision** Source control revision identifier for the packaged software.
+- **org.opencontainers.image.vendor** Name of the distributing entity, organization or individual.
+- **org.opencontainers.image.licenses** License(s) under which contained software is distributed as an [SPDX License Expression][spdx-license-expression].
+- **org.opencontainers.image.ref.name** Name of the reference for a target (string).
+ - SHOULD only be considered valid when on descriptors on `index.json` within [image layout](image-layout.md).
+ - Character set of the value SHOULD conform to alphanum of `A-Za-z0-9` and separator set of `-._:@/+`
+ - The reference must match the following [grammar](considerations.md#ebnf):
+
+ ```ebnf
+ ref ::= component ("/" component)*
+ component ::= alphanum (separator alphanum)*
+ alphanum ::= [A-Za-z0-9]+
+ separator ::= [-._:@+] | "--"
+ ```
+
+- **org.opencontainers.image.title** Human-readable title of the image (string)
+- **org.opencontainers.image.description** Human-readable description of the software packaged in the image (string)
+- **org.opencontainers.image.base.digest** [Digest](descriptor.md#digests) of the image this image is based on (string)
+ - This SHOULD be the immediate image sharing zero-indexed layers with the image, such as from a Dockerfile `FROM` statement.
+ - This SHOULD NOT reference any other images used to generate the contents of the image (e.g., multi-stage Dockerfile builds).
+- **org.opencontainers.image.base.name** Image reference of the image this image is based on (string)
+ - This SHOULD be image references in the format defined by [distribution/distribution][distribution-reference].
+ - This SHOULD be a fully qualified reference name, without any assumed default registry. (e.g., `registry.example.com/my-org/my-image:tag` instead of `my-org/my-image:tag`).
+ - This SHOULD be the immediate image sharing zero-indexed layers with the image, such as from a Dockerfile `FROM` statement.
+ - This SHOULD NOT reference any other images used to generate the contents of the image (e.g., multi-stage Dockerfile builds).
+ - If the `image.base.name` annotation is specified, the `image.base.digest` annotation SHOULD be the digest of the manifest referenced by the `image.ref.name` annotation.
+
+## Back-compatibility with Label Schema
+
+[Label Schema][label-schema] defined a number of conventional labels for container images, and these are now superceded by annotations with keys starting **org.opencontainers.image**.
+
+While users are encouraged to use the **org.opencontainers.image** keys, tools MAY choose to support compatible annotations using the **org.label-schema** prefix as follows.
+
+| `org.opencontainers.image` prefix | `org.label-schema` prefix | Compatibility notes |
+|---------------------------|-------------------------|---------------------|
+| `created` | `build-date` | Compatible |
+| `url` | `url` | Compatible |
+| `source` | `vcs-url` | Compatible |
+| `version` | `version` | Compatible |
+| `revision` | `vcs-ref` | Compatible |
+| `vendor` | `vendor` | Compatible |
+| `title` | `name` | Compatible |
+| `description` | `description` | Compatible |
+| `documentation` | `usage` | Value is compatible if the documentation is located by a URL |
+| `authors` | | No equivalent in Label Schema |
+| `licenses` | | No equivalent in Label Schema |
+| `ref.name` | | No equivalent in Label Schema |
+| | `schema-version`| No equivalent in the OCI Image Spec |
+| | `docker.*`, `rkt.*` | No equivalent in the OCI Image Spec |
+
+[distribution-reference]: https://github.com/distribution/distribution/blob/d0deff9cd6c2b8c82c6f3d1c713af51df099d07b/reference/reference.go
+[label-schema]: https://github.com/label-schema/label-schema.org/blob/gh-pages/rc1.md
+[rfc3339]: https://tools.ietf.org/html/rfc3339#section-5.6
+[spdx-license-expression]: https://spdx.org/spdx-specification-21-web-version#h.jxpfx0ykyb60
diff --git a/artifacts-guidance.md b/artifacts-guidance.md
new file mode 100644
index 0000000..49b8c63
--- /dev/null
+++ b/artifacts-guidance.md
@@ -0,0 +1,6 @@
+# Guidance for Artifacts Authors
+
+Content other than OCI container images MAY be packaged using the image manifest.
+When this is done, the `config.mediaType` value should not be a known OCI image config [media type](media-types.md).
+Historically, due to registry limitations, some tools have created non-OCI conformant artifacts using the `application/vnd.oci.image.config.v1+json` value for `config.mediaType` and values specific to the artifact in `layer[*].mediaType`.
+Implementation details and examples are provided in the [image manifest specification](manifest.md#guidelines-for-artifact-usage).
diff --git a/config.md b/config.md
new file mode 100644
index 0000000..23c109b
--- /dev/null
+++ b/config.md
@@ -0,0 +1,320 @@
+# OCI Image Configuration
+
+An OCI _Image_ is an ordered collection of root filesystem changes and the corresponding execution parameters for use within a container runtime.
+This specification outlines the JSON format describing images for use with a container runtime and execution tool and its relationship to filesystem changesets, described in [Layers](layer.md).
+
+This section defines the `application/vnd.oci.image.config.v1+json` [media type](media-types.md).
+
+## Terminology
+
+This specification uses the following terms:
+
+### [Layer](layer.md)
+
+- Image filesystems are composed of _layers_.
+- Each layer represents a set of filesystem changes in a tar-based [layer format](layer.md), recording files to be added, changed, or deleted relative to its parent layer.
+- Layers do not have configuration metadata such as environment variables or default arguments - these are properties of the image as a whole rather than any particular layer.
+- Using a layer-based or union filesystem such as AUFS, or by computing the diff from filesystem snapshots, the filesystem changeset can be used to present a series of image layers as if they were one cohesive filesystem.
+
+### Image JSON
+
+- Each image has an associated JSON structure which describes some basic information about the image such as date created, author, as well as execution/runtime configuration like its entrypoint, default arguments, networking, and volumes.
+- The JSON structure also references a cryptographic hash of each layer used by the image, and provides history information for those layers.
+- This JSON is considered to be immutable, because changing it would change the computed [ImageID](#imageid).
+- Changing it means creating a new derived image, instead of changing the existing image.
+
+### Layer DiffID
+
+A layer DiffID is the digest over the layer's uncompressed tar archive and serialized in the descriptor digest format, e.g., `sha256:a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9`.
+Layers SHOULD be packed and unpacked reproducibly to avoid changing the layer DiffID, for example by using [tar-split][] to save the tar headers.
+
+NOTE: Do not confuse DiffIDs with [layer digests](manifest.md#image-manifest-property-descriptions), often referenced in the manifest, which are digests over compressed or uncompressed content.
+
+### Layer ChainID
+
+For convenience, it is sometimes useful to refer to a stack of layers with a single identifier.
+While a layer's `DiffID` identifies a single changeset, the `ChainID` identifies the subsequent application of those changesets.
+This ensures that we have handles referring to both the layer itself, as well as the result of the application of a series of changesets.
+Use in combination with `rootfs.diff_ids` while applying layers to a root filesystem to uniquely and safely identify the result.
+
+#### Definition
+
+The `ChainID` of an applied set of layers is defined with the following recursion:
+
+```text
+ChainID(L₀) = DiffID(L₀)
+ChainID(L₀|...|Lₙ₋₁|Lₙ) = Digest(ChainID(L₀|...|Lₙ₋₁) + " " + DiffID(Lₙ))
+```
+
+For this, we define the binary `|` operation to be the result of applying the right operand to the left operand.
+For example, given base layer `A` and a changeset `B`, we refer to the result of applying `B` to `A` as `A|B`.
+
+Above, we define the `ChainID` for a single layer (`L₀`) as equivalent to the `DiffID` for that layer.
+Otherwise, the `ChainID` for a set of applied layers (`L₀|...|Lₙ₋₁|Lₙ`) is defined as the recursion `Digest(ChainID(L₀|...|Lₙ₋₁) + " " + DiffID(Lₙ))`.
+
+#### Explanation
+
+Let's say we have layers A, B, C, ordered from bottom to top, where A is the base and C is the top.
+Defining `|` as a binary application operator, the root filesystem may be `A|B|C`.
+While it is implied that `C` is only useful when applied to `A|B`, the identifier `C` is insufficient to identify this result, as we'd have the equality `C = A|B|C`, which isn't true.
+
+The main issue is when we have two definitions of `C`, `C = C` and `C = A|B|C`.
+If this is true (with some handwaving), `C = x|C` where `x = any application`.
+This means that if an attacker can define `x`, relying on `C` provides no guarantee that the layers were applied in any order.
+
+The `ChainID` addresses this problem by being defined as a compound hash.
+**We differentiate the changeset `C`, from the order-dependent application `A|B|C` by saying that the resulting rootfs is identified by ChainID(A|B|C), which can be calculated by `ImageConfig.rootfs`.**
+
+Let's expand the definition of `ChainID(A|B|C)` to explore its internal structure:
+
+```text
+ChainID(A) = DiffID(A)
+ChainID(A|B) = Digest(ChainID(A) + " " + DiffID(B))
+ChainID(A|B|C) = Digest(ChainID(A|B) + " " + DiffID(C))
+```
+
+We can replace each definition and reduce to a single equality:
+
+```text
+ChainID(A|B|C) = Digest(Digest(DiffID(A) + " " + DiffID(B)) + " " + DiffID(C))
+```
+
+Hopefully, the above is illustrative of the _actual_ contents of the `ChainID`.
+Most importantly, we can easily see that `ChainID(C) != ChainID(A|B|C)`, otherwise, `ChainID(C) = DiffID(C)`, which is the base case, could not be true.
+
+### ImageID
+
+Each image's ID is given by the SHA256 hash of its [configuration JSON](#image-json).
+It is represented as a hexadecimal encoding of 256 bits, e.g., `sha256:a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9`.
+Since the [configuration JSON](#image-json) that gets hashed references hashes of each layer in the image, this formulation of the ImageID makes images content-addressable.
+
+## Properties
+
+Note: Any OPTIONAL field MAY also be set to null, which is equivalent to being absent.
+
+- **created** _string_, OPTIONAL
+
+ An combined date and time at which the image was created, formatted as defined by [RFC 3339, section 5.6][rfc3339-s5.6].
+
+- **author** _string_, OPTIONAL
+
+ Gives the name and/or email address of the person or entity which created and is responsible for maintaining the image.
+
+- **architecture** _string_, REQUIRED
+
+ The CPU architecture which the binaries in this image are built to run on.
+ Configurations SHOULD use, and implementations SHOULD understand, values listed in the Go Language document for [`GOARCH`][go-environment].
+
+- **os** _string_, REQUIRED
+
+ The name of the operating system which the image is built to run on.
+ Configurations SHOULD use, and implementations SHOULD understand, values listed in the Go Language document for [`GOOS`][go-environment].
+
+- **os.version** _string_, OPTIONAL
+
+ This OPTIONAL property specifies the version of the operating system targeted by the referenced blob.
+ Implementations MAY refuse to use manifests where `os.version` is not known to work with the host OS version.
+ Valid values are implementation-defined. e.g. `10.0.14393.1066` on `windows`.
+
+- **os.features** _array of strings_, OPTIONAL
+
+ This OPTIONAL property specifies an array of strings, each specifying a mandatory OS feature.
+ When `os` is `windows`, image indexes SHOULD use, and implementations SHOULD understand the following values:
+
+ - `win32k`: image requires `win32k.sys` on the host (Note: `win32k.sys` is missing on Nano Server)
+
+- **variant** _string_, OPTIONAL
+
+ The variant of the specified CPU architecture.
+ Configurations SHOULD use, and implementations SHOULD understand, `variant` values listed in the [Platform Variants](image-index.md#platform-variants) table.
+
+- **config** _object_, OPTIONAL
+
+ The execution parameters which SHOULD be used as a base when running a container using the image.
+ This field can be `null`, in which case any execution parameters should be specified at creation of the container.
+
+ - **User** _string_, OPTIONAL
+
+ The username or UID which is a platform-specific structure that allows specific control over which user the process run as.
+ This acts as a default value to use when the value is not specified when creating a container.
+ For Linux based systems, all of the following are valid: `user`, `uid`, `user:group`, `uid:gid`, `uid:group`, `user:gid`.
+ If `group`/`gid` is not specified, the default group and supplementary groups of the given `user`/`uid` in `/etc/passwd` and `/etc/group` from the container are applied.
+ If `group`/`gid` is specified, supplementary groups from the container are ignored.
+
+ - **ExposedPorts** _object_, OPTIONAL
+
+ A set of ports to expose from a container running this image.
+ Its keys can be in the format of:
+`port/tcp`, `port/udp`, `port` with the default protocol being `tcp` if not specified.
+ These values act as defaults and are merged with any specified when creating a container.
+ **NOTE:** This JSON structure value is unusual because it is a direct JSON serialization of the Go type `map[string]struct{}` and is represented in JSON as an object mapping its keys to an empty object.
+
+ - **Env** _array of strings_, OPTIONAL
+
+ Entries are in the format of `VARNAME=VARVALUE`.
+ These values act as defaults and are merged with any specified when creating a container.
+
+ - **Entrypoint** _array of strings_, OPTIONAL
+
+ A list of arguments to use as the command to execute when the container starts.
+ These values act as defaults and may be replaced by an entrypoint specified when creating a container.
+
+ - **Cmd** _array of strings_, OPTIONAL
+
+ Default arguments to the entrypoint of the container.
+ These values act as defaults and may be replaced by any specified when creating a container.
+ If an `Entrypoint` value is not specified, then the first entry of the `Cmd` array SHOULD be interpreted as the executable to run.
+
+ - **Volumes** _object_, OPTIONAL
+
+ A set of directories describing where the process is likely to write data specific to a container instance.
+ **NOTE:** This JSON structure value is unusual because it is a direct JSON serialization of the Go type `map[string]struct{}` and is represented in JSON as an object mapping its keys to an empty object.
+
+ - **WorkingDir** _string_, OPTIONAL
+
+ Sets the current working directory of the entrypoint process in the container.
+ This value acts as a default and may be replaced by a working directory specified when creating a container.
+
+ - **Labels** _object_, OPTIONAL
+
+ The field contains arbitrary metadata for the container.
+ This property MUST use the [annotation rules](annotations.md#rules).
+
+ - **StopSignal** _string_, OPTIONAL
+
+ The field contains the system call signal that will be sent to the container to exit. The signal can be a signal name in the format `SIGNAME`, for instance `SIGKILL` or `SIGRTMIN+3`.
+
+ - **ArgsEscaped** _boolean_, OPTIONAL
+
+ `[Deprecated]` - This field is present only for legacy compatibility with Docker and should not be used by new image builders.
+ It is used by Docker for Windows images to indicate that the `Entrypoint` or `Cmd` or both, contains only a single element array, that is a pre-escaped, and combined into a single string `CommandLine`.
+ If `true` the value in `Entrypoint` or `Cmd` should be used as-is to avoid double escaping.
+ Note, the exact behavior of `ArgsEscaped` is complex and subject to implementation details in Moby project.
+
+ - **Memory** _integer_, OPTIONAL
+
+ This property is _reserved_ for use, to [maintain compatibility](media-types.md#compatibility-matrix).
+
+ - **MemorySwap** _integer_, OPTIONAL
+
+ This property is _reserved_ for use, to [maintain compatibility](media-types.md#compatibility-matrix).
+
+ - **CpuShares** _integer_, OPTIONAL
+
+ This property is _reserved_ for use, to [maintain compatibility](media-types.md#compatibility-matrix).
+
+ - **Healthcheck** _object_, OPTIONAL
+
+ This property is _reserved_ for use, to [maintain compatibility](media-types.md#compatibility-matrix).
+
+- **rootfs** _object_, REQUIRED
+
+ The rootfs key references the layer content addresses used by the image.
+ This makes the image config hash depend on the filesystem hash.
+
+ - **type** _string_, REQUIRED
+
+ MUST be set to `layers`.
+ Implementations MUST generate an error if they encounter a unknown value while verifying or unpacking an image.
+
+ - **diff_ids** _array of strings_, REQUIRED
+
+ An array of layer content hashes (`DiffIDs`), in order from first to last.
+
+- **history** _array of objects_, OPTIONAL
+
+ Describes the history of each layer.
+ The array is ordered from first to last.
+ The object has the following fields:
+
+ - **created** _string_, OPTIONAL
+
+ A combined date and time at which the layer was created, formatted as defined by [RFC 3339, section 5.6][rfc3339-s5.6].
+
+ - **author** _string_, OPTIONAL
+
+ The author of the build point.
+
+ - **created_by** _string_, OPTIONAL
+
+ The command which created the layer.
+
+ - **comment** _string_, OPTIONAL
+
+ A custom message set when creating the layer.
+
+ - **empty_layer** _boolean_, OPTIONAL
+
+ This field is used to mark if the history item created a filesystem diff.
+ It is set to true if this history item doesn't correspond to an actual layer in the rootfs section (for example, Dockerfile's [ENV](https://docs.docker.com/engine/reference/builder/#/env) command results in no change to the filesystem).
+
+Any extra fields in the Image JSON struct are considered implementation specific and MUST NOT generate an error by any implementations which are unable to interpret them.
+
+Whitespace is OPTIONAL and implementations MAY have compact JSON with no whitespace.
+
+## Example
+
+Here is an example image configuration JSON document:
+
+```json,title=Image%20JSON&mediatype=application/vnd.oci.image.config.v1%2Bjson
+{
+ "created": "2015-10-31T22:22:56.015925234Z",
+ "author": "Alyssa P. Hacker <alyspdev@example.com>",
+ "architecture": "amd64",
+ "os": "linux",
+ "config": {
+ "User": "alice",
+ "ExposedPorts": {
+ "8080/tcp": {}
+ },
+ "Env": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+ "FOO=oci_is_a",
+ "BAR=well_written_spec"
+ ],
+ "Entrypoint": [
+ "/bin/my-app-binary"
+ ],
+ "Cmd": [
+ "--foreground",
+ "--config",
+ "/etc/my-app.d/default.cfg"
+ ],
+ "Volumes": {
+ "/var/job-result-data": {},
+ "/var/log/my-app-logs": {}
+ },
+ "WorkingDir": "/home/alice",
+ "Labels": {
+ "com.example.project.git.url": "https://example.com/project.git",
+ "com.example.project.git.commit": "45a939b2999782a3f005621a8d0f29aa387e1d6b"
+ }
+ },
+ "rootfs": {
+ "diff_ids": [
+ "sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
+ "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
+ ],
+ "type": "layers"
+ },
+ "history": [
+ {
+ "created": "2015-10-31T22:22:54.690851953Z",
+ "created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
+ },
+ {
+ "created": "2015-10-31T22:22:55.613815829Z",
+ "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]",
+ "empty_layer": true
+ },
+ {
+ "created": "2015-10-31T22:22:56.329850019Z",
+ "created_by": "/bin/sh -c apk add curl"
+ }
+ ]
+}
+```
+
+[rfc3339-s5.6]: https://tools.ietf.org/html/rfc3339#section-5.6
+[go-environment]: https://golang.org/doc/install/source#environment
+[tar-split]: https://github.com/vbatts/tar-split
diff --git a/considerations.md b/considerations.md
new file mode 100644
index 0000000..11d2a37
--- /dev/null
+++ b/considerations.md
@@ -0,0 +1,147 @@
+# Considerations
+
+## Extensibility
+
+Implementations storing or copying content MUST NOT modify or alter the content in a way that would change the digest of the content. Examples of these implementations include:
+
+- A [registry implementing the distribution specification][distribution-spec], including local registries, caching proxies
+- An application which copies content to disk or between registries
+
+Implementations processing content SHOULD NOT generate an error if they encounter an unknown property in a known media type. Examples of these implementations include:
+
+- A [runtime implementing the runtime specification][runtime-spec]
+- An implementation using OCI to retrieve and utilize artifacts, e.g.: a WASM runtime
+
+## Canonicalization
+
+- OCI Images are [content-addressable](https://en.wikipedia.org/wiki/Content-addressable_storage). See [descriptors](descriptor.md) for more.
+- One benefit of content-addressable storage is easy deduplication.
+- Many images might depend on a particular [layer](layer.md), but there will only be one blob in the [store](image-layout.md).
+- With a different serialization, that same semantic layer would have a different hash, and if both versions of the layer are referenced there will be two blobs with the same semantic content.
+- To allow efficient storage, implementations serializing content for blobs SHOULD use a canonical serialization.
+- This increases the chance that different implementations can push the same semantic content to the store without creating redundant blobs.
+
+### JSON
+
+[JSON][JSON] content SHOULD be serialized as [canonical JSON][canonical-json].
+Of the [OCI Image Format Specification media types](media-types.md), all the types ending in `+json` contain JSON content.
+Implementations:
+
+- [Go][Go]: [github.com/docker/go][docker-go], which claims to implement [canonical JSON][canonical-json] except for Unicode normalization.
+
+## EBNF
+
+For field formats described in this specification, we use a limited subset of [Extended Backus-Naur Form][ebnf], similar to that used by the [XML specification][xmlebnf].
+Grammars present in the OCI specification are regular and can be converted to a single regular expressions.
+However, regular expressions are avoided to limit ambiguity between regular expression syntax.
+By defining a subset of EBNF used here, the possibility of variation, misunderstanding or ambiguities from linking to a larger specification can be avoided.
+
+Grammars are made up of rules in the following form:
+
+```ebnf
+symbol ::= expression
+```
+
+We can say we have the production identified by symbol if the input is matched by the expression.
+Whitespace is completely ignored in rule definitions.
+
+### Expressions
+
+The simplest expression is the literal, surrounded by quotes:
+
+```ebnf
+literal ::= "matchthis"
+```
+
+The above expression defines a symbol, "literal", that matches the exact input of "matchthis".
+Character classes are delineated by brackets (`[]`), describing either a set, range or multiple range of characters:
+
+```ebnf
+set := [abc]
+range := [A-Z]
+```
+
+The above symbol "set" would match one character of either "a", "b" or "c".
+The symbol "range" would match any character, "A" to "Z", inclusive.
+Currently, only matching for 7-bit ascii literals and character classes is defined, as that is all that is required by this specification.
+Multiple character ranges and explicit characters can be specified in a single character classes, as follows:
+
+```ebnf
+multipleranges := [a-zA-Z=-]
+```
+
+The above matches the characters in the range `A` to `Z`, `a` to `z` and the individual characters `-` and `=`.
+
+Expressions can be made up of one or more expressions, such that one must be followed by the other.
+This is known as an implicit concatenation operator.
+For example, to satisfy the following rule, both `A` and `B` must be matched to satisfy the rule:
+
+```ebnf
+symbol ::= A B
+```
+
+Each expression must be matched once and only once, `A` followed by `B`.
+To support the description of repetition and optional match criteria, the postfix operators `*` and `+` are defined.
+`*` indicates that the preceding expression can be matched zero or more times.
+`+` indicates that the preceding expression must be matched one or more times.
+These appear in the following form:
+
+```ebnf
+zeroormore ::= expression*
+oneormore ::= expression+
+```
+
+Parentheses are used to group expressions into a larger expression:
+
+```ebnf
+group ::= (A B)
+```
+
+Like simpler expressions above, operators can be applied to groups, as well.
+To allow for alternates, we also define the infix operator `|`.
+
+```ebnf
+oneof ::= A | B
+```
+
+The above indicates that the expression should match one of the expressions, `A` or `B`.
+
+### Precedence
+
+The operator precedence is in the following order:
+
+- Terminals (literals and character classes)
+- Grouping `()`
+- Unary operators `+*`
+- Concatenation
+- Alternates `|`
+
+The precedence can be better described using grouping to show equivalents.
+Concatenation has higher precedence than alternates, such `A B | C D` is equivalent to `(A B) | (C D)`.
+Unary operators have higher precedence than alternates and concatenation, such that `A+ | B+` is equivalent to `(A+) | (B+)`.
+
+### Examples
+
+The following combines the previous definitions to match a simple, relative path name, describing the individual components:
+
+```ebnf
+path ::= component ("/" component)*
+component ::= [a-z]+
+```
+
+The production "component" is one or more lowercase letters.
+A "path" is then at least one component, possibly followed by zero or more slash-component pairs.
+The above can be converted into the following regular expression:
+
+```regex
+[a-z]+(?:/[a-z]+)*
+```
+
+[canonical-json]: https://wiki.laptop.org/go/Canonical_JSON
+[distribution-spec]: https://github.com/opencontainers/distribution-spec/blob/main/spec.md
+[docker-go]: https://github.com/docker/go/
+[ebnf]: https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form
+[Go]: https://golang.org/
+[JSON]: https://json.org/
+[runtime-spec]: https://github.com/opencontainers/runtime-spec/blob/main/spec.md
+[xmlebnf]: https://www.w3.org/TR/REC-xml/#sec-notation
diff --git a/conversion.md b/conversion.md
new file mode 100644
index 0000000..81420fa
--- /dev/null
+++ b/conversion.md
@@ -0,0 +1,130 @@
+# Conversion to OCI Runtime Configuration
+
+When extracting an OCI Image into an [OCI Runtime bundle][oci-runtime-bundle], two orthogonal components of the extraction are relevant:
+
+1. Extraction of the root filesystem from the set of [filesystem layers](layer.md).
+2. Conversion of the [image configuration blob](config.md) to an [OCI Runtime configuration blob][oci-runtime-config].
+
+This section defines how to convert an `application/vnd.oci.image.config.v1+json` blob to an [OCI runtime configuration blob][oci-runtime-config] (the latter component of extraction).
+The former component of extraction is defined [elsewhere](layer.md) and is orthogonal to configuration of a runtime bundle.
+The values of runtime configuration properties not specified by this document are implementation-defined.
+
+A converter MUST rely on the OCI image configuration to build the OCI runtime configuration as described by this document; this will create the "default generated runtime configuration".
+
+The "default generated runtime configuration" MAY be overridden or combined with externally provided inputs from the caller.
+In addition, a converter MAY have its own implementation-defined defaults and extensions which MAY be combined with the "default generated runtime configuration".
+The restrictions in this document refer only to combining implementation-defined defaults with the "default generated runtime configuration".
+Externally provided inputs are considered to be a modification of the `application/vnd.oci.image.config.v1+json` used as a source, and such modifications have no restrictions.
+
+For example, externally provided inputs MAY cause an environment variable to be added, removed or changed.
+However an implementation-defined default SHOULD NOT result in an environment variable being removed or changed.
+
+[oci-runtime-bundle]: https://github.com/opencontainers/runtime-spec/blob/v1.0.0/bundle.md
+[oci-runtime-config]: https://github.com/opencontainers/runtime-spec/blob/v1.0.0/config.md
+
+## Verbatim Fields
+
+Certain image configuration fields have an identical counterpart in the runtime configuration.
+Some of these are purely annotation-based fields, and have been extracted into a [separate subsection](#annotation-fields).
+A compliant configuration converter MUST extract the following fields verbatim to the corresponding field in the generated runtime configuration:
+
+| Image Field | Runtime Field | Notes |
+| ------------------- | --------------- | ----- |
+| `Config.WorkingDir` | `process.cwd` | |
+| `Config.Env` | `process.env` | 1 |
+| `Config.Entrypoint` | `process.args` | 2 |
+| `Config.Cmd` | `process.args` | 2 |
+
+1. The converter MAY add additional entries to `process.env` but it SHOULD NOT add entries that have variable names present in `Config.Env`.
+2. If both `Config.Entrypoint` and `Config.Cmd` are specified, the converter MUST append the value of `Config.Cmd` to the value of `Config.Entrypoint` and set `process.args` to that combined value.
+
+### Annotation Fields
+
+These fields all affect the `annotations` of the runtime configuration, and are thus subject to [precedence](#annotations).
+
+| Image Field | Runtime Field | Notes |
+| ------------------- | --------------- | ----- |
+| `os` | `annotations` | 1,2 |
+| `architecture` | `annotations` | 1,3 |
+| `variant` | `annotations` | 1,4 |
+| `os.version` | `annotations` | 1,5 |
+| `os.features` | `annotations` | 1,6 |
+| `author` | `annotations` | 1,7 |
+| `created` | `annotations` | 1,8 |
+| `Config.Labels` | `annotations` | |
+| `Config.StopSignal` | `annotations` | 1,9 |
+
+1. If a user has explicitly specified this annotation with `Config.Labels`, then the value specified in this field takes lower [precedence](#annotations) and the converter MUST instead use the value from `Config.Labels`.
+2. The value of this field MUST be set as the value of `org.opencontainers.image.os` in `annotations`.
+3. The value of this field MUST be set as the value of `org.opencontainers.image.architecture` in `annotations`.
+4. The value of this field MUST be set as the value of `org.opencontainers.image.variant` in `annotations`.
+5. The value of this field MUST be set as the value of `org.opencontainers.image.os.version` in `annotations`.
+6. The value of this field MUST be set as the value of `org.opencontainers.image.os.features` in `annotations`.
+7. The value of this field MUST be set as the value of `org.opencontainers.image.author` in `annotations`.
+8. The value of this field MUST be set as the value of `org.opencontainers.image.created` in `annotations`.
+9. The value of this field MUST be set as the value of `org.opencontainers.image.stopSignal` in `annotations`.
+
+## Parsed Fields
+
+Certain image configuration fields have a counterpart that must first be translated.
+A compliant configuration converter SHOULD parse all of these fields and set the corresponding fields in the generated runtime configuration:
+
+| Image Field | Runtime Field |
+| ------------------- | --------------- |
+| `Config.User` | `process.user.*` |
+
+The method of parsing the above image fields are described in the following sections.
+
+### `Config.User`
+
+If the values of [`user` or `group`](config.md#properties) in `Config.User` are numeric (`uid` or `gid`) then the values MUST be copied verbatim to `process.user.uid` and `process.user.gid` respectively.
+If the values of [`user` or `group`](config.md#properties) in `Config.User` are not numeric (`user` or `group`) then a converter SHOULD resolve the user information using a method appropriate for the container's context.
+For Unix-like systems, this MAY involve resolution through NSS or parsing `/etc/passwd` from the extracted container's root filesystem to determine the values of `process.user.uid` and `process.user.gid`.
+
+In addition, a converter SHOULD set the value of `process.user.additionalGids` to a value corresponding to the user in the container's context described by `Config.User`.
+For Unix-like systems, this MAY involve resolution through NSS or parsing `/etc/group` and determining the group memberships of the user specified in `process.user.uid`.
+The converter SHOULD NOT modify `process.user.additionalGids` if the value of [`user`](config.md#properties) in `Config.User` is numeric or if `Config.User` specifies a group.
+
+If `Config.User` is not defined, the converted `process.user` value is implementation-defined.
+If `Config.User` does not correspond to a user in the container's context, the converter MUST return an error.
+
+## Optional Fields
+
+Certain image configuration fields are not applicable to all conversion use cases, and thus are optional for configuration converters to implement.
+A compliant configuration converter SHOULD provide a way for users to extract these fields into the generated runtime configuration:
+
+| Image Field | Runtime Field | Notes |
+| --------------------- | ------------------ | ----- |
+| `Config.ExposedPorts` | `annotations` | 1 |
+| `Config.Volumes` | `mounts` | 2 |
+
+1. The runtime configuration does not have a corresponding field for this image field.
+ However, converters SHOULD set the [`org.opencontainers.image.exposedPorts` annotation](#configexposedports).
+2. Implementations SHOULD provide mounts for these locations such that application data is not written to the container's root filesystem.
+ If a converter implements conversion for this field using mountpoints, it SHOULD set the `destination` of the mountpoint to the value specified in `Config.Volumes`.
+ An implementation MAY seed the contents of the mount with data in the image at the same location.
+ If a _new_ image is created from a container based on the image described by this configuration, data in these paths SHOULD NOT be included in the _new_ image.
+ The other `mounts` fields are platform and context dependent, and thus are implementation-defined.
+ Note that the implementation of `Config.Volumes` need not use mountpoints, as it is effectively a mask of the filesystem.
+
+### `Config.ExposedPorts`
+
+The OCI runtime configuration does not provide a way of expressing the concept of "container exposed ports".
+However, converters SHOULD set the **org.opencontainers.image.exposedPorts** annotation, unless doing so will [cause a conflict](#annotations).
+
+**org.opencontainers.image.exposedPorts** is the list of values that correspond to the [keys defined for `Config.ExposedPorts`](config.md) (string, comma-separated values).
+
+## Annotations
+
+There are three ways of annotating an OCI image in this specification:
+
+1. `Config.Labels` in the [configuration](config.md) of the image.
+2. `annotations` in the [manifest](manifest.md) of the image.
+3. `annotations` in the [image index](image-index.md) of the image.
+
+In addition, there are also implicit annotations that are defined by this section which are determined from the values of the image configuration.
+A converter SHOULD NOT attempt to extract annotations from [manifests](manifest.md) or [image indices](image-index.md).
+If there is a conflict (same key but different value) between an implicit annotation (or annotation in [manifests](manifest.md) or [image indices](image-index.md)) and an explicitly specified annotation in `Config.Labels`, the value specified in `Config.Labels` MUST take precedence.
+
+A converter MAY add annotations which have keys not specified in the image.
+A converter MUST NOT modify the values of annotations specified in the image.
diff --git a/descriptor.md b/descriptor.md
new file mode 100644
index 0000000..004ad0e
--- /dev/null
+++ b/descriptor.md
@@ -0,0 +1,221 @@
+# OCI Content Descriptors
+
+- An OCI image consists of several different components, arranged in a [Merkle Directed Acyclic Graph (DAG)](https://en.wikipedia.org/wiki/Merkle_tree).
+- References between components in the graph are expressed through _Content Descriptors_.
+- A Content Descriptor (or simply _Descriptor_) describes the disposition of the targeted content.
+- A Content Descriptor includes the type of the content, a content identifier (_digest_), and the byte-size of the raw content.
+ Optionally, it includes the type of artifact it is describing.
+- Descriptors SHOULD be embedded in other formats to securely reference external content.
+- Other formats SHOULD use descriptors to securely reference external content.
+
+This section defines the `application/vnd.oci.descriptor.v1+json` [media type](media-types.md).
+
+## Properties
+
+A descriptor consists of a set of properties encapsulated in key-value fields.
+
+The following fields contain the primary properties that constitute a Descriptor:
+
+- **`mediaType`** *string*
+
+ This REQUIRED property contains the media type of the referenced content.
+ Values MUST comply with [RFC 6838][rfc6838], including the [naming requirements in its section 4.2][rfc6838-s4.2].
+
+ The OCI image specification defines [several of its own MIME types](media-types.md) for resources defined in the specification.
+
+- **`digest`** *string*
+
+ This REQUIRED property is the _digest_ of the targeted content, conforming to the requirements outlined in [Digests](#digests).
+ Retrieved content SHOULD be verified against this digest when consumed via untrusted sources.
+
+- **`size`** *int64*
+
+ This REQUIRED property specifies the size, in bytes, of the raw content.
+ This property exists so that a client will have an expected size for the content before processing.
+ If the length of the retrieved content does not match the specified length, the content SHOULD NOT be trusted.
+
+- **`urls`** *array of strings*
+
+ This OPTIONAL property specifies a list of URIs from which this object MAY be downloaded.
+ Each entry MUST conform to [RFC 3986][rfc3986].
+ Entries SHOULD use the `http` and `https` schemes, as defined in [RFC 7230][rfc7230-s2.7].
+
+- **`annotations`** *string-string map*
+
+ This OPTIONAL property contains arbitrary metadata for this descriptor.
+ This OPTIONAL property MUST use the [annotation rules](annotations.md#rules).
+
+- **`data`** *string*
+
+ This OPTIONAL property contains an embedded representation of the referenced content.
+ Values MUST conform to the Base 64 encoding, as defined in [RFC 4648][rfc4648-s4].
+ The decoded data MUST be identical to the referenced content and SHOULD be verified against the [`digest`](#digests) and `size` fields by content consumers.
+ See [Embedded Content](#embedded-content) for when this is appropriate.
+
+- **`artifactType`** *string*
+
+ This OPTIONAL property contains the type of an artifact when the descriptor points to an artifact.
+ This is the value of the config descriptor `mediaType` when the descriptor references an [image manifest](manifest.md).
+ If defined, the value MUST comply with [RFC 6838][rfc6838], including the [naming requirements in its section 4.2][rfc6838-s4.2], and MAY be registered with [IANA][iana].
+
+Descriptors pointing to [`application/vnd.oci.image.manifest.v1+json`](manifest.md) SHOULD include the extended field `platform`, see [Image Index Property Descriptions](image-index.md#image-index-property-descriptions) for details.
+
+### Reserved
+
+Extended _Descriptor_ field additions proposed in other OCI specifications SHOULD first be considered for addition into this specification.
+
+## Digests
+
+The _digest_ property of a Descriptor acts as a content identifier, enabling [content addressability](https://en.wikipedia.org/wiki/Content-addressable_storage).
+It uniquely identifies content by taking a [collision-resistant hash](https://en.wikipedia.org/wiki/Cryptographic_hash_function) of the bytes.
+If the _digest_ can be communicated in a secure manner, one can verify content from an insecure source by recalculating the digest independently, ensuring the content has not been modified.
+
+The value of the `digest` property is a string consisting of an _algorithm_ portion and an _encoded_ portion.
+The _algorithm_ specifies the cryptographic hash function and encoding used for the digest; the _encoded_ portion contains the encoded result of the hash function.
+
+A digest string MUST match the following [grammar](considerations.md#ebnf):
+
+```ebnf
+digest ::= algorithm ":" encoded
+algorithm ::= algorithm-component (algorithm-separator algorithm-component)*
+algorithm-component ::= [a-z0-9]+
+algorithm-separator ::= [+._-]
+encoded ::= [a-zA-Z0-9=_-]+
+```
+
+Note that _algorithm_ MAY impose algorithm-specific restriction on the grammar of the _encoded_ portion.
+See also [Registered Algorithms](#registered-algorithms).
+
+Some example digest strings include the following:
+
+digest | algorithm | Registered |
+--------------------------------------------------------------------------|---------------------|------------|
+`sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b` | [SHA-256](#sha-256) | Yes |
+`sha512:401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b372742...` | [SHA-512](#sha-512) | Yes |
+`multihash+base58:QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8` | Multihash | No |
+`sha256+b64u:LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564` | SHA-256 with urlsafe base64 | No |
+
+Please see [Registered Algorithms](#registered-algorithms) for a list of registered algorithms.
+
+Implementations SHOULD allow digests with unrecognized algorithms to pass validation if they comply with the above grammar.
+While `sha256` will only use hex encoded digests, separators in _algorithm_ and alphanumerics in _encoded_ are included to allow for extensions.
+As an example, we can parameterize the encoding and algorithm as `multihash+base58:QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8`, which would be considered valid but unregistered by this specification.
+
+### Verification
+
+Before consuming content targeted by a descriptor from untrusted sources, the byte content SHOULD be verified against the digest string.
+Before calculating the digest, the size of the content SHOULD be verified to reduce hash collision space.
+Heavy processing before calculating a hash SHOULD be avoided.
+Implementations MAY employ [canonicalization](considerations.md#canonicalization) of the underlying content to ensure stable content identifiers.
+
+### Digest calculations
+
+A _digest_ is calculated by the following pseudo-code, where `H` is the selected hash algorithm, identified by string `<alg>`:
+
+```text
+let ID(C) = Descriptor.digest
+let C = <bytes>
+let D = '<alg>:' + Encode(H(C))
+let verified = ID(C) == D
+```
+
+Above, we define the content identifier as `ID(C)`, extracted from the `Descriptor.digest` field.
+Content `C` is a string of bytes.
+Function `H` returns the hash of `C` in bytes and is passed to function `Encode` and prefixed with the algorithm to obtain the digest.
+The result `verified` is true if `ID(C)` is equal to `D`, confirming that `C` is the content identified by `D`.
+After verification, the following is true:
+
+```text
+D == ID(C) == '<alg>:' + Encode(H(C))
+```
+
+The _digest_ is confirmed as the content identifier by independently calculating the _digest_.
+
+### Registered algorithms
+
+While the _algorithm_ component of the digest string allows the use of a variety of cryptographic algorithms, compliant implementations SHOULD use [SHA-256](#sha-256).
+
+The following algorithm identifiers are currently defined by this specification:
+
+| algorithm identifier | algorithm |
+|----------------------|---------------------|
+| `sha256` | [SHA-256](#sha-256) |
+| `sha512` | [SHA-512](#sha-512) |
+
+If a useful algorithm is not included in the above table, it SHOULD be submitted to this specification for registration.
+
+#### SHA-256
+
+[SHA-256][rfc4634-s4.1] is a collision-resistant hash function, chosen for ubiquity, reasonable size and secure characteristics.
+Implementations MUST implement SHA-256 digest verification for use in descriptors.
+
+When the _algorithm identifier_ is `sha256`, the _encoded_ portion MUST match `/[a-f0-9]{64}/`.
+Note that `[A-F]` MUST NOT be used here.
+
+#### SHA-512
+
+[SHA-512][rfc4634-s4.2] is a collision-resistant hash function which [may be more performant][sha256-vs-sha512] than [SHA-256](#sha-256) on some CPUs.
+Implementations MAY implement SHA-512 digest verification for use in descriptors.
+
+When the _algorithm identifier_ is `sha512`, the _encoded_ portion MUST match `/[a-f0-9]{128}/`.
+Note that `[A-F]` MUST NOT be used here.
+
+## Embedded Content
+
+In many contexts, such as when downloading content over a network, resolving a descriptor to its content has a measurable fixed "roundtrip" latency cost.
+For large blobs, the fixed cost is usually inconsequential, as the majority of time will be spent actually fetching the content.
+For very small blobs, the fixed cost can be quite significant.
+
+Implementations MAY choose to embed small pieces of content directly within a descriptor to avoid roundtrips.
+
+Implementations MUST NOT populate the `data` field in situations where doing so would modify existing content identifiers.
+For example, a registry MUST NOT arbitrarily populate `data` fields within uploaded manifests, as that would modify the content identifier of those manifests.
+In contrast, a client MAY populate the `data` field before uploading a manifest, because the manifest would not yet have a content identifier in the registry.
+
+Implementations SHOULD consider portability when deciding whether to embed data, as some providers are known to refuse to accept or parse manifests that exceed a certain size.
+
+## Examples
+
+The following example describes a [_Manifest_](manifest.md#image-manifest) with a content identifier of "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" and a size of 7682 bytes:
+
+```json,title=Content%20Descriptor&mediatype=application/vnd.oci.descriptor.v1%2Bjson
+{
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+}
+```
+
+In the following example, the descriptor indicates that the referenced manifest is retrievable from a particular URL:
+
+```json,title=Content%20Descriptor&mediatype=application/vnd.oci.descriptor.v1%2Bjson
+{
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
+ "urls": [
+ "https://example.com/example-manifest"
+ ]
+}
+```
+
+In the following example, the descriptor indicates the type of artifact it is referencing:
+
+```json,title=Content%20Descriptor&mediatype=application/vnd.oci.descriptor.v1%2Bjson
+{
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 123,
+ "digest": "sha256:87923725d74f4bfb94c9e86d64170f7521aad8221a5de834851470ca142da630",
+ "artifactType": "application/vnd.example.sbom.v1"
+}
+```
+
+[rfc3986]: https://tools.ietf.org/html/rfc3986
+[rfc4634-s4.1]: https://tools.ietf.org/html/rfc4634#section-4.1
+[rfc4634-s4.2]: https://tools.ietf.org/html/rfc4634#section-4.2
+[rfc4648-s4]: https://tools.ietf.org/html/rfc4648#section-4
+[rfc6838]: https://tools.ietf.org/html/rfc6838
+[rfc6838-s4.2]: https://tools.ietf.org/html/rfc6838#section-4.2
+[rfc7230-s2.7]: https://tools.ietf.org/html/rfc7230#section-2.7
+[sha256-vs-sha512]: https://groups.google.com/a/opencontainers.org/forum/#!topic/dev/hsMw7cAwrZE
+[iana]: https://www.iana.org/assignments/media-types/media-types.xhtml
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..dad0d8c
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,18 @@
+module github.com/opencontainers/image-spec
+
+go 1.18
+
+require (
+ github.com/opencontainers/go-digest v1.0.0
+ github.com/pkg/errors v0.9.1
+ github.com/russross/blackfriday v1.6.0
+ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415
+ github.com/xeipuuv/gojsonschema v1.2.0
+)
+
+require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/stretchr/testify v1.7.0 // indirect
+ github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
+ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..766d107
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,25 @@
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
+github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/identity/chainid.go b/identity/chainid.go
new file mode 100644
index 0000000..0bb2853
--- /dev/null
+++ b/identity/chainid.go
@@ -0,0 +1,67 @@
+// 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 identity provides implementations of subtle calculations pertaining
+// to image and layer identity. The primary item present here is the ChainID
+// calculation used in identifying the result of subsequent layer applications.
+//
+// Helpers are also provided here to ease transition to the
+// github.com/opencontainers/go-digest package, but that package may be used
+// directly.
+package identity
+
+import "github.com/opencontainers/go-digest"
+
+// ChainID takes a slice of digests and returns the ChainID corresponding to
+// the last entry. Typically, these are a list of layer DiffIDs, with the
+// result providing the ChainID identifying the result of sequential
+// application of the preceding layers.
+func ChainID(dgsts []digest.Digest) digest.Digest {
+ chainIDs := make([]digest.Digest, len(dgsts))
+ copy(chainIDs, dgsts)
+ ChainIDs(chainIDs)
+
+ if len(chainIDs) == 0 {
+ return ""
+ }
+ return chainIDs[len(chainIDs)-1]
+}
+
+// ChainIDs calculates the recursively applied chain id for each identifier in
+// the slice. The result is written direcly back into the slice such that the
+// ChainID for each item will be in the respective position.
+//
+// By definition of ChainID, the zeroth element will always be the same before
+// and after the call.
+//
+// As an example, given the chain of ids `[A, B, C]`, the result `[A,
+// ChainID(A|B), ChainID(A|B|C)]` will be written back to the slice.
+//
+// The input is provided as a return value for convenience.
+//
+// Typically, these are a list of layer DiffIDs, with the
+// result providing the ChainID for each the result of each layer application
+// sequentially.
+func ChainIDs(dgsts []digest.Digest) []digest.Digest {
+ if len(dgsts) < 2 {
+ return dgsts
+ }
+
+ parent := digest.FromBytes([]byte(dgsts[0] + " " + dgsts[1]))
+ next := dgsts[1:]
+ next[0] = parent
+ ChainIDs(next)
+
+ return dgsts
+}
diff --git a/identity/chainid_test.go b/identity/chainid_test.go
new file mode 100644
index 0000000..241dd91
--- /dev/null
+++ b/identity/chainid_test.go
@@ -0,0 +1,95 @@
+// 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 identity
+
+import (
+ _ "crypto/sha256" // required to install sha256 digest support
+ "reflect"
+ "testing"
+
+ "github.com/opencontainers/go-digest"
+)
+
+func TestChainID(t *testing.T) {
+ // To provide a good testing base, we define the individual links in a
+ // chain recursively, illustrating the calculations for each chain.
+ //
+ // Note that we use invalid digests for the unmodified identifiers here to
+ // make the computation more readable.
+ chainDigestAB := digest.FromString("sha256:a" + " " + "sha256:b") // chain for A|B
+ chainDigestABC := digest.FromString(chainDigestAB.String() + " " + "sha256:c") // chain for A|B|C
+
+ for _, testcase := range []struct {
+ Name string
+ Digests []digest.Digest
+ Expected []digest.Digest
+ }{
+ {
+ Name: "nil",
+ },
+ {
+ Name: "empty",
+ Digests: []digest.Digest{},
+ Expected: []digest.Digest{},
+ },
+ {
+ Name: "identity",
+ Digests: []digest.Digest{"sha256:a"},
+ Expected: []digest.Digest{"sha256:a"},
+ },
+ {
+ Name: "two",
+ Digests: []digest.Digest{"sha256:a", "sha256:b"},
+ Expected: []digest.Digest{"sha256:a", chainDigestAB},
+ },
+ {
+ Name: "three",
+ Digests: []digest.Digest{"sha256:a", "sha256:b", "sha256:c"},
+ Expected: []digest.Digest{"sha256:a", chainDigestAB, chainDigestABC},
+ },
+ } {
+ t.Run(testcase.Name, func(t *testing.T) {
+ t.Log("before", testcase.Digests)
+
+ var ids []digest.Digest
+
+ if testcase.Digests != nil {
+ ids = make([]digest.Digest, len(testcase.Digests))
+ copy(ids, testcase.Digests)
+ }
+
+ ids = ChainIDs(ids)
+ t.Log("after", ids)
+ if !reflect.DeepEqual(ids, testcase.Expected) {
+ t.Errorf("unexpected chain: %v != %v", ids, testcase.Expected)
+ }
+
+ if len(testcase.Digests) == 0 {
+ return
+ }
+
+ // Make sure parent stays stable
+ if ids[0] != testcase.Digests[0] {
+ t.Errorf("parent changed: %v != %v", ids[0], testcase.Digests[0])
+ }
+
+ // make sure that the ChainID function takes the last element
+ id := ChainID(testcase.Digests)
+ if id != ids[len(ids)-1] {
+ t.Errorf("incorrect chain id returned from ChainID: %v != %v", id, ids[len(ids)-1])
+ }
+ })
+ }
+}
diff --git a/identity/helpers.go b/identity/helpers.go
new file mode 100644
index 0000000..9d96eaa
--- /dev/null
+++ b/identity/helpers.go
@@ -0,0 +1,40 @@
+// 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 identity
+
+import (
+ _ "crypto/sha256" // side-effect to install impls, sha256
+ _ "crypto/sha512" // side-effect to install impls, sha384/sh512
+
+ "io"
+
+ digest "github.com/opencontainers/go-digest"
+)
+
+// FromReader consumes the content of rd until io.EOF, returning canonical
+// digest.
+func FromReader(rd io.Reader) (digest.Digest, error) {
+ return digest.Canonical.FromReader(rd)
+}
+
+// FromBytes digests the input and returns a Digest.
+func FromBytes(p []byte) digest.Digest {
+ return digest.Canonical.FromBytes(p)
+}
+
+// FromString digests the input and returns a Digest.
+func FromString(s string) digest.Digest {
+ return digest.Canonical.FromString(s)
+}
diff --git a/image-index.md b/image-index.md
new file mode 100644
index 0000000..c661760
--- /dev/null
+++ b/image-index.md
@@ -0,0 +1,187 @@
+# OCI Image Index Specification
+
+The image index is a higher-level manifest which points to specific [image manifests](manifest.md), ideal for one or more platforms.
+While the use of an image index is OPTIONAL for image providers, image consumers SHOULD be prepared to process them.
+
+This section defines the `application/vnd.oci.image.index.v1+json` [media type](media-types.md).
+
+For the media type(s) that this document is compatible with, see the [matrix][matrix].
+
+## _Image Index_ Property Descriptions
+
+- **`schemaVersion`** *int*
+
+ This REQUIRED property specifies the image manifest schema version.
+ For this version of the specification, this MUST be `2` to ensure backward compatibility with older versions of Docker.
+ The value of this field will not change.
+ This field MAY be removed in a future version of the specification.
+
+- **`mediaType`** *string*
+
+ This property SHOULD be used and [remain compatible][matrix] with earlier versions of this specification and with other similar external formats.
+ When used, this field MUST contain the media type `application/vnd.oci.image.index.v1+json`.
+ This field usage differs from the [descriptor](descriptor.md#properties) use of `mediaType`.
+
+- **`artifactType`** *string*
+
+ This OPTIONAL property contains the type of an artifact when the manifest is used for an artifact.
+ If defined, the value MUST comply with [RFC 6838][rfc6838], including the [naming requirements in its section 4.2][rfc6838-s4.2], and MAY be registered with [IANA][iana].
+
+- **`manifests`** *array of objects*
+
+ This REQUIRED property contains a list of [manifests](manifest.md) for specific platforms.
+ While this property MUST be present, the size of the array MAY be zero.
+
+ Each object in `manifests` includes a set of [descriptor properties](descriptor.md#properties) with the following additional properties and restrictions:
+
+ - **`mediaType`** *string*
+
+ This [descriptor property](descriptor.md#properties) has additional restrictions for `manifests`.
+ Implementations MUST support at least the following media types:
+
+ - [`application/vnd.oci.image.manifest.v1+json`](manifest.md)
+
+ Also, implementations SHOULD support the following media types:
+
+ - `application/vnd.oci.image.index.v1+json` (nested index)
+
+ Image indexes concerned with portability SHOULD use one of the above media types.
+ Future versions of the spec MAY use a different mediatype (i.e. a new versioned format).
+ An encountered `mediaType` that is unknown to the implementation MUST NOT generate an error.
+
+ - **`platform`** *object*
+
+ This OPTIONAL property describes the minimum runtime requirements of the image.
+ This property SHOULD be present if its target is platform-specific.
+
+ - **`architecture`** *string*
+
+ This REQUIRED property specifies the CPU architecture.
+ Image indexes SHOULD use, and implementations SHOULD understand, values listed in the Go Language document for [`GOARCH`][go-environment2].
+
+ - **`os`** *string*
+
+ This REQUIRED property specifies the operating system.
+ Image indexes SHOULD use, and implementations SHOULD understand, values listed in the Go Language document for [`GOOS`][go-environment2].
+
+ - **`os.version`** *string*
+
+ This OPTIONAL property specifies the version of the operating system targeted by the referenced blob.
+ Implementations MAY refuse to use manifests where `os.version` is not known to work with the host OS version.
+ Valid values are implementation-defined. e.g. `10.0.14393.1066` on `windows`.
+
+ - **`os.features`** *array of strings*
+
+ This OPTIONAL property specifies an array of strings, each specifying a mandatory OS feature.
+ When `os` is `windows`, image indexes SHOULD use, and implementations SHOULD understand the following values:
+
+ - `win32k`: image requires `win32k.sys` on the host (Note: `win32k.sys` is missing on Nano Server)
+
+ When `os` is not `windows`, values are implementation-defined and SHOULD be submitted to this specification for standardization.
+
+ - **`variant`** *string*
+
+ This OPTIONAL property specifies the variant of the CPU.
+ Image indexes SHOULD use, and implementations SHOULD understand, `variant` values listed in the [Platform Variants](#platform-variants) table.
+
+ - **`features`** *array of strings*
+
+ This property is RESERVED for future versions of the specification.
+
+ If multiple manifests match a client or runtime's requirements, the first matching entry SHOULD be used.
+
+- **`subject`** *[descriptor](descriptor.md)*
+
+ This OPTIONAL property specifies a [descriptor](descriptor.md) of another manifest.
+ This value, used by the [`referrers` API][referrers-api], indicates a relationship to the specified manifest.
+
+- **`annotations`** *string-string map*
+
+ This OPTIONAL property contains arbitrary metadata for the image index.
+ This OPTIONAL property MUST use the [annotation rules](annotations.md#rules).
+
+ See [Pre-Defined Annotation Keys](annotations.md#pre-defined-annotation-keys).
+
+## Platform Variants
+
+When the variant of the CPU is not listed in the table, values are implementation-defined and SHOULD be submitted to this specification for standardization.
+
+| ISA/ABI | `architecture` | `variant` |
+|-----------------|----------------|-------------|
+| ARM 32-bit, v6 | `arm` | `v6` |
+| ARM 32-bit, v7 | `arm` | `v7` |
+| ARM 32-bit, v8 | `arm` | `v8` |
+| ARM 64-bit, v8 | `arm64` | `v8` |
+
+## Example Image Index
+
+*Example showing a simple image index pointing to image manifests for two platforms:*
+
+```json,title=Image%20Index&mediatype=application/vnd.oci.image.index.v1%2Bjson
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "manifests": [
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7143,
+ "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
+ "platform": {
+ "architecture": "ppc64le",
+ "os": "linux"
+ }
+ },
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
+ "platform": {
+ "architecture": "amd64",
+ "os": "linux"
+ }
+ }
+ ],
+ "annotations": {
+ "com.example.key1": "value1",
+ "com.example.key2": "value2"
+ }
+}
+```
+
+## Example Image Index with multiple media types
+
+*Example showing an image index pointing to manifests with multiple media types:*
+
+```json,title=Image%20Index&mediatype=application/vnd.oci.image.index.v1%2Bjson
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "manifests": [
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7143,
+ "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
+ "platform": {
+ "architecture": "ppc64le",
+ "os": "linux"
+ }
+ },
+ {
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "size": 7682,
+ "digest": "sha256:601570aaff1b68a61eb9c85b8beca1644e698003e0cdb5bce960f193d265a8b7"
+ }
+ ],
+ "annotations": {
+ "com.example.key1": "value1",
+ "com.example.key2": "value2"
+ }
+}
+```
+
+[go-environment2]: https://golang.org/doc/install/source#environment
+[iana]: https://www.iana.org/assignments/media-types/media-types.xhtml
+[matrix]: media-types.md#compatibility-matrix
+[referrers-api]: https://github.com/opencontainers/distribution-spec/blob/main/spec.md#listing-referrers
+[rfc6838]: https://tools.ietf.org/html/rfc6838
+[rfc6838-s4.2]: https://tools.ietf.org/html/rfc6838#section-4.2
diff --git a/image-layout.md b/image-layout.md
new file mode 100644
index 0000000..112561e
--- /dev/null
+++ b/image-layout.md
@@ -0,0 +1,208 @@
+# OCI Image Layout Specification
+
+- The OCI Image Layout is the directory structure for OCI content-addressable blobs and [location-addressable](https://en.wikipedia.org/wiki/Content-addressable_storage#Content-addressed_vs._location-addressed) references (refs).
+- This layout MAY be used in a variety of different transport mechanisms: archive formats (e.g. tar, zip), shared filesystem environments (e.g. nfs), or networked file fetching (e.g. http, ftp, rsync).
+
+Given an image layout and a ref, a tool can create an [OCI Runtime Specification bundle](https://github.com/opencontainers/runtime-spec/blob/v1.0.0/bundle.md) by:
+
+- Following the ref to find a [manifest](manifest.md#image-manifest), possibly via an [image index](image-index.md)
+- [Applying the filesystem layers](layer.md#applying) in the specified order
+- Converting the [image configuration](config.md) into an [OCI Runtime Specification `config.json`](https://github.com/opencontainers/runtime-spec/blob/v1.0.0/config.md)
+
+## Content
+
+The image layout is as follows:
+
+- `blobs` directory
+ - Contains content-addressable blobs
+ - A blob has no schema and SHOULD be considered opaque
+ - Directory MUST exist and MAY be empty
+ - See [blobs](#blobs) section
+- `oci-layout` file
+ - It MUST exist
+ - It MUST be a JSON object
+ - It MUST contain an `imageLayoutVersion` field
+ - See [oci-layout file](#oci-layout-file) section
+ - It MAY include additional fields
+- `index.json` file
+ - It MUST exist
+ - It MUST be an [image index](image-index.md) JSON object.
+ - See [index.json](#indexjson-file) section
+
+## Example Layout
+
+This is an example image layout:
+
+```shell
+$ cd example.com/app/
+$ find . -type f
+./index.json
+./oci-layout
+./blobs/sha256/3588d02542238316759cbf24502f4344ffcc8a60c803870022f335d1390c13b4
+./blobs/sha256/4b0bc1c4050b03c95ef2a8e36e25feac42fd31283e8c30b3ee5df6b043155d3c
+./blobs/sha256/7968321274dc6b6171697c33df7815310468e694ac5be0ec03ff053bb135e768
+```
+
+Blobs are named by their contents:
+
+```shell
+$ shasum -a 256 ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51
+afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51
+```
+
+## Blobs
+
+- Object names in the `blobs` subdirectories are composed of a directory for each hash algorithm, the children of which will contain the actual content.
+- The content of `blobs/<alg>/<encoded>` MUST match the digest `<alg>:<encoded>` (referenced per [descriptor](descriptor.md#digests)). For example, the content of `blobs/sha256/da39a3ee5e6b4b0d3255bfef95601890afd80709` MUST match the digest `sha256:da39a3ee5e6b4b0d3255bfef95601890afd80709`.
+- The character set of the entry name for `<alg>` and `<encoded>` MUST match the respective grammar elements described in [descriptor](descriptor.md#digests).
+- The blobs directory MAY contain blobs which are not referenced by any of the [refs](#indexjson-file).
+- The blobs directory MAY be missing referenced blobs, in which case the missing blobs SHOULD be fulfilled by an external blob store.
+
+### Example Blobs
+
+```shell
+$ cat ./blobs/sha256/9b97579de92b1c195b85bb42a11011378ee549b02d7fe9c17bf2a6b35d5cb079 | jq
+{
+ "schemaVersion": 2,
+ "manifests": [
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7143,
+ "digest": "sha256:afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51",
+ "platform": {
+ "architecture": "ppc64le",
+ "os": "linux"
+ }
+ },
+...
+```
+
+```shell
+$ cat ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 | jq
+{
+ "schemaVersion": 2,
+ "config": {
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "size": 7023,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "size": 32654,
+ "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0"
+ },
+...
+```
+
+```shell
+$ cat ./blobs/sha256/5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270 | jq
+{
+ "architecture": "amd64",
+ "author": "Alyssa P. Hacker <alyspdev@example.com>",
+ "config": {
+ "Hostname": "8dfe43d80430",
+ "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"
+ ],
+ "Cmd": null,
+ "Image": "sha256:6986ae504bbf843512d680cc959484452034965db15f75ee8bdd1b107f61500b",
+...
+```
+
+```shell
+$ cat ./blobs/sha256/9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0
+[gzipped tar stream]
+```
+
+## oci-layout file
+
+This JSON object serves as a marker for the base of an Open Container Image Layout and to provide the version of the image-layout in use.
+The `imageLayoutVersion` value will align with the OCI Image Specification version at the time changes to the layout are made, and will pin a given version until changes to the image layout are required.
+This section defines the `application/vnd.oci.layout.header.v1+json` [media type](media-types.md).
+
+### oci-layout Example
+
+```json,title=OCI%20Layout&mediatype=application/vnd.oci.layout.header.v1%2Bjson
+{
+ "imageLayoutVersion": "1.0.0"
+}
+```
+
+## index.json file
+
+This REQUIRED file is the entry point for references and descriptors of the image-layout.
+The [image index](image-index.md) is a multi-descriptor entry point.
+
+This index provides an established path (`/index.json`) to have an entry point for an image-layout and to discover auxiliary descriptors.
+
+- No semantic restriction is given for the "org.opencontainers.image.ref.name" annotation of descriptors.
+- In general the `mediaType` of each [descriptor][descriptors] object in the `manifests` field will be either `application/vnd.oci.image.index.v1+json` or `application/vnd.oci.image.manifest.v1+json`.
+- Future versions of the spec MAY use a different mediatype (i.e. a new versioned format).
+- An encountered `mediaType` that is unknown MUST NOT generate an error.
+
+**Implementor's Note:**
+A common use case of descriptors with a "org.opencontainers.image.ref.name" annotation is representing a "tag" for a container image.
+For example, an image may have a tag for different versions or builds of the software.
+In the wild you often see "tags" like "v1.0.0-vendor.0", "2.0.0-debug", etc.
+Those tags will often be represented in an image-layout repository with matching "org.opencontainers.image.ref.name" annotations like "v1.0.0-vendor.0", "2.0.0-debug", etc.
+
+### Index Example
+
+```json,title=Image%20Index&mediatype=application/vnd.oci.image.index.v1%2Bjson
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "manifests": [
+ {
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "size": 7143,
+ "digest": "sha256:0228f90e926ba6b96e4f39cf294b2586d38fbb5a1e385c05cd1ee40ea54fe7fd",
+ "annotations": {
+ "org.opencontainers.image.ref.name": "stable-release"
+ }
+ },
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7143,
+ "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
+ "platform": {
+ "architecture": "ppc64le",
+ "os": "linux"
+ },
+ "annotations": {
+ "org.opencontainers.image.ref.name": "v1.0"
+ }
+ },
+ {
+ "mediaType": "application/xml",
+ "size": 7143,
+ "digest": "sha256:b3d63d132d21c3ff4c35a061adf23cf43da8ae054247e32faa95494d904a007e",
+ "annotations": {
+ "org.freedesktop.specifications.metainfo.version": "1.0",
+ "org.freedesktop.specifications.metainfo.type": "AppStream"
+ }
+ }
+ ],
+ "annotations": {
+ "com.example.index.revision": "r124356"
+ }
+}
+```
+
+This illustrates an index that provides two named references and an auxiliary mediatype for this image layout.
+
+The first named reference (`stable-release`) points to another index that might contain multiple references with distinct platforms and annotations.
+Note that the [`org.opencontainers.image.ref.name` annotation](annotations.md) SHOULD only be considered valid when on descriptors on `index.json`.
+
+The second named reference (`v1.0`) points to a manifest that is specific to the linux/ppc64le platform.
+
+[descriptors]: ./descriptor.md
diff --git a/img/build-diagram.png b/img/build-diagram.png
new file mode 100644
index 0000000..9ad9b84
--- /dev/null
+++ b/img/build-diagram.png
Binary files differ
diff --git a/img/media-types.dot b/img/media-types.dot
new file mode 100644
index 0000000..8583d5f
--- /dev/null
+++ b/img/media-types.dot
@@ -0,0 +1,17 @@
+digraph G {
+ {
+ imageIndex [shape=note, label="Image Index\n<<optional>>\napplication/vnd.oci.image.index.v1+json"]
+ {
+ rank=same
+ manifest [shape=note, label="Image manifest\napplication/vnd.oci.image.manifest.v1+json"]
+ }
+ config [shape=note, label="Image config JSON\napplication/vnd.oci.image.config.v1+json"]
+ layer [shape=note, label="Layer tar archive\napplication/vnd.oci.image.layer.v1.tar\napplication/vnd.oci.image.layer.v1.tar+gzip\napplication/vnd.oci.image.layer.nondistributable.v1.tar\napplication/vnd.oci.image.layer.nondistributable.v1.tar+gzip"]
+ }
+
+ imageIndex -> imageIndex [label="1..*"]
+ imageIndex -> manifest [label="1..*"]
+ manifest -> config [label="1..1"]
+ manifest -> layer [label="1..*"]
+ manifest -> manifest [label="0..1"];
+}
diff --git a/img/media-types.png b/img/media-types.png
new file mode 100644
index 0000000..70d2fe6
--- /dev/null
+++ b/img/media-types.png
Binary files differ
diff --git a/img/run-diagram.png b/img/run-diagram.png
new file mode 100644
index 0000000..c1009f4
--- /dev/null
+++ b/img/run-diagram.png
Binary files differ
diff --git a/implementations.md b/implementations.md
new file mode 100644
index 0000000..cf4b201
--- /dev/null
+++ b/implementations.md
@@ -0,0 +1,26 @@
+# OCI Image Implementations
+
+Projects or Companies currently adopting the OCI Image Specification
+
+- [projectatomic/skopeo](https://github.com/projectatomic/skopeo)
+- [Amazon Elastic Container Registry (ECR)](https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-manifest-formats.html) ([announcement](https://aws.amazon.com/about-aws/whats-new/2017/01/amazon-ecr-supports-docker-image-manifest-v2-schema-2/))
+- [Azure Container Registry (ACR)](https://docs.microsoft.com/azure/container-registry/container-registry-image-formats#oci-images)
+- [openSUSE/umoci](https://github.com/openSUSE/umoci)
+- [cloudfoundry/grootfs](https://github.com/cloudfoundry/grootfs) ([source](https://github.com/cloudfoundry/grootfs/blob/c3da26e1e463b51be1add289032f3dca6698b335/fetcher/remote/docker_src.go))
+- [Mesos plans](https://issues.apache.org/jira/browse/MESOS-5011) ([design doc](https://docs.google.com/document/d/1Pus7D-inIBoLSIPyu3rl_apxvUhtp3rp0_b0Ttr2Xww/edit#heading=h.hrvk2wboog4p))
+- [Docker](https://github.com/docker)
+ - [docker/docker (`docker save/load` WIP)](https://github.com/docker/docker/pull/26369)
+ - [distribution/distribution (registry PR)](https://github.com/distribution/distribution/pull/2076)
+- [containerd/containerd](https://github.com/containerd/containerd)
+- [Containers](https://github.com/containers/)
+ - [containers/build](https://github.com/containers/build)
+ - [containers/image](https://github.com/containers/image)
+ - [containers/oci-spec-rs](https://github.com/containers/oci-spec-rs)
+ - [containers/libocispec](https://github.com/containers/libocispec)
+- [krustlet/oci-distribution](https://github.com/krustlet/oci-distribution)
+- [coreos/rkt](https://github.com/coreos/rkt)
+- [box-builder/box](https://github.com/box-builder/box)
+- [coolljt0725/docker2oci](https://github.com/coolljt0725/docker2oci)
+- [regclient/regclient](https://github.com/regclient/regclient)
+
+_(to add your project please open a [pull-request](https://github.com/opencontainers/image-spec/pulls))_
diff --git a/layer.md b/layer.md
new file mode 100644
index 0000000..fd12a5b
--- /dev/null
+++ b/layer.md
@@ -0,0 +1,344 @@
+# Image Layer Filesystem Changeset
+
+This document describes how to serialize a filesystem and filesystem changes like removed files into a blob called a layer.
+One or more layers are applied on top of each other to create a complete filesystem.
+This document will use a concrete example to illustrate how to create and consume these filesystem layers.
+
+This section defines the `application/vnd.oci.image.layer.v1.tar`, `application/vnd.oci.image.layer.v1.tar+gzip`, `application/vnd.oci.image.layer.v1.tar+zstd`, `application/vnd.oci.image.layer.nondistributable.v1.tar`, `application/vnd.oci.image.layer.nondistributable.v1.tar+gzip`, and `application/vnd.oci.image.layer.nondistributable.v1.tar+zstd` [media types](media-types.md).
+
+## `+gzip` Media Types
+
+- The media type `application/vnd.oci.image.layer.v1.tar+gzip` represents an `application/vnd.oci.image.layer.v1.tar` payload which has been compressed with [gzip][rfc1952_2].
+- The media type `application/vnd.oci.image.layer.nondistributable.v1.tar+gzip` represents an `application/vnd.oci.image.layer.nondistributable.v1.tar` payload which has been compressed with [gzip][rfc1952_2].
+
+## `+zstd` Media Types
+
+- The media type `application/vnd.oci.image.layer.v1.tar+zstd` represents an `application/vnd.oci.image.layer.v1.tar` payload which has been compressed with [zstd][rfc8478].
+- The media type `application/vnd.oci.image.layer.nondistributable.v1.tar+zstd` represents an `application/vnd.oci.image.layer.nondistributable.v1.tar` payload which has been compressed with [zstd][rfc8478].
+
+## Distributable Format
+
+- Layer Changesets for the [media type](media-types.md) `application/vnd.oci.image.layer.v1.tar` MUST be packaged in [tar archive][tar-archive].
+- Layer Changesets for the [media type](media-types.md) `application/vnd.oci.image.layer.v1.tar` MUST NOT include duplicate entries for file paths in the resulting [tar archive][tar-archive].
+
+## Change Types
+
+Types of changes that can occur in a changeset are:
+
+- Additions
+- Modifications
+- Removals
+
+Additions and Modifications are represented the same in the changeset tar archive.
+
+Removals are represented using "[whiteout](#whiteouts)" file entries (See [Representing Changes](#representing-changes)).
+
+### File Types
+
+Throughout this document section, the use of word "files" or "entries" includes the following, where supported:
+
+- regular files
+- directories
+- sockets
+- symbolic links
+- block devices
+- character devices
+- FIFOs
+
+### File Attributes
+
+Where supported, MUST include file attributes for Additions and Modifications include:
+
+- Modification Time (`mtime`)
+- User ID (`uid`)
+ - User Name (`uname`) *secondary to `uid`*
+- Group ID (`gid`)
+ - Group Name (`gname`) *secondary to `gid`*
+- Mode (`mode`)
+- Extended Attributes (`xattrs`)
+- Symlink reference (`linkname` + symbolic link type)
+- [Hardlink](#hardlinks) reference (`linkname`)
+
+[Sparse files](https://en.wikipedia.org/wiki/Sparse_file) SHOULD NOT be used because they lack consistent support across tar implementations.
+
+#### Hardlinks
+
+- Hardlinks are a [POSIX concept](https://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html) for having one or more directory entries for the same file on the same device.
+- Not all filesystems support hardlinks (e.g. [FAT](https://en.wikipedia.org/wiki/File_Allocation_Table)).
+- Hardlinks are possible with all [file types](#file-types) except `directories`.
+- Non-directory files are considered "hardlinked" when their link count is greater than 1.
+- Hardlinked files are on a same device (i.e. comparing Major:Minor pair) and have the same inode.
+- The corresponding files that share the link with the > 1 linkcount may be outside the directory that the changeset is being produced from, in which case the `linkname` is not recorded in the changeset.
+- Hardlinks are stored in a tar archive with type of a `1` char, per the [GNU Basic Tar Format][gnu-tar-standard] and [libarchive tar(5)][libarchive-tar].
+- While approaches to deriving new or changed hardlinks may vary, a possible approach is:
+
+```text
+SET LinkMap to map[< Major:Minor String >]map[< inode integer >]< path string >
+SET LinkNames to map[< src path string >]< dest path string >
+FOR each path in root path
+ IF path type is directory
+ CONTINUE
+ ENDIF
+ SET filestat to stat(path)
+ IF filestat num of links == 1
+ CONTINUE
+ ENDIF
+ IF LinkMap[filestat device][filestat inode] is not empty
+ SET LinkNames[path] to LinkMap[filestat device][filestat inode]
+ ELSE
+ SET LinkMap[filestat device][filestat inode] to path
+ ENDIF
+END FOR
+```
+
+With this approach, the link map and links names of a directory could be compared against that of another directory to derive additions and changes to hardlinks.
+
+#### Platform-specific attributes
+
+Implementations on Windows MUST support these additional attributes, encoded in [PAX vendor
+extensions](https://github.com/libarchive/libarchive/wiki/ManPageTar5#pax-interchange-format) as follows:
+
+- [Windows file attributes](https://msdn.microsoft.com/en-us/library/windows/desktop/gg258117(v=vs.85).aspx) (`MSWINDOWS.fileattr`)
+- [Security descriptor](https://msdn.microsoft.com/en-us/library/cc230366.aspx) (`MSWINDOWS.rawsd`): base64-encoded self-relative binary security descriptor
+- Mount points (`MSWINDOWS.mountpoint`): if present on a directory symbolic link, then the link should be created as a [directory junction](https://en.wikipedia.org/wiki/NTFS_junction_point)
+- Creation time (`LIBARCHIVE.creationtime`)
+
+## Creating
+
+### Initial Root Filesystem
+
+The initial root filesystem is the base or parent layer.
+
+For this example, an image root filesystem has an initial state as an empty directory.
+The name of the directory is not relevant to the layer itself, only for the purpose of producing comparisons.
+
+Here is an initial empty directory structure for a changeset, with a unique directory name `rootfs-c9d-v1`.
+
+```text
+rootfs-c9d-v1/
+```
+
+### Populate Initial Filesystem
+
+Files and directories are then created:
+
+```text
+rootfs-c9d-v1/
+ etc/
+ my-app-config
+ bin/
+ my-app-binary
+ my-app-tools
+```
+
+The `rootfs-c9d-v1` directory is then created as a plain [tar archive][tar-archive] with relative path to `rootfs-c9d-v1`.
+Entries for the following files:
+
+```text
+./
+./etc/
+./etc/my-app-config
+./bin/
+./bin/my-app-binary
+./bin/my-app-tools
+```
+
+### Populate a Comparison Filesystem
+
+Create a new directory and initialize it with a copy or snapshot of the prior root filesystem.
+Example commands that can preserve [file attributes](#file-attributes) to make this copy are:
+
+- [cp(1)](https://linux.die.net/man/1/cp): `cp -a rootfs-c9d-v1/ rootfs-c9d-v1.s1/`
+- [rsync(1)](https://linux.die.net/man/1/rsync): `rsync -aHAX rootfs-c9d-v1/ rootfs-c9d-v1.s1/`
+- [tar(1)](https://linux.die.net/man/1/tar): `mkdir rootfs-c9d-v1.s1 && tar --acls --xattrs -C rootfs-c9d-v1/ -c . | tar -C rootfs-c9d-v1.s1/ --acls --xattrs -x` (including `--selinux` where supported)
+
+Any [changes](#change-types) to the snapshot MUST NOT change or affect the directory it was copied from.
+
+For example `rootfs-c9d-v1.s1` is an identical snapshot of `rootfs-c9d-v1`.
+In this way `rootfs-c9d-v1.s1` is prepared for updates and alterations.
+
+**Implementor's Note**: *a copy-on-write or union filesystem can efficiently make directory snapshots*
+
+Initial layout of the snapshot:
+
+```text
+rootfs-c9d-v1.s1/
+ etc/
+ my-app-config
+ bin/
+ my-app-binary
+ my-app-tools
+```
+
+See [Change Types](#change-types) for more details on changes.
+
+For example, add a directory at `/etc/my-app.d` containing a default config file, removing the existing config file.
+Also a change (in attribute or file content) to `./bin/my-app-tools` binary to handle the config layout change.
+
+Following these changes, the representation of the `rootfs-c9d-v1.s1` directory:
+
+```text
+rootfs-c9d-v1.s1/
+ etc/
+ my-app.d/
+ default.cfg
+ bin/
+ my-app-binary
+ my-app-tools
+```
+
+### Determining Changes
+
+When two directories are compared, the relative root is the top-level directory.
+The directories are compared, looking for files that have been [added, modified, or removed](#change-types).
+
+For this example, `rootfs-c9d-v1/` and `rootfs-c9d-v1.s1/` are recursively compared, each as relative root path.
+
+The following changeset is found:
+
+```text
+Added: /etc/my-app.d/
+Added: /etc/my-app.d/default.cfg
+Modified: /bin/my-app-tools
+Deleted: /etc/my-app-config
+```
+
+This reflects the removal of `/etc/my-app-config` and creation of a file and directory at `/etc/my-app.d/default.cfg`.
+`/bin/my-app-tools` has also been replaced with an updated version.
+
+### Representing Changes
+
+A [tar archive][tar-archive] is then created which contains _only_ this changeset:
+
+- Added and modified files and directories in their entirety
+- Deleted files or directories marked with a [whiteout file](#whiteouts)
+
+The resulting tar archive for `rootfs-c9d-v1.s1` has the following entries:
+
+```text
+./etc/my-app.d/
+./etc/my-app.d/default.cfg
+./bin/my-app-tools
+./etc/.wh.my-app-config
+```
+
+To signify that the resource `./etc/my-app-config` MUST be removed when the changeset is applied, the basename of the entry is prefixed with `.wh.`.
+
+## Applying Changesets
+
+- Layer Changesets of [media type](media-types.md) `application/vnd.oci.image.layer.v1.tar` are _applied_, rather than simply extracted as tar archives.
+- Applying a layer changeset requires special consideration for the [whiteout](#whiteouts) files.
+- In the absence of any [whiteout](#whiteouts) files in a layer changeset, the archive is extracted like a regular tar archive.
+
+### Changeset over existing files
+
+This section specifies applying an entry from a layer changeset if the target path already exists.
+
+If the entry and the existing path are both directories, then the existing path's attributes MUST be replaced by those of the entry in the changeset.
+In all other cases, the implementation MUST do the semantic equivalent of the following:
+
+- removing the file path (e.g. [`unlink(2)`](https://linux.die.net/man/2/unlink) on Linux systems)
+- recreating the file path, based on the contents and attributes of the changeset entry
+
+## Whiteouts
+
+- A whiteout file is an empty file with a special filename that signifies a path should be deleted.
+- A whiteout filename consists of the prefix `.wh.` plus the basename of the path to be deleted.
+- As files prefixed with `.wh.` are special whiteout markers, it is not possible to create a filesystem which has a file or directory with a name beginning with `.wh.`.
+- Once a whiteout is applied, the whiteout itself MUST also be hidden.
+- Whiteout files MUST only apply to resources in lower/parent layers.
+- Files that are present in the same layer as a whiteout file can only be hidden by whiteout files in subsequent layers.
+
+The following is a base layer with several resources:
+
+```text
+a/
+a/b/
+a/b/c/
+a/b/c/bar
+```
+
+When the next layer is created, the original `a/b` directory is deleted and recreated with `a/b/c/foo`:
+
+```text
+a/
+a/.wh..wh..opq
+a/b/
+a/b/c/
+a/b/c/foo
+```
+
+When processing the second layer, `a/.wh..wh..opq` is applied first, before creating the new version of `a/b`, regardless of the ordering in which the whiteout file was encountered.
+For example, the following layer is equivalent to the layer above:
+
+```text
+a/
+a/b/
+a/b/c/
+a/b/c/foo
+a/.wh..wh..opq
+```
+
+Implementations SHOULD generate layers such that the whiteout files appear before sibling directory entries.
+
+### Opaque Whiteout
+
+- In addition to expressing that a single entry should be removed from a lower layer, layers may remove all of the children using an opaque whiteout entry.
+- An opaque whiteout entry is a file with the name `.wh..wh..opq` indicating that all siblings are hidden in the lower layer.
+
+Let's take the following base layer as an example:
+
+```text
+etc/
+ my-app-config
+bin/
+ my-app-binary
+ my-app-tools
+ tools/
+ my-app-tool-one
+```
+
+If all children of `bin/` are removed, the next layer would have the following:
+
+```text
+bin/
+ .wh..wh..opq
+```
+
+This is called _opaque whiteout_ format.
+An _opaque whiteout_ file hides _all_ children of the `bin/` including sub-directories and all descendants.
+Using _explicit whiteout_ files, this would be equivalent to the following:
+
+```text
+bin/
+ .wh.my-app-binary
+ .wh.my-app-tools
+ .wh.tools
+```
+
+In this case, a unique whiteout file is generated for each entry.
+If there were more children of `bin/` in the base layer, there would be an entry for each.
+Note that this opaque file will apply to _all_ children, including sub-directories, other resources and all descendants.
+
+Implementations SHOULD generate layers using _explicit whiteout_ files, but MUST accept both.
+
+Any given image is likely to be composed of several of these Image Filesystem Changeset tar archives.
+
+## Non-Distributable Layers
+
+> **NOTE**: Non-distributable layers are deprecated, and not recommended for future use.
+> Implementations SHOULD NOT produce new non-distributable layers.
+
+Due to legal requirements, certain layers may not be regularly distributable.
+Such "non-distributable" layers are typically downloaded directly from a distributor but never uploaded.
+
+Non-distributable layers SHOULD be tagged with an alternative mediatype of `application/vnd.oci.image.layer.nondistributable.v1.tar`.
+Implementations SHOULD NOT upload layers tagged with this media type; however, such a media type SHOULD NOT affect whether an implementation downloads the layer.
+
+[Descriptors](descriptor.md) referencing non-distributable layers MAY include `urls` for downloading these layers directly; however, the presence of the `urls` field SHOULD NOT be used to determine whether or not a layer is non-distributable.
+
+[libarchive-tar]: https://github.com/libarchive/libarchive/wiki/ManPageTar5#POSIX_ustar_Archives
+[gnu-tar-standard]: https://www.gnu.org/software/tar/manual/html_node/Standard.html
+[rfc1952_2]: https://tools.ietf.org/html/rfc1952
+[tar-archive]: https://en.wikipedia.org/wiki/Tar_(computing)
+[rfc8478]: https://tools.ietf.org/html/rfc8478
diff --git a/manifest.md b/manifest.md
new file mode 100644
index 0000000..9839416
--- /dev/null
+++ b/manifest.md
@@ -0,0 +1,262 @@
+# OCI Image Manifest Specification
+
+There are three main goals of the Image Manifest Specification.
+The first goal is content-addressable images, by supporting an image model where the image's configuration can be hashed to generate a unique ID for the image and its components.
+The second goal is to allow multi-architecture images, through a "fat manifest" which references image manifests for platform-specific versions of an image.
+In OCI, this is codified in an [image index](image-index.md).
+The third goal is to be [translatable](conversion.md) to the [OCI Runtime Specification](https://github.com/opencontainers/runtime-spec).
+
+This section defines the `application/vnd.oci.image.manifest.v1+json` [media type](media-types.md).
+For the media type(s) that this is compatible with see the [matrix](media-types.md#compatibility-matrix).
+
+## Image Manifest
+
+Unlike the [image index](image-index.md), which contains information about a set of images that can span a variety of architectures and operating systems, an image manifest provides a configuration and set of layers for a single container image for a specific architecture and operating system.
+
+## _Image Manifest_ Property Descriptions
+
+- **`schemaVersion`** *int*
+
+ This REQUIRED property specifies the image manifest schema version.
+ For this version of the specification, this MUST be `2` to ensure backward compatibility with older versions of Docker. The value of this field will not change. This field MAY be removed in a future version of the specification.
+
+- **`mediaType`** *string*
+
+ This property SHOULD be used and [remain compatible](media-types.md#compatibility-matrix) with earlier versions of this specification and with other similar external formats.
+ When used, this field MUST contain the media type `application/vnd.oci.image.manifest.v1+json`.
+ This field usage differs from the [descriptor](descriptor.md#properties) use of `mediaType`.
+
+- **`artifactType`** *string*
+
+ This OPTIONAL property contains the type of an artifact when the manifest is used for an artifact.
+ This MUST be set when `config.mediaType` is set to the [empty value](#guidance-for-an-empty-descriptor).
+ If defined, the value MUST comply with [RFC 6838][rfc6838], including the [naming requirements in its section 4.2][rfc6838-s4.2], and MAY be registered with [IANA][iana].
+ Implementations storing or copying image manifests MUST NOT error on encountering an `artifactType` that is unknown to the implementation.
+
+- **`config`** *[descriptor](descriptor.md)*
+
+ This REQUIRED property references a configuration object for a container, by digest.
+ Beyond the [descriptor requirements](descriptor.md#properties), the value has the following additional restrictions:
+
+ - **`mediaType`** *string*
+
+ This [descriptor property](descriptor.md#properties) has additional restrictions for `config`.
+
+ Implementations MUST NOT attempt to parse the referenced content if this media type is unknown and instead consider the referenced content as arbitrary binary data (e.g.: as `application/octet-stream`).
+
+ Implementations storing or copying image manifests MUST NOT error on encountering a value that is unknown to the implementation.
+
+ Implementations MUST support at least the following media types:
+
+ - [`application/vnd.oci.image.config.v1+json`](config.md)
+
+ Manifests for container images concerned with portability SHOULD use one of the above media types.
+ Manifests for artifacts concerned with portability SHOULD use `config.mediaType` as described in [Guidelines for Artifact Usage](#guidelines-for-artifact-usage).
+
+ If the manifest uses a different media type than the above, it MUST comply with [RFC 6838][rfc6838], including the [naming requirements in its section 4.2][rfc6838-s4.2], and MAY be registered with [IANA][iana].
+
+ To set an effectively null or empty config and maintain portability see the [guidance for an empty descriptor](#guidance-for-an-empty-descriptor) below, and `DescriptorEmptyJSON` of the reference code.
+
+- **`layers`** *array of objects*
+
+ Each item in the array MUST be a [descriptor](descriptor.md).
+ For portability, `layers` SHOULD have at least one entry.
+ See the [guidance for an empty descriptor](#guidance-for-an-empty-descriptor) below, and `DescriptorEmptyJSON` of the reference code.
+
+ When the `config.mediaType` is set to `application/vnd.oci.image.config.v1+json`, the following additional restrictions apply:
+
+ - The array MUST have the base layer at index 0.
+ - Subsequent layers MUST then follow in stack order (i.e. from `layers[0]` to `layers[len(layers)-1]`).
+ - The final filesystem layout MUST match the result of [applying](layer.md#applying-changesets) the layers to an empty directory.
+ - The [ownership, mode, and other attributes](layer.md#file-attributes) of the initial empty directory are unspecified.
+
+ Beyond the [descriptor requirements](descriptor.md#properties), the value has the following additional restrictions:
+
+ - **`mediaType`** *string*
+
+ This [descriptor property](descriptor.md#properties) has additional restrictions for `layers[]`.
+ Implementations MUST support at least the following media types:
+
+ - [`application/vnd.oci.image.layer.v1.tar`](layer.md)
+ - [`application/vnd.oci.image.layer.v1.tar+gzip`](layer.md#gzip-media-types)
+ - [`application/vnd.oci.image.layer.nondistributable.v1.tar`](layer.md#non-distributable-layers)
+ - [`application/vnd.oci.image.layer.nondistributable.v1.tar+gzip`](layer.md#gzip-media-types)
+
+ Manifests concerned with portability SHOULD use one of the above media types.
+ Implementations storing or copying image manifests MUST NOT error on encountering a `mediaType` that is unknown to the implementation.
+
+ Entries in this field will frequently use the `+gzip` types.
+
+ If the manifest uses a different media type than the above, it MUST comply with [RFC 6838][rfc6838], including the [naming requirements in its section 4.2][rfc6838-s4.2], and MAY be registered with [IANA][iana].
+
+ See [Guidelines for Artifact Usage](#guidelines-for-artifact-usage) for other uses of the `layers`.
+
+- **`subject`** *[descriptor](descriptor.md)*
+
+ This OPTIONAL property specifies a [descriptor](descriptor.md) of another manifest.
+ This value, used by the [`referrers` API](https://github.com/opencontainers/distribution-spec/blob/main/spec.md#listing-referrers), indicates a relationship to the specified manifest.
+
+- **`annotations`** *string-string map*
+
+ This OPTIONAL property contains arbitrary metadata for the image manifest.
+ This OPTIONAL property MUST use the [annotation rules](annotations.md#rules).
+
+ See [Pre-Defined Annotation Keys](annotations.md#pre-defined-annotation-keys).
+
+## Example Image Manifest
+
+*Example showing an image manifest:*
+
+```json,title=Manifest&mediatype=application/vnd.oci.image.manifest.v1%2Bjson
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "config": {
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7",
+ "size": 7023
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0",
+ "size": 32654
+ },
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b",
+ "size": 16724
+ },
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736",
+ "size": 73109
+ }
+ ],
+ "subject": {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
+ "size": 7682
+ },
+ "annotations": {
+ "com.example.key1": "value1",
+ "com.example.key2": "value2"
+ }
+}
+```
+
+## Guidance for an Empty Descriptor
+
+*Implementers note*: The following is considered GUIDANCE for portability.
+
+Parts of the spec necessitate including a descriptor to a blob where some implementations of artifacts do not have associated content.
+While an empty blob (`size` of 0) may be preferable, practice has shown that not to be ubiquitously supported.
+The media type `application/vnd.oci.empty.v1+json` (`MediaTypeEmptyJSON`) has been specified for a descriptor that has no content for the implementation.
+The blob payload is the most minimal content that is still a valid JSON object: `{}` (`size` of 2).
+The blob digest of `{}` is `sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a`.
+The data field is optional, and if included is the base64 encoding of `{}`: `e30=`.
+
+The resulting descriptor shown here is also defined in reference code as `DescriptorEmptyJSON`:
+
+```json,title=empty%20config&mediatype=application/vnd.oci.descriptor.v1%2Bjson
+{
+ "mediaType": "application/vnd.oci.empty.v1+json",
+ "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
+ "size": 2,
+ "data": "e30="
+}
+```
+
+## Guidelines for Artifact Usage
+
+Content other than OCI container images MAY be packaged using the image manifest.
+When this is done, the `config.mediaType` value MUST be set to a value specific to the artifact type or the [empty value](#guidance-for-an-empty-descriptor).
+If the `config.mediaType` is set to the empty value, the `artifactType` MUST be defined.
+If the artifact does not need layers, a single layer SHOULD be included with a non-zero size.
+The suggested content for an unused `layers` array is the [empty descriptor](#guidance-for-an-empty-descriptor).
+
+The design of the artifact depends on what content is being packaged with the artifact.
+The decision tree below and the associated examples MAY be used to design new artifacts:
+
+1. Does the artifact consist of at least one file or blob?
+ If yes, continue to 2.
+ If no, specify the `artifactType`, and set the `config` and a single `layers` element to the empty descriptor value.
+ Here is an example of this with annotations included:
+
+ ```json,title=Minimal%20artifact&mediatype=application/vnd.oci.image.manifest.v1%2Bjson
+ {
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "artifactType": "application/vnd.example+type",
+ "config": {
+ "mediaType": "application/vnd.oci.empty.v1+json",
+ "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
+ "size": 2
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.oci.empty.v1+json",
+ "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
+ "size": 2
+ }
+ ],
+ "annotations": {
+ "oci.opencontainers.image.created": "2023-01-02T03:04:05Z",
+ "com.example.data": "payload"
+ }
+ }
+ ```
+
+2. Does the artifact have additional JSON formatted metadata as configuration?
+ If yes, continue to 3.
+ If no, specify the `artifactType`, include the artifact in the `layers`, and set `config` to the empty descriptor value.
+ Here is an example of this with a single layer:
+
+ ```json,title=Artifact%20without%20config&mediatype=application/vnd.oci.image.manifest.v1%2Bjson
+ {
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "artifactType": "application/vnd.example+type",
+ "config": {
+ "mediaType": "application/vnd.oci.empty.v1+json",
+ "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
+ "size": 2
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.example+type",
+ "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317",
+ "size": 1234
+ }
+ ]
+ }
+ ```
+
+3. For artifacts with a config blob, specify the `artifactType` to a common value for your artifact tooling, specify the `config` with the metadata for this artifact, and include the artifact in the `layers`.
+ Here is an example of this:
+
+ ```json,title=Artifact%20with%20config&mediatype=application/vnd.oci.image.manifest.v1%2Bjson
+ {
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "artifactType": "application/vnd.example+type",
+ "config": {
+ "mediaType": "application/vnd.example.config.v1+json",
+ "digest": "sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03",
+ "size": 123
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.example.data.v1.tar+gzip",
+ "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317",
+ "size": 1234
+ }
+ ]
+ }
+ ```
+
+_Implementers note:_ artifacts have historically been created without an `artifactType` field, and tooling to work with artifacts should fallback to the `config.mediaType` value.
+
+[iana]: https://www.iana.org/assignments/media-types/media-types.xhtml
+[rfc6838]: https://tools.ietf.org/html/rfc6838
+[rfc6838-s4.2]: https://tools.ietf.org/html/rfc6838#section-4.2
diff --git a/media-types.md b/media-types.md
new file mode 100644
index 0000000..8cc372e
--- /dev/null
+++ b/media-types.md
@@ -0,0 +1,90 @@
+# OCI Image Media Types
+
+The following media types identify the formats described here and their referenced resources:
+
+- `application/vnd.oci.descriptor.v1+json`: [Content Descriptor](descriptor.md)
+- `application/vnd.oci.layout.header.v1+json`: [OCI Layout](image-layout.md#oci-layout-file)
+- `application/vnd.oci.image.index.v1+json`: [Image Index](image-index.md)
+- `application/vnd.oci.image.manifest.v1+json`: [Image manifest](manifest.md#image-manifest)
+- `application/vnd.oci.image.config.v1+json`: [Image config](config.md)
+- `application/vnd.oci.image.layer.v1.tar`: ["Layer", as a tar archive](layer.md)
+- `application/vnd.oci.image.layer.v1.tar+gzip`: ["Layer", as a tar archive](layer.md#gzip-media-types) compressed with [gzip][rfc1952]
+- `application/vnd.oci.image.layer.v1.tar+zstd`: ["Layer", as a tar archive](layer.md#zstd-media-types) compressed with [zstd][rfc8478]
+- `application/vnd.oci.empty.v1+json`: [Empty for unused descriptors](manifest.md#guidance-for-an-empty-descriptor)
+
+The following media types identify a ["Layer" with distribution restrictions](layer.md#non-distributable-layers), but are **deprecated** and not recommended for future use:
+
+- `application/vnd.oci.image.layer.nondistributable.v1.tar`: "Layer", as a tar archive
+- `application/vnd.oci.image.layer.nondistributable.v1.tar+gzip`: ["Layer", as a tar archive with distribution restrictions](layer.md#gzip-media-types) compressed with [gzip][rfc1952]
+- `application/vnd.oci.image.layer.nondistributable.v1.tar+zstd`: ["Layer", as a tar archive with distribution restrictions](layer.md#zstd-media-types) compressed with [zstd][rfc8478]
+
+## Media Type Conflicts
+
+[Blob](image-layout.md) retrieval methods MAY return media type metadata.
+For example, a HTTP response might return a manifest with the Content-Type header set to `application/vnd.oci.image.manifest.v1+json`.
+Implementations MAY also have expectations for the blob's media type and digest (e.g. from a [descriptor](descriptor.md) referencing the blob).
+
+- Implementations that do not have an expected media type for the blob SHOULD respect the returned media type.
+- Implementations that have an expected media type which matches the returned media type SHOULD respect the matched media type.
+- Implementations that have an expected media type which does not match the returned media type SHOULD:
+ - Respect the expected media type if the blob matches the expected digest.
+ Implementations MAY warn about the media type mismatch.
+ - Return an error if the blob does not match the expected digest (as [recommended for descriptors](descriptor.md#properties)).
+ - Return an error if they do not have an expected digest.
+
+## Compatibility Matrix
+
+The OCI Image Specification strives to be backwards and forwards compatible when possible.
+Breaking compatibility with existing systems creates a burden on users whether they are build systems, distribution systems, container engines, etc.
+This section shows where the OCI Image Specification is compatible with formats external to the OCI Image and different versions of this specification.
+
+### application/vnd.oci.image.index.v1+json
+
+Similar/related schema:
+
+- [application/vnd.docker.distribution.manifest.list.v2+json](https://github.com/distribution/distribution/blob/master/docs/spec/manifest-v2-2.md#manifest-list)
+ - `.annotations`: only present in OCI
+ - `.[]manifests.annotations`: only present in OCI
+ - `.[]manifests.urls`: only present in OCI
+
+### application/vnd.oci.image.manifest.v1+json
+
+Similar/related schema:
+
+- [application/vnd.docker.distribution.manifest.v2+json](https://github.com/distribution/distribution/blob/master/docs/spec/manifest-v2-2.md#image-manifest-field-descriptions)
+ - `.annotations`: only present in OCI
+ - `.config.annotations`: only present in OCI
+ - `.config.urls`: only present in OCI
+ - `.[]layers.annotations`: only present in OCI
+
+### application/vnd.oci.image.layer.v1.tar+gzip
+
+Interchangeable and fully compatible mime-types:
+
+- [application/vnd.docker.image.rootfs.diff.tar.gzip](https://github.com/moby/moby/blob/v20.10.8/image/spec/v1.2.md#creating-an-image-filesystem-changeset)
+
+### application/vnd.oci.image.config.v1+json
+
+Similar/related schema:
+
+- [application/vnd.docker.container.image.v1+json](https://github.com/moby/moby/blob/v20.10.8/image/spec/v1.2.md#image-json-description) (Docker Image Spec v1.2)
+ - `.config.Memory`: only present in Docker, and reserved in OCI
+ - `.config.MemorySwap`: only present in Docker, and reserved in OCI
+ - `.config.CpuShares`: only present in Docker, and reserved in OCI
+ - `.config.Healthcheck`: only present in Docker, and reserved in OCI
+- [Moby/Docker](https://github.com/moby/moby)
+ - `.config.ArgsEscaped`: Windows-specific Moby/Docker extension, deprecated in OCI, available for compatibility with older images.
+
+`.config.StopSignal` and `.config.Labels` are accidentally undocumented in Docker Image Spec v1.2, but these fields are not OCI-specific concepts.
+
+## Relations
+
+The following figure shows how the above media types reference each other:
+
+![media types](img/media-types.png)
+
+[Descriptors](descriptor.md) are used for all references.
+The image-index being a "fat manifest" references a list of image manifests per target platform. An image manifest references exactly one target configuration and possibly many layers.
+
+[rfc1952]: https://tools.ietf.org/html/rfc1952
+[rfc8478]: https://tools.ietf.org/html/rfc8478
diff --git a/project.md b/project.md
new file mode 100644
index 0000000..8b75c8e
--- /dev/null
+++ b/project.md
@@ -0,0 +1,6 @@
+# Project docs
+
+## Release Process
+
+- `git tag` the prior commit (preferably signed tag)
+- Make a release on [github.com/opencontainers/image-spec](https://github.com/opencontainers/image-spec/releases) for the version. Attach the produced docs.
diff --git a/schema/backwards_compatibility_test.go b/schema/backwards_compatibility_test.go
new file mode 100644
index 0000000..ca17bbc
--- /dev/null
+++ b/schema/backwards_compatibility_test.go
@@ -0,0 +1,223 @@
+// 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"
+ "strings"
+ "testing"
+
+ "github.com/opencontainers/go-digest"
+ "github.com/opencontainers/image-spec/schema"
+ v1 "github.com/opencontainers/image-spec/specs-go/v1"
+)
+
+var compatMap = map[string]string{
+ "application/vnd.docker.distribution.manifest.list.v2+json": v1.MediaTypeImageIndex,
+ "application/vnd.docker.distribution.manifest.v2+json": v1.MediaTypeImageManifest,
+ "application/vnd.docker.image.rootfs.diff.tar.gzip": v1.MediaTypeImageLayerGzip,
+ "application/vnd.docker.container.image.v1+json": v1.MediaTypeImageConfig,
+}
+
+// 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, k, v, -1)
+ }
+ return out
+}
+
+func TestBackwardsCompatibilityImageIndex(t *testing.T) {
+ for i, tt := range []struct {
+ imageIndex string
+ digest digest.Digest
+ fail bool
+ }{
+ {
+ digest: "sha256:4ffd0883f25635999f04ea543240a27c9a4341979ff7d46a9774f71512eebb1f",
+ imageIndex: `{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
+ "manifests": [
+ {
+ "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
+ "size": 2094,
+ "digest": "sha256:7820f9a86d4ad15a2c4f0c0e5479298df2aa7c2f6871288e2ef8546f3e7b6783",
+ "platform": {
+ "architecture": "ppc64le",
+ "os": "linux"
+ }
+ },
+ {
+ "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
+ "size": 1922,
+ "digest": "sha256:ae1b0e06e8ade3a11267564a26e750585ba2259c0ecab59ab165ad1af41d1bdd",
+ "platform": {
+ "architecture": "amd64",
+ "os": "linux"
+ }
+ },
+ {
+ "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
+ "size": 2084,
+ "digest": "sha256:e4c0df75810b953d6717b8f8f28298d73870e8aa2a0d5e77b8391f16fdfbbbe2",
+ "platform": {
+ "architecture": "s390x",
+ "os": "linux"
+ }
+ },
+ {
+ "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
+ "size": 2084,
+ "digest": "sha256:07ebe243465ef4a667b78154ae6c3ea46fdb1582936aac3ac899ea311a701b40",
+ "platform": {
+ "architecture": "arm",
+ "os": "linux",
+ "variant": "v7"
+ }
+ },
+ {
+ "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
+ "size": 2090,
+ "digest": "sha256:fb2fc0707b86dafa9959fe3d29e66af8787aee4d9a23581714be65db4265ad8a",
+ "platform": {
+ "architecture": "arm64",
+ "os": "linux",
+ "variant": "v8"
+ }
+ }
+ ]
+}`,
+ fail: false,
+ },
+ } {
+ got := digest.FromString(tt.imageIndex)
+ if tt.digest != got {
+ t.Errorf("test %d: expected digest %s but got %s", i, tt.digest, got)
+ }
+
+ imageIndex := convertFormats(tt.imageIndex)
+ r := strings.NewReader(imageIndex)
+ err := schema.ValidatorMediaTypeImageIndex.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 TestBackwardsCompatibilityManifest(t *testing.T) {
+ for i, tt := range []struct {
+ manifest string
+ digest digest.Digest
+ 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,
+ },
+ } {
+ got := digest.FromString(tt.manifest)
+ 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.ValidatorMediaTypeManifest.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 {
+ config string
+ digest digest.Digest
+ fail bool
+ }{
+ // config pulled from docker hub blob store
+ //
+ // $ TOKEN=$(curl https://auth.docker.io/token\?service\=registry.docker.io\&scope\=repository:library/docker:pull | jq -r .token)
+ // $ CONFIG_DIGEST=$(curl -H "Authorization: Bearer ${TOKEN}" -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' https://index.docker.io/v2/library/docker/manifests/1.12.1 | jq -r .config.digest)
+ // $ curl -LH "Authorization: Bearer ${TOKEN}" https://index.docker.io/v2/library/docker/blobs/${CONFIG_DIGEST}
+ {
+ digest: "sha256:a059ea7356d5b5a9e0f6352bfa463e7bd4721c2ade3ef168603826e0de6fe54b",
+ config: `{"architecture":"amd64","config":{"Hostname":"09713501c176","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.12.1","DOCKER_SHA256=05ceec7fd937e1416e5dce12b0b6e1c655907d349d52574319a1e875077ccb79"],"Cmd":["sh"],"Image":"sha256:32e2e3ccf2a4fbaa75b078bf539cd5ea2e374a4242665a5ec3f3c01e7a3eefb8","Volumes":null,"WorkingDir":"","Entrypoint":["docker-entrypoint.sh"],"OnBuild":[],"Labels":{}},"container":"15a30be053fb3069a7879b4ea537e84689d8e8e8ba94dc4dd499271506803ba1","container_config":{"Hostname":"09713501c176","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.12.1","DOCKER_SHA256=05ceec7fd937e1416e5dce12b0b6e1c655907d349d52574319a1e875077ccb79"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"sh\"]"],"Image":"sha256:32e2e3ccf2a4fbaa75b078bf539cd5ea2e374a4242665a5ec3f3c01e7a3eefb8","Volumes":null,"WorkingDir":"","Entrypoint":["docker-entrypoint.sh"],"OnBuild":[],"Labels":{}},"created":"2016-10-10T23:04:00.821781828Z","docker_version":"1.12.1","history":[{"created":"2016-09-23T16:29:57.276868291Z","created_by":"/bin/sh -c #(nop) ADD file:d6ee3ba7a4d59b161917082cc7242c660c61bb3f3cc1549c7e2dfff2b0de7104 in / "},{"created":"2016-09-23T16:36:54.024611637Z","created_by":"/bin/sh -c apk add --no-cache \t\tca-certificates \t\tcurl \t\topenssl"},{"created":"2016-09-23T16:36:54.365914519Z","created_by":"/bin/sh -c #(nop) ENV DOCKER_BUCKET=get.docker.com","empty_layer":true},{"created":"2016-09-23T16:36:54.662005049Z","created_by":"/bin/sh -c #(nop) ENV DOCKER_VERSION=1.12.1","empty_layer":true},{"created":"2016-09-23T16:36:54.946033025Z","created_by":"/bin/sh -c #(nop) ENV DOCKER_SHA256=05ceec7fd937e1416e5dce12b0b6e1c655907d349d52574319a1e875077ccb79","empty_layer":true},{"created":"2016-09-23T16:36:58.535084011Z","created_by":"/bin/sh -c set -x \t\u0026\u0026 curl -fSL \"https://${DOCKER_BUCKET}/builds/Linux/x86_64/docker-${DOCKER_VERSION}.tgz\" -o docker.tgz \t\u0026\u0026 echo \"${DOCKER_SHA256} *docker.tgz\" | sha256sum -c - \t\u0026\u0026 tar -xzvf docker.tgz \t\u0026\u0026 mv docker/* /usr/local/bin/ \t\u0026\u0026 rmdir docker \t\u0026\u0026 rm docker.tgz \t\u0026\u0026 docker -v"},{"created":"2016-10-10T23:04:00.334158993Z","created_by":"/bin/sh -c #(nop) COPY file:399605dc1850a60a586b5494ab538bad495fd6f94eabca0c5f8a26468ce6030f in /usr/local/bin/ "},{"created":"2016-10-10T23:04:00.577900192Z","created_by":"/bin/sh -c #(nop) ENTRYPOINT [\"docker-entrypoint.sh\"]","empty_layer":true},{"created":"2016-10-10T23:04:00.821781828Z","created_by":"/bin/sh -c #(nop) CMD [\"sh\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:9007f5987db353ec398a223bc5a135c5a9601798ba20a1abba537ea2f8ac765f","sha256:1b06990ff0df8dad281fad7e6e4c5e91f32f8f8c095d6c74cf1e90a6f4407e28","sha256:9d12251ce74aac7619a83641ab72431a8d82e58bcd8a262c2bb0cdb280f1f3b5","sha256:17a7f292c2427adfc75c3a789bab8efec925dc38c5437bf83d2f528013ab80e2"]}}`,
+ fail: false,
+ },
+ {
+ // fedora:23 from docker hub
+ // both Entrypoint and Cmd can be nullable
+ digest: "sha256:a20665eb1fe2912accb3d5dadaed360430df0d1aa46874875886947d61d3d4ee",
+ config: `{"architecture":"amd64","author":"Patrick Uiterwijk \u003cpatrick@puiterwijk.org\u003e","config":{"Hostname":"8dfe43d80430","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"],"Cmd":null,"Image":"sha256:6986ae504bbf843512d680cc959484452034965db15f75ee8bdd1b107f61500b","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"container":"6249cd2c4b1d6b1bf05903364cbcb95781508994d6407c1564d494e748ea1b41","container_config":{"Hostname":"8dfe43d80430","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"],"Cmd":["/bin/sh","-c","#(nop) ADD file:293a6e463aa402bb8f80eb5cfc937f375cedc6843abaeb9eccfe3923bb3fc80b in /"],"Image":"sha256:6986ae504bbf843512d680cc959484452034965db15f75ee8bdd1b107f61500b","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2016-06-10T18:44:31.784795904Z","docker_version":"1.10.3","history":[{"created":"2016-06-10T18:44:03.360264073Z","author":"Patrick Uiterwijk \u003cpatrick@puiterwijk.org\u003e","created_by":"/bin/sh -c #(nop) MAINTAINER Patrick Uiterwijk \u003cpatrick@puiterwijk.org\u003e","empty_layer":true},{"created":"2016-06-10T18:44:31.784795904Z","author":"Patrick Uiterwijk \u003cpatrick@puiterwijk.org\u003e","created_by":"/bin/sh -c #(nop) ADD file:293a6e463aa402bb8f80eb5cfc937f375cedc6843abaeb9eccfe3923bb3fc80b in /"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:d43f38155a799dc53d8fbb9f3bc11f51805f4027cd5c3d10b9823201cd5b9400"]}}`,
+ fail: false,
+ },
+ } {
+ got := digest.FromString(tt.config)
+ if tt.digest != got {
+ t.Errorf("test %d: expected digest %s but got %s", i, tt.digest, got)
+ }
+
+ config := convertFormats(tt.config)
+ r := strings.NewReader(config)
+ err := schema.ValidatorMediaTypeImageConfig.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/schema/config-schema.json b/schema/config-schema.json
new file mode 100644
index 0000000..0c325f1
--- /dev/null
+++ b/schema/config-schema.json
@@ -0,0 +1,155 @@
+{
+ "description": "OpenContainer Config Specification",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "https://opencontainers.org/schema/image/config",
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "author": {
+ "type": "string"
+ },
+ "architecture": {
+ "type": "string"
+ },
+ "variant": {
+ "type": "string"
+ },
+ "os": {
+ "type": "string"
+ },
+ "os.version": {
+ "type": "string"
+ },
+ "os.features": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "config": {
+ "type": "object",
+ "properties": {
+ "User": {
+ "type": "string"
+ },
+ "ExposedPorts": {
+ "$ref": "defs.json#/definitions/mapStringObject"
+ },
+ "Env": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "Entrypoint": {
+ "oneOf": [
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "Cmd": {
+ "oneOf": [
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "Volumes": {
+ "oneOf": [
+ {
+ "$ref": "defs.json#/definitions/mapStringObject"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "WorkingDir": {
+ "type": "string"
+ },
+ "Labels": {
+ "oneOf": [
+ {
+ "$ref": "defs.json#/definitions/mapStringString"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "StopSignal": {
+ "type": "string"
+ },
+ "ArgsEscaped": {
+ "type": "boolean"
+ }
+ }
+ },
+ "rootfs": {
+ "type": "object",
+ "properties": {
+ "diff_ids": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "layers"
+ ]
+ }
+ },
+ "required": [
+ "diff_ids",
+ "type"
+ ]
+ },
+ "history": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "author": {
+ "type": "string"
+ },
+ "created_by": {
+ "type": "string"
+ },
+ "comment": {
+ "type": "string"
+ },
+ "empty_layer": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ },
+ "required": [
+ "architecture",
+ "os",
+ "rootfs"
+ ]
+}
diff --git a/schema/config_test.go b/schema/config_test.go
new file mode 100644
index 0000000..9daa41e
--- /dev/null
+++ b/schema/config_test.go
@@ -0,0 +1,260 @@
+// 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 (
+ "strings"
+ "testing"
+
+ "github.com/opencontainers/image-spec/schema"
+)
+
+func TestConfig(t *testing.T) {
+ for i, tt := range []struct {
+ config string
+ fail bool
+ }{
+ // expected failure: field "os" has numeric value, must be string
+ {
+ config: `
+{
+ "architecture": "amd64",
+ "os": 123,
+ "rootfs": {
+ "diff_ids": [
+ "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
+ ],
+ "type": "layers"
+ }
+}
+`,
+ fail: true,
+ },
+ // expected failure: field "variant" has numeric value, must be string
+ {
+ config: `
+{
+ "architecture": "arm64",
+ "variant": 123,
+ "os": "linux",
+ "rootfs": {
+ "diff_ids": [
+ "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
+ ],
+ "type": "layers"
+ }
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: field "config.User" has numeric value, must be string
+ {
+ config: `
+{
+ "created": "2015-10-31T22:22:56.015925234Z",
+ "author": "Alyssa P. Hacker <alyspdev@example.com>",
+ "architecture": "amd64",
+ "os": "linux",
+ "config": {
+ "User": 1234
+ },
+ "rootfs": {
+ "diff_ids": [
+ "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
+ ],
+ "type": "layers"
+ }
+}
+`,
+ fail: true,
+ },
+
+ // expected failue: history has string value, must be an array
+ {
+ config: `
+{
+ "history": "should be an array",
+ "architecture": "amd64",
+ "os": 123,
+ "rootfs": {
+ "diff_ids": [
+ "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
+ ],
+ "type": "layers"
+ }
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: Env has numeric value, must be a string
+ {
+ config: `
+{
+ "architecture": "amd64",
+ "os": 123,
+ "config": {
+ "Env": [
+ 7353
+ ]
+ },
+ "rootfs": {
+ "diff_ids": [
+ "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
+ ],
+ "type": "layers"
+ }
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: config.Volumes has string array, must be an object (string set)
+ {
+ config: `
+{
+ "architecture": "amd64",
+ "os": 123,
+ "config": {
+ "Volumes": [
+ "/var/job-result-data",
+ "/var/log/my-app-logs"
+ ]
+ },
+ "rootfs": {
+ "diff_ids": [
+ "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
+ ],
+ "type": "layers"
+ }
+}
+`,
+ fail: true,
+ },
+
+ // expected failue: invalid JSON
+ {
+ config: `invalid JSON`,
+ fail: true,
+ },
+
+ // valid config with optional fields
+ {
+ config: `
+{
+ "created": "2015-10-31T22:22:56.015925234Z",
+ "author": "Alyssa P. Hacker <alyspdev@example.com>",
+ "architecture": "arm64",
+ "variant": "v8",
+ "os": "linux",
+ "config": {
+ "User": "1:1",
+ "ExposedPorts": {
+ "8080/tcp": {}
+ },
+ "Env": [
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+ "FOO=docker_is_a_really",
+ "BAR=great_tool_you_know"
+ ],
+ "Entrypoint": [
+ "/bin/sh"
+ ],
+ "Cmd": [
+ "--foreground",
+ "--config",
+ "/etc/my-app.d/default.cfg"
+ ],
+ "Volumes": {
+ "/var/job-result-data": {},
+ "/var/log/my-app-logs": {}
+ },
+ "StopSignal": "SIGKILL",
+ "WorkingDir": "/home/alice",
+ "Labels": {
+ "com.example.project.git.url": "https://example.com/project.git",
+ "com.example.project.git.commit": "45a939b2999782a3f005621a8d0f29aa387e1d6b"
+ }
+ },
+ "rootfs": {
+ "diff_ids": [
+ "sha256:9d3dd9504c685a304985025df4ed0283e47ac9ffa9bd0326fddf4d59513f0827",
+ "sha256:2b689805fbd00b2db1df73fae47562faac1a626d5f61744bfe29946ecff5d73d"
+ ],
+ "type": "layers"
+ },
+ "history": [
+ {
+ "created": "2015-10-31T22:22:54.690851953Z",
+ "created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
+ },
+ {
+ "created": "2015-10-31T22:22:55.613815829Z",
+ "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]",
+ "empty_layer": true
+ }
+ ]
+}
+`,
+ fail: false,
+ },
+
+ // valid config with only required fields
+ {
+ config: `
+{
+ "architecture": "amd64",
+ "os": "linux",
+ "rootfs": {
+ "diff_ids": [
+ "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
+ ],
+ "type": "layers"
+ }
+}
+`,
+ fail: false,
+ },
+ // expected failure: Env is invalid
+ {
+ config: `
+{
+ "architecture": "amd64",
+ "os": "linux",
+ "config": {
+ "Env": [
+ "foo"
+ ]
+ },
+ "rootfs": {
+ "diff_ids": [
+ "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
+ ],
+ "type": "layers"
+ }
+}
+`,
+ fail: true,
+ },
+ } {
+ r := strings.NewReader(tt.config)
+ err := schema.ValidatorMediaTypeImageConfig.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/schema/content-descriptor.json b/schema/content-descriptor.json
new file mode 100644
index 0000000..b2f6dee
--- /dev/null
+++ b/schema/content-descriptor.json
@@ -0,0 +1,41 @@
+{
+ "description": "OpenContainer Content Descriptor Specification",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "https://opencontainers.org/schema/descriptor",
+ "type": "object",
+ "properties": {
+ "mediaType": {
+ "description": "the mediatype of the referenced object",
+ "$ref": "defs-descriptor.json#/definitions/mediaType"
+ },
+ "size": {
+ "description": "the size in bytes of the referenced object",
+ "$ref": "defs.json#/definitions/int64"
+ },
+ "digest": {
+ "description": "the cryptographic checksum digest of the object, in the pattern '<algorithm>:<encoded>'",
+ "$ref": "defs-descriptor.json#/definitions/digest"
+ },
+ "urls": {
+ "description": "a list of urls from which this object may be downloaded",
+ "$ref": "defs-descriptor.json#/definitions/urls"
+ },
+ "data": {
+ "description": "an embedding of the targeted content (base64 encoded)",
+ "$ref": "defs.json#/definitions/base64"
+ },
+ "artifactType": {
+ "description": "the IANA media type of this artifact",
+ "$ref": "defs-descriptor.json#/definitions/mediaType"
+ },
+ "annotations": {
+ "id": "https://opencontainers.org/schema/descriptor/annotations",
+ "$ref": "defs-descriptor.json#/definitions/annotations"
+ }
+ },
+ "required": [
+ "mediaType",
+ "size",
+ "digest"
+ ]
+}
diff --git a/schema/defs-descriptor.json b/schema/defs-descriptor.json
new file mode 100644
index 0000000..dad2b0a
--- /dev/null
+++ b/schema/defs-descriptor.json
@@ -0,0 +1,26 @@
+{
+ "description": "Definitions particular to OpenContainer Descriptor Specification",
+ "definitions": {
+ "mediaType": {
+ "id": "https://opencontainers.org/schema/image/descriptor/mediaType",
+ "type": "string",
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9!#$&-^_.+]{0,126}/[A-Za-z0-9][A-Za-z0-9!#$&-^_.+]{0,126}$"
+ },
+ "digest": {
+ "description": "the cryptographic checksum digest of the object, in the pattern '<algorithm>:<encoded>'",
+ "type": "string",
+ "pattern": "^[a-z0-9]+(?:[+._-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$"
+ },
+ "urls": {
+ "description": "a list of urls from which this object may be downloaded",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "annotations": {
+ "$ref": "defs.json#/definitions/mapStringString"
+ }
+ }
+}
diff --git a/schema/defs.json b/schema/defs.json
new file mode 100644
index 0000000..220f5d4
--- /dev/null
+++ b/schema/defs.json
@@ -0,0 +1,97 @@
+{
+ "description": "Definitions used throughout the OpenContainer Specification",
+ "definitions": {
+ "int8": {
+ "type": "integer",
+ "minimum": -128,
+ "maximum": 127
+ },
+ "int16": {
+ "type": "integer",
+ "minimum": -32768,
+ "maximum": 32767
+ },
+ "int32": {
+ "type": "integer",
+ "minimum": -2147483648,
+ "maximum": 2147483647
+ },
+ "int64": {
+ "type": "integer",
+ "minimum": -9223372036854776000,
+ "maximum": 9223372036854776000
+ },
+ "uint8": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 255
+ },
+ "uint16": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 65535
+ },
+ "uint32": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 4294967295
+ },
+ "uint64": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 18446744073709552000
+ },
+ "uint16Pointer": {
+ "oneOf": [
+ {
+ "$ref": "#/definitions/uint16"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "uint64Pointer": {
+ "oneOf": [
+ {
+ "$ref": "#/definitions/uint64"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "base64": {
+ "type": "string",
+ "media": {
+ "binaryEncoding": "base64"
+ }
+ },
+ "stringPointer": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "mapStringString": {
+ "type": "object",
+ "patternProperties": {
+ ".{1,}": {
+ "type": "string"
+ }
+ }
+ },
+ "mapStringObject": {
+ "type": "object",
+ "patternProperties": {
+ ".{1,}": {
+ "type": "object"
+ }
+ }
+ }
+ }
+}
diff --git a/schema/descriptor_test.go b/schema/descriptor_test.go
new file mode 100644
index 0000000..c3bce28
--- /dev/null
+++ b/schema/descriptor_test.go
@@ -0,0 +1,354 @@
+// 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 (
+ "strings"
+ "testing"
+
+ "github.com/opencontainers/image-spec/schema"
+)
+
+func TestDescriptor(t *testing.T) {
+ for i, tt := range []struct {
+ descriptor string
+ fail bool
+ }{
+ // valid descriptor
+ {
+ descriptor: `
+{
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+}
+`,
+ fail: false,
+ },
+
+ // expected failure: mediaType missing
+ {
+ descriptor: `
+{
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: mediaType does not match pattern (no subtype)
+ {
+ descriptor: `
+{
+ "mediaType": "application",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: mediaType does not match pattern (invalid first type character)
+ {
+ descriptor: `
+{
+ "mediaType": ".foo/bar",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: mediaType does not match pattern (invalid first subtype character)
+ {
+ descriptor: `
+{
+ "mediaType": "foo/.bar",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+}
+`,
+ fail: true,
+ },
+
+ // expected success: mediaType has type and subtype as long as possible
+ {
+ descriptor: `
+{
+ "mediaType": "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567/1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+}
+`,
+ fail: false,
+ },
+
+ // expected failure: mediaType does not match pattern (type too long)
+ {
+ descriptor: `
+{
+ "mediaType": "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678/bar",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: mediaType does not match pattern (subtype too long)
+ {
+ descriptor: `
+{
+ "mediaType": "foo/12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: size missing
+ {
+ descriptor: `
+{
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: size is a string, expected integer
+ {
+ descriptor: `
+{
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": "7682",
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: digest missing
+ {
+ descriptor: `
+{
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: digest does not match pattern (no algorithm)
+ {
+ descriptor: `
+{
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682,
+ "digest": ":5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: digest does not match pattern (no hash)
+ {
+ descriptor: `
+{
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682,
+ "digest": "sha256"
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: digest does not match pattern (invalid aglorithm characters)
+ {
+ descriptor: `
+{
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682,
+ "digest": "SHA256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: digest does not match pattern (characters needs to be lower for sha256)
+ {
+ descriptor: `
+{
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682,
+ "digest": "sha256:5B0BCABD1ED22E9FB1310CF6C2DEC7CDEF19F0AD69EFA1F392E94A4333501270"
+}
+`,
+ fail: true,
+ },
+
+ // expected success: valid URL entry
+ {
+ descriptor: `
+{
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
+ "urls": [
+ "https://example.com/foo"
+ ]
+}
+`,
+ fail: false,
+ },
+
+ // expected failure: urls does not match format (invalide url characters)
+ {
+ descriptor: `
+{
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
+ "urls": [
+ "value"
+ ]
+}
+`,
+ fail: true,
+ },
+
+ // expected success: artifactType is present and an IANA compliant value
+ {
+ descriptor: `
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "artifactType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+ }
+ `,
+ fail: false,
+ },
+
+ // expected failure: artifactType does not match pattern (invalid first subtype character)
+ {
+ descriptor: `
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "artifactType": "foo/.bar",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+ }
+ `,
+ fail: true,
+ },
+
+ // expected success: data field is present and has base64 content
+ {
+ descriptor: `
+ {
+ "mediaType": "text/plain",
+ "size": 34,
+ "data": "aHR0cHM6Ly9naXRodWIuY29tL29wZW5jb250YWluZXJzCg==",
+ "digest": "sha256:2690af59371e9eca9453dc29882643f46e5ca47ec2862bd517b5e17351325153"
+ }
+ `,
+ fail: false,
+ },
+
+ {
+ descriptor: `{
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "size": 1470,
+ "digest": "sha256+b64:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+}`,
+ },
+ {
+ descriptor: `{
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "size": 1470,
+ "digest": "sha256+b64:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ }`,
+ },
+ {
+ descriptor: `{
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "size": 1470,
+ "digest": "sha256+foo-bar:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ }`,
+ },
+ {
+ descriptor: `
+ {
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "size": 1470,
+ "digest": "sha256.foo-bar:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ }`,
+ },
+ {
+ descriptor: `{
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "size": 1470,
+ "digest": "multihash+base58:QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8"
+ }`,
+ },
+ {
+ // fail: repeated separators in algorithm
+ descriptor: `{
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "size": 1470,
+ "digest": "sha256+foo+-b:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ }`,
+ fail: true,
+ },
+ {
+ descriptor: `{
+ "digest": "sha256+b64u:LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564",
+ "size": 1000000,
+ "mediaType": "application/vnd.oci.image.config.v1+json"
+ }`,
+ },
+ {
+ // test for those who cannot use modulo arithmetic to recover padding.
+ descriptor: `{
+ "digest": "sha256+b64u.unknownlength:LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564=",
+ "size": 1000000,
+ "mediaType": "application/vnd.oci.image.config.v1+json"
+ }`,
+ },
+ {
+ descriptor: `
+ {
+ "mediaType": "text/plain",
+ "size": 34,
+ "data": "aHR0cHM6Ly9naXRodWIuY29tL29wZW5jb250YWluZXJzCg",
+ "digest": "sha256:2690af59371e9eca9453dc29882643f46e5ca47ec2862bd517b5e17351325153"
+ }
+ `,
+ fail: true,
+ },
+ } {
+ r := strings.NewReader(tt.descriptor)
+ err := schema.ValidatorMediaTypeDescriptor.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/schema/doc.go b/schema/doc.go
new file mode 100644
index 0000000..5ea5914
--- /dev/null
+++ b/schema/doc.go
@@ -0,0 +1,16 @@
+// 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 defines the OCI image media types, schema definitions and validation functions.
+package schema
diff --git a/schema/error.go b/schema/error.go
new file mode 100644
index 0000000..baf8751
--- /dev/null
+++ b/schema/error.go
@@ -0,0 +1,57 @@
+// 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
+
+import (
+ "bufio"
+ "encoding/json"
+ "io"
+)
+
+// A SyntaxError is a description of a JSON syntax error
+// including line, column and offset in the JSON file.
+type SyntaxError struct {
+ msg string
+ Line, Col int
+ Offset int64
+}
+
+func (e *SyntaxError) Error() string { return e.msg }
+
+// WrapSyntaxError checks whether the given error is a *json.SyntaxError
+// and converts it into a *schema.SyntaxError containing line/col information using the given reader.
+// If the given error is not a *json.SyntaxError it is returned unchanged.
+func WrapSyntaxError(r io.Reader, err error) error {
+ if serr, ok := err.(*json.SyntaxError); ok {
+ buf := bufio.NewReader(r)
+ line := 0
+ col := 0
+ for i := int64(0); i < serr.Offset; i++ {
+ b, berr := buf.ReadByte()
+ if berr != nil {
+ break
+ }
+ if b == '\n' {
+ line++
+ col = 1
+ } else {
+ col++
+ }
+ }
+ return &SyntaxError{serr.Error(), line, col, serr.Offset}
+ }
+
+ return err
+}
diff --git a/schema/image-index-schema.json b/schema/image-index-schema.json
new file mode 100644
index 0000000..2e5dbf5
--- /dev/null
+++ b/schema/image-index-schema.json
@@ -0,0 +1,100 @@
+{
+ "description": "OpenContainer Image Index Specification",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "https://opencontainers.org/schema/image/index",
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "description": "This field specifies the image index schema version as an integer",
+ "id": "https://opencontainers.org/schema/image/index/schemaVersion",
+ "type": "integer",
+ "minimum": 2,
+ "maximum": 2
+ },
+ "mediaType": {
+ "description": "the mediatype of the referenced object",
+ "$ref": "defs-descriptor.json#/definitions/mediaType"
+ },
+ "artifactType": {
+ "description": "the artifact mediatype of the referenced object",
+ "$ref": "defs-descriptor.json#/definitions/mediaType"
+ },
+ "subject": {
+ "$ref": "content-descriptor.json"
+ },
+ "manifests": {
+ "type": "array",
+ "items": {
+ "id": "https://opencontainers.org/schema/image/manifestDescriptor",
+ "type": "object",
+ "required": [
+ "mediaType",
+ "size",
+ "digest"
+ ],
+ "properties": {
+ "mediaType": {
+ "description": "the mediatype of the referenced object",
+ "$ref": "defs-descriptor.json#/definitions/mediaType"
+ },
+ "size": {
+ "description": "the size in bytes of the referenced object",
+ "$ref": "defs.json#/definitions/int64"
+ },
+ "digest": {
+ "description": "the cryptographic checksum digest of the object, in the pattern '<algorithm>:<encoded>'",
+ "$ref": "defs-descriptor.json#/definitions/digest"
+ },
+ "urls": {
+ "description": "a list of urls from which this object may be downloaded",
+ "$ref": "defs-descriptor.json#/definitions/urls"
+ },
+ "platform": {
+ "id": "https://opencontainers.org/schema/image/platform",
+ "type": "object",
+ "required": [
+ "architecture",
+ "os"
+ ],
+ "properties": {
+ "architecture": {
+ "id": "https://opencontainers.org/schema/image/platform/architecture",
+ "type": "string"
+ },
+ "os": {
+ "id": "https://opencontainers.org/schema/image/platform/os",
+ "type": "string"
+ },
+ "os.version": {
+ "id": "https://opencontainers.org/schema/image/platform/os.version",
+ "type": "string"
+ },
+ "os.features": {
+ "id": "https://opencontainers.org/schema/image/platform/os.features",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "variant": {
+ "type": "string"
+ }
+ }
+ },
+ "annotations": {
+ "id": "https://opencontainers.org/schema/image/descriptor/annotations",
+ "$ref": "defs-descriptor.json#/definitions/annotations"
+ }
+ }
+ }
+ },
+ "annotations": {
+ "id": "https://opencontainers.org/schema/image/index/annotations",
+ "$ref": "defs-descriptor.json#/definitions/annotations"
+ }
+ },
+ "required": [
+ "schemaVersion",
+ "manifests"
+ ]
+}
diff --git a/schema/image-layout-schema.json b/schema/image-layout-schema.json
new file mode 100644
index 0000000..874d217
--- /dev/null
+++ b/schema/image-layout-schema.json
@@ -0,0 +1,18 @@
+{
+ "description": "OpenContainer Image Layout Schema",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "https://opencontainers.org/schema/image/layout",
+ "type": "object",
+ "properties": {
+ "imageLayoutVersion": {
+ "description": "version of the OCI Image Layout (in the oci-layout file)",
+ "type": "string",
+ "enum": [
+ "1.0.0"
+ ]
+ }
+ },
+ "required": [
+ "imageLayoutVersion"
+ ]
+}
diff --git a/schema/image-manifest-schema.json b/schema/image-manifest-schema.json
new file mode 100644
index 0000000..9bce5a1
--- /dev/null
+++ b/schema/image-manifest-schema.json
@@ -0,0 +1,45 @@
+{
+ "description": "OpenContainer Image Manifest Specification",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "id": "https://opencontainers.org/schema/image/manifest",
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "description": "This field specifies the image manifest schema version as an integer",
+ "id": "https://opencontainers.org/schema/image/manifest/schemaVersion",
+ "type": "integer",
+ "minimum": 2,
+ "maximum": 2
+ },
+ "mediaType": {
+ "description": "the mediatype of the referenced object",
+ "$ref": "defs-descriptor.json#/definitions/mediaType"
+ },
+ "artifactType": {
+ "description": "the artifact mediatype of the referenced object",
+ "$ref": "defs-descriptor.json#/definitions/mediaType"
+ },
+ "config": {
+ "$ref": "content-descriptor.json"
+ },
+ "subject": {
+ "$ref": "content-descriptor.json"
+ },
+ "layers": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "content-descriptor.json"
+ }
+ },
+ "annotations": {
+ "id": "https://opencontainers.org/schema/image/manifest/annotations",
+ "$ref": "defs-descriptor.json#/definitions/annotations"
+ }
+ },
+ "required": [
+ "schemaVersion",
+ "config",
+ "layers"
+ ]
+}
diff --git a/schema/imageindex_test.go b/schema/imageindex_test.go
new file mode 100644
index 0000000..7b04935
--- /dev/null
+++ b/schema/imageindex_test.go
@@ -0,0 +1,316 @@
+// 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 (
+ "strings"
+ "testing"
+
+ "github.com/opencontainers/image-spec/schema"
+)
+
+func TestImageIndex(t *testing.T) {
+ for i, tt := range []struct {
+ imageIndex string
+ fail bool
+ }{
+ // expected failure: mediaType does not match pattern
+ {
+ imageIndex: `
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "manifests": [
+ {
+ "mediaType": "invalid",
+ "size": 7143,
+ "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
+ "platform": {
+ "architecture": "ppc64le",
+ "os": "linux"
+ }
+ }
+ ]
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: manifest.size is string, expected integer
+ {
+ imageIndex: `
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "manifests": [
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": "7682",
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
+ "platform": {
+ "architecture": "amd64",
+ "os": "linux"
+ }
+ }
+ ]
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: manifest.digest is missing, expected required
+ {
+ imageIndex: `
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "manifests": [
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682,
+ "platform": {
+ "architecture": "amd64",
+ "os": "linux"
+ }
+ }
+ ]
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: in the optional field platform platform.architecture is missing, expected required
+ {
+ imageIndex: `
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "manifests": [
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
+ "platform": {
+ "os": "linux"
+ }
+ }
+ ]
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: invalid referenced manifest media type
+ {
+ imageIndex: `
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "manifests": [
+ {
+ "mediaType": "invalid",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
+ "platform": {
+ "architecture": "amd64",
+ "os": "linux"
+ }
+ }
+ ]
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: empty referenced manifest media type
+ {
+ imageIndex: `
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "manifests": [
+ {
+ "mediaType": "",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
+ "platform": {
+ "architecture": "amd64",
+ "os": "linux"
+ }
+ }
+ ]
+}
+`,
+ fail: true,
+ },
+
+ // valid image index, with optional fields
+ {
+ imageIndex: `
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "manifests": [
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7143,
+ "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
+ "platform": {
+ "architecture": "ppc64le",
+ "os": "linux"
+ }
+ },
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
+ "platform": {
+ "architecture": "amd64",
+ "os": "linux"
+ }
+ }
+ ],
+ "annotations": {
+ "com.example.key1": "value1",
+ "com.example.key2": "value2"
+ }
+}
+`,
+ fail: false,
+ },
+
+ // valid image index, with required fields only
+ {
+ imageIndex: `
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "manifests": [
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7143,
+ "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"
+ }
+ ]
+}
+`,
+ fail: false,
+ },
+
+ // valid image index, with customized media type of referenced manifest
+ {
+ imageIndex: `
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "manifests": [
+ {
+ "mediaType": "application/customized.manifest+json",
+ "size": 7143,
+ "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f",
+ "platform": {
+ "architecture": "ppc64le",
+ "os": "linux"
+ }
+ }
+ ]
+}
+`,
+ fail: false,
+ },
+
+ // valid image index with artifactType and manifests
+ {
+ imageIndex: `
+{
+ "schemaVersion": 2,
+ "mediaType" : "application/vnd.oci.image.index.v1+json",
+ "artifactType": "application/vnd.example+type",
+ "manifests": [
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7143,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270"
+ },
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "artifactType": "application/vnd.example1+type",
+ "size": 506,
+ "digest": "sha256:99953afc4b90c7d78079d189ae10da0a1002e6be5e9e8dedaf9f7f29def42111"
+ }
+ ]
+}
+`,
+ fail: false,
+ },
+
+ // valid image index with a subject field
+ {
+ imageIndex: `
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "manifests": [
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
+ "platform": {
+ "architecture": "amd64",
+ "os": "linux"
+ }
+ }
+ ],
+ "subject" : {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 1234,
+ "digest": "sha256:220a60ecd4a3c32c282622a625a54db9ba0ff55b5ba9c29c7064a2bc358b6a3e"
+ }
+}
+`,
+ fail: false,
+ },
+
+ // expected failure, invalid subject field
+ {
+ imageIndex: `
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "manifests": [
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 7682,
+ "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270",
+ "platform": {
+ "architecture": "amd64",
+ "os": "linux"
+ }
+ }
+ ],
+ "subject" : "nope"
+}
+`,
+ fail: true,
+ },
+ } {
+ r := strings.NewReader(tt.imageIndex)
+ err := schema.ValidatorMediaTypeImageIndex.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/schema/imagelayout_test.go b/schema/imagelayout_test.go
new file mode 100644
index 0000000..144ff08
--- /dev/null
+++ b/schema/imagelayout_test.go
@@ -0,0 +1,56 @@
+// 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 (
+ "strings"
+ "testing"
+
+ "github.com/opencontainers/image-spec/schema"
+)
+
+func TestImageLayout(t *testing.T) {
+ for i, tt := range []struct {
+ imageLayout string
+ fail bool
+ }{
+ // expected faulure: imageLayoutVersion does not match pattern
+ {
+ imageLayout: `
+{
+ "imageLayoutVersion": 1.0.0
+}
+`,
+ fail: true,
+ },
+
+ // validate layout
+ {
+ imageLayout: `
+{
+ "imageLayoutVersion": "1.0.0"
+}
+`,
+ fail: false,
+ },
+ } {
+ r := strings.NewReader(tt.imageLayout)
+ err := schema.ValidatorMediaTypeLayoutHeader.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/schema/loader.go b/schema/loader.go
new file mode 100644
index 0000000..d773758
--- /dev/null
+++ b/schema/loader.go
@@ -0,0 +1,125 @@
+// Copyright 2018 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
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+
+ "github.com/xeipuuv/gojsonreference"
+ "github.com/xeipuuv/gojsonschema"
+)
+
+// fsLoaderFactory implements gojsonschema.JSONLoaderFactory by reading files under the specified namespaces from the root of fs.
+type fsLoaderFactory struct {
+ namespaces []string
+ fs http.FileSystem
+}
+
+// newFSLoaderFactory returns a fsLoaderFactory reading files under the specified namespaces from the root of fs.
+func newFSLoaderFactory(namespaces []string, fs http.FileSystem) *fsLoaderFactory {
+ return &fsLoaderFactory{
+ namespaces: namespaces,
+ fs: fs,
+ }
+}
+
+func (factory *fsLoaderFactory) New(source string) gojsonschema.JSONLoader {
+ return &fsLoader{
+ factory: factory,
+ source: source,
+ }
+}
+
+// refContents returns the contents of ref, if available in fsLoaderFactory.
+func (factory *fsLoaderFactory) refContents(ref gojsonreference.JsonReference) ([]byte, error) {
+ refStr := ref.String()
+ path := ""
+ for _, ns := range factory.namespaces {
+ if strings.HasPrefix(refStr, ns) {
+ path = "/" + strings.TrimPrefix(refStr, ns)
+ break
+ }
+ }
+ if path == "" {
+ return nil, fmt.Errorf("schema reference %#v unexpectedly not available in fsLoaderFactory with namespaces %#v", path, factory.namespaces)
+ }
+
+ f, err := factory.fs.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ return io.ReadAll(f)
+}
+
+// fsLoader implements gojsonschema.JSONLoader by reading the document named by source from a fsLoaderFactory.
+type fsLoader struct {
+ factory *fsLoaderFactory
+ source string
+}
+
+// JsonSource implements gojsonschema.JSONLoader.JsonSource. The "Json" capitalization needs to be maintained to conform to the interface.
+func (l *fsLoader) JsonSource() interface{} { // revive:disable-line:var-naming
+ return l.source
+}
+
+func (l *fsLoader) LoadJSON() (interface{}, error) {
+ // Based on gojsonschema.jsonReferenceLoader.LoadJSON.
+ reference, err := gojsonreference.NewJsonReference(l.source)
+ if err != nil {
+ return nil, err
+ }
+
+ refToURL := reference
+ refToURL.GetUrl().Fragment = ""
+
+ body, err := l.factory.refContents(refToURL)
+ if err != nil {
+ return nil, err
+ }
+
+ return decodeJSONUsingNumber(bytes.NewReader(body))
+}
+
+// decodeJSONUsingNumber returns JSON parsed from an io.Reader
+func decodeJSONUsingNumber(r io.Reader) (interface{}, error) {
+ // Copied from gojsonschema.
+ var document interface{}
+
+ decoder := json.NewDecoder(r)
+ decoder.UseNumber()
+
+ err := decoder.Decode(&document)
+ if err != nil {
+ return nil, err
+ }
+
+ return document, nil
+}
+
+// JsonReference implements gojsonschema.JSONLoader.JsonReference. The "Json" capitalization needs to be maintained to conform to the interface.
+func (l *fsLoader) JsonReference() (gojsonreference.JsonReference, error) { // revive:disable-line:var-naming
+ return gojsonreference.NewJsonReference(l.JsonSource().(string))
+}
+
+func (l *fsLoader) LoaderFactory() gojsonschema.JSONLoaderFactory {
+ return l.factory
+}
diff --git a/schema/manifest_test.go b/schema/manifest_test.go
new file mode 100644
index 0000000..732b156
--- /dev/null
+++ b/schema/manifest_test.go
@@ -0,0 +1,347 @@
+// 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 (
+ "strings"
+ "testing"
+
+ "github.com/opencontainers/image-spec/schema"
+)
+
+func TestManifest(t *testing.T) {
+ for i, tt := range []struct {
+ manifest string
+ fail bool
+ }{
+ // expected failure: mediaType does not match pattern
+ {
+ manifest: `
+{
+ "schemaVersion": 2,
+ "mediaType" : "application/vnd.oci.image.manifest.v1+json",
+ "config": {
+ "mediaType": "invalid",
+ "size": 1470,
+ "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "size": 148,
+ "digest": "sha256:c57089565e894899735d458f0fd4bb17a0f1e0df8d72da392b85c9b35ee777cd"
+ }
+ ]
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: config.size is a string, expected integer
+ {
+ manifest: `
+{
+ "schemaVersion": 2,
+ "mediaType" : "application/vnd.oci.image.manifest.v1+json",
+ "config": {
+ "config": {
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "size": "1470",
+ "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "size": 148,
+ "digest": "sha256:c57089565e894899735d458f0fd4bb17a0f1e0df8d72da392b85c9b35ee777cd"
+ }
+ ]
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: layers.size is string, expected integer
+ {
+ manifest: `
+{
+ "schemaVersion": 2,
+ "mediaType" : "application/vnd.oci.image.manifest.v1+json",
+ "config": {
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "size": 1470,
+ "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "size": "675598",
+ "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ }
+ ]
+}
+`,
+ fail: true,
+ },
+
+ // valid manifest with optional fields
+ {
+ manifest: `
+{
+ "schemaVersion": 2,
+ "mediaType" : "application/vnd.oci.image.manifest.v1+json",
+ "config": {
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "size": 1470,
+ "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "size": 675598,
+ "digest": "sha256:9d3dd9504c685a304985025df4ed0283e47ac9ffa9bd0326fddf4d59513f0827"
+ },
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "size": 156,
+ "digest": "sha256:2b689805fbd00b2db1df73fae47562faac1a626d5f61744bfe29946ecff5d73d"
+ },
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "size": 148,
+ "digest": "sha256:c57089565e894899735d458f0fd4bb17a0f1e0df8d72da392b85c9b35ee777cd"
+ }
+ ],
+ "annotations": {
+ "key1": "value1",
+ "key2": "value2"
+ }
+}
+`,
+ fail: false,
+ },
+
+ // valid manifest with only required fields
+ {
+ manifest: `
+{
+ "schemaVersion": 2,
+ "mediaType" : "application/vnd.oci.image.manifest.v1+json",
+ "config": {
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "size": 1470,
+ "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "size": 675598,
+ "digest": "sha256:9d3dd9504c685a304985025df4ed0283e47ac9ffa9bd0326fddf4d59513f0827"
+ },
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "size": 156,
+ "digest": "sha256:2b689805fbd00b2db1df73fae47562faac1a626d5f61744bfe29946ecff5d73d"
+ },
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "size": 148,
+ "digest": "sha256:c57089565e894899735d458f0fd4bb17a0f1e0df8d72da392b85c9b35ee777cd"
+ }
+ ]
+}
+`,
+ fail: false,
+ },
+
+ // expected failure: empty layer, expected at least one
+ {
+ manifest: `
+{
+ "schemaVersion": 2,
+ "mediaType" : "application/vnd.oci.image.manifest.v1+json",
+ "config": {
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "size": 1470,
+ "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ },
+ "layers": []
+}
+`,
+ fail: true,
+ },
+
+ // expected pass: test bounds of algorithm field in digest.
+ {
+ manifest: `
+{
+ "schemaVersion": 2,
+ "mediaType" : "application/vnd.oci.image.manifest.v1+json",
+ "config": {
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "size": 1470,
+ "digest": "sha256+b64:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "size": 1470,
+ "digest": "sha256+foo-bar:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ },
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "size": 1470,
+ "digest": "sha256.foo-bar:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ },
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "size": 1470,
+ "digest": "multihash+base58:QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8"
+ }
+ ]
+}
+`,
+ },
+
+ // expected success: subject field with a valid descriptor
+ {
+ manifest: `
+{
+ "schemaVersion": 2,
+ "mediaType" : "application/vnd.oci.image.manifest.v1+json",
+ "config": {
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "size": 1470,
+ "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "size": 1470,
+ "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ }
+ ],
+ "subject" : {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "size": 1234,
+ "digest": "sha256:220a60ecd4a3c32c282622a625a54db9ba0ff55b5ba9c29c7064a2bc358b6a3e"
+ }
+}
+`,
+ fail: false,
+ },
+
+ // expected failure: subject field with invalid value (something that is not a descriptor)
+ {
+ manifest: `
+{
+ "schemaVersion": 2,
+ "mediaType" : "application/vnd.oci.image.manifest.v1+json",
+ "config": {
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "size": 1470,
+ "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "size": 1470,
+ "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ }
+ ],
+ "subject" : ".nope"
+}
+`,
+ fail: true,
+ },
+
+ // expected failure: push bounds of algorithm field in digest too far.
+ {
+ manifest: `
+{
+ "schemaVersion": 2,
+ "mediaType" : "application/vnd.oci.image.manifest.v1+json",
+ "config": {
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "size": 1470,
+ "digest": "sha256+b64:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
+ "size": 1470,
+ "digest": "sha256+foo+-b:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ }
+ ]
+}
+`,
+ fail: true,
+ },
+
+ // valid manifest for an artifact with a dedicated config
+ {
+ manifest: `
+{
+ "schemaVersion": 2,
+ "mediaType" : "application/vnd.oci.image.manifest.v1+json",
+ "config": {
+ "mediaType": "application/vnd.example.config+json",
+ "size": 1470,
+ "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b"
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.example.data+type",
+ "size": 675598,
+ "digest": "sha256:9d3dd9504c685a304985025df4ed0283e47ac9ffa9bd0326fddf4d59513f0827"
+ }
+ ]
+}
+`,
+ fail: false,
+ },
+
+ // valid manifest for an artifact using the empty config and artifactType
+ {
+ manifest: `
+{
+ "schemaVersion": 2,
+ "mediaType" : "application/vnd.oci.image.manifest.v1+json",
+ "artifactType": "application/vnd.example+type",
+ "config": {
+ "mediaType": "application/vnd.oci.empty.v1+json",
+ "size": 2,
+ "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.example+type",
+ "size": 675598,
+ "digest": "sha256:9d3dd9504c685a304985025df4ed0283e47ac9ffa9bd0326fddf4d59513f0827"
+ }
+ ]
+}
+`,
+ fail: false,
+ },
+ } {
+ r := strings.NewReader(tt.manifest)
+ err := schema.ValidatorMediaTypeManifest.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/schema/schema.go b/schema/schema.go
new file mode 100644
index 0000000..7a338d8
--- /dev/null
+++ b/schema/schema.go
@@ -0,0 +1,78 @@
+// 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
+
+import (
+ "embed"
+ "net/http"
+
+ v1 "github.com/opencontainers/image-spec/specs-go/v1"
+)
+
+// Media types for the OCI image formats
+const (
+ ValidatorMediaTypeDescriptor Validator = v1.MediaTypeDescriptor
+ ValidatorMediaTypeLayoutHeader Validator = v1.MediaTypeLayoutHeader
+ ValidatorMediaTypeManifest Validator = v1.MediaTypeImageManifest
+ ValidatorMediaTypeImageIndex Validator = v1.MediaTypeImageIndex
+ ValidatorMediaTypeImageConfig Validator = v1.MediaTypeImageConfig
+ ValidatorMediaTypeImageLayer unimplemented = v1.MediaTypeImageLayer
+)
+
+var (
+ // fs stores the embedded http.FileSystem
+ // having the OCI JSON schema files in root "/".
+ //go:embed *.json
+ fs embed.FS
+
+ // schemaNamespaces is a set of URI prefixes which are treated as containing the schema files of fs.
+ // This is necessary because *.json schema files in this directory use "id" and "$ref" attributes which evaluate to such URIs, e.g.
+ // ./image-manifest-schema.json URI contains
+ // "id": "https://opencontainers.org/schema/image/manifest",
+ // and
+ // "$ref": "content-descriptor.json"
+ // which evaluates as a link to https://opencontainers.org/schema/image/content-descriptor.json .
+ //
+ // To support such links without accessing the network (and trying to load content which is not hosted at these URIs),
+ // fsLoaderFactory accepts any URI starting with one of the schemaNamespaces below,
+ // and uses _escFS to load them from the root of its in-memory filesystem tree.
+ //
+ // (Note that this must contain subdirectories before its parent directories for fsLoaderFactory.refContents to work.)
+ schemaNamespaces = []string{
+ "https://opencontainers.org/schema/image/descriptor/",
+ "https://opencontainers.org/schema/image/index/",
+ "https://opencontainers.org/schema/image/manifest/",
+ "https://opencontainers.org/schema/image/",
+ "https://opencontainers.org/schema/descriptor/",
+ "https://opencontainers.org/schema/",
+ }
+
+ // specs maps OCI schema media types to schema URIs.
+ // These URIs are expected to be used only by fsLoaderFactory (which trims schemaNamespaces defined above)
+ // and should never cause a network access.
+ specs = map[Validator]string{
+ ValidatorMediaTypeDescriptor: "https://opencontainers.org/schema/content-descriptor.json",
+ ValidatorMediaTypeLayoutHeader: "https://opencontainers.org/schema/image/image-layout-schema.json",
+ ValidatorMediaTypeManifest: "https://opencontainers.org/schema/image/image-manifest-schema.json",
+ ValidatorMediaTypeImageIndex: "https://opencontainers.org/schema/image/image-index-schema.json",
+ ValidatorMediaTypeImageConfig: "https://opencontainers.org/schema/image/config-schema.json",
+ }
+)
+
+// FileSystem returns an in-memory filesystem including the schema files.
+// The schema files are located at the root directory.
+func FileSystem() http.FileSystem {
+ return http.FS(fs)
+}
diff --git a/schema/spec_test.go b/schema/spec_test.go
new file mode 100644
index 0000000..e8dde99
--- /dev/null
+++ b/schema/spec_test.go
@@ -0,0 +1,216 @@
+// 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 (
+ "bytes"
+ "fmt"
+ "io"
+ "net/url"
+ "os"
+ "path/filepath"
+ "reflect"
+ "strings"
+ "testing"
+
+ "github.com/opencontainers/image-spec/schema"
+ "github.com/pkg/errors"
+ "github.com/russross/blackfriday"
+)
+
+var (
+ errFormatInvalid = errors.New("format: invalid")
+)
+
+func TestValidateDescriptor(t *testing.T) {
+ validate(t, "../descriptor.md")
+}
+
+func TestValidateManifest(t *testing.T) {
+ validate(t, "../manifest.md")
+}
+
+func TestValidateImageIndex(t *testing.T) {
+ validate(t, "../image-index.md")
+}
+
+func TestValidateImageLayout(t *testing.T) {
+ validate(t, "../image-layout.md")
+}
+
+func TestValidateConfig(t *testing.T) {
+ validate(t, "../config.md")
+}
+
+func TestSchemaFS(t *testing.T) {
+ expectedSchemaFileNames, err := filepath.Glob("*.json")
+ if err != nil {
+ t.Error(err)
+ }
+
+ dir, err := schema.FileSystem().Open("/")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ files, err := dir.Readdir(-1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var schemaFileNames []string
+ for _, f := range files {
+ schemaFileNames = append(schemaFileNames, f.Name())
+ }
+
+ if !reflect.DeepEqual(schemaFileNames, expectedSchemaFileNames) {
+ t.Fatalf("got %v, expected %v", schemaFileNames, expectedSchemaFileNames)
+ }
+}
+
+// TODO(sur): include examples from all specification files
+func validate(t *testing.T, name string) {
+ m, err := os.Open(name)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer m.Close()
+
+ examples, err := extractExamples(m)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, example := range examples {
+ if example.Err == errFormatInvalid && example.Mediatype == "" { // ignore
+ continue
+ }
+
+ if example.Err != nil {
+ printFields(t, "error", example.Mediatype, example.Title, example.Err)
+ t.Error(err)
+ continue
+ }
+
+ err = schema.Validator(example.Mediatype).Validate(strings.NewReader(example.Body))
+ if err == nil {
+ printFields(t, "ok", example.Mediatype, example.Title)
+ t.Log(example.Body, "---")
+ continue
+ }
+
+ var errs []error
+ if verr, ok := errors.Cause(err).(schema.ValidationError); ok {
+ errs = verr.Errs
+ } else {
+ printFields(t, "error", example.Mediatype, example.Title, err)
+ t.Error(err)
+ t.Log(example.Body, "---")
+ continue
+ }
+
+ for _, err := range errs {
+ printFields(t, "invalid", example.Mediatype, example.Title)
+ t.Error(err)
+ fmt.Println(example.Body, "---")
+ continue
+ }
+ }
+}
+
+// renderer allows one to incercept fenced blocks in markdown documents.
+type renderer struct {
+ blackfriday.Renderer
+ fn func(text []byte, lang string)
+}
+
+func (r *renderer) BlockCode(out *bytes.Buffer, text []byte, lang string) {
+ r.fn(text, lang)
+ r.Renderer.BlockCode(out, text, lang)
+}
+
+type example struct {
+ Lang string // gets raw "lang" field
+ Title string
+ Mediatype string
+ Body string
+ Err error
+
+ // TODO(stevvooe): Figure out how to keep track of revision, file, line so
+ // that we can trace back verification output.
+}
+
+// parseExample treats the field as a syntax,attribute tuple separated by a comma.
+// Attributes are encoded as a url values.
+//
+// An example of this is `json,title=Foo%20Bar&mediatype=application/json. We
+// get that the "lang" is json, the title is "Foo Bar" and the mediatype is
+// "application/json".
+//
+// This preserves syntax highlighting and lets us tag examples with further
+// metadata.
+func parseExample(lang, body string) (e example) {
+ e.Lang = lang
+ e.Body = body
+
+ parts := strings.SplitN(lang, ",", 2)
+ if len(parts) < 2 {
+ e.Err = errFormatInvalid
+ return
+ }
+
+ m, err := url.ParseQuery(parts[1])
+ if err != nil {
+ e.Err = err
+ return
+ }
+
+ e.Mediatype = m.Get("mediatype")
+ e.Title = m.Get("title")
+ return
+}
+
+func extractExamples(rd io.Reader) ([]example, error) {
+ p, err := io.ReadAll(rd)
+ if err != nil {
+ return nil, err
+ }
+
+ var examples []example
+ renderer := &renderer{
+ Renderer: blackfriday.HtmlRenderer(0, "test test", ""),
+ fn: func(text []byte, lang string) {
+ examples = append(examples, parseExample(lang, string(text)))
+ },
+ }
+
+ // just pass over the markdown and ignore the rendered result. We just want
+ // the side-effect of calling back for each code block.
+ // TODO(stevvooe): Consider just parsing these with a scanner. It will be
+ // faster and we can retain file, line no.
+ blackfriday.MarkdownOptions(p, renderer, blackfriday.Options{
+ Extensions: blackfriday.EXTENSION_FENCED_CODE,
+ })
+
+ return examples, nil
+}
+
+// printFields prints each value tab separated.
+func printFields(t *testing.T, vs ...interface{}) {
+ var ss []string
+ for _, f := range vs {
+ ss = append(ss, fmt.Sprint(f))
+ }
+ t.Log(strings.Join(ss, "\t"))
+}
diff --git a/schema/validator.go b/schema/validator.go
new file mode 100644
index 0000000..4b8cd08
--- /dev/null
+++ b/schema/validator.go
@@ -0,0 +1,253 @@
+// 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
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "regexp"
+
+ digest "github.com/opencontainers/go-digest"
+ v1 "github.com/opencontainers/image-spec/specs-go/v1"
+ "github.com/pkg/errors"
+ "github.com/xeipuuv/gojsonschema"
+)
+
+// Validator wraps a media type string identifier
+// and implements validation against a JSON schema.
+type Validator string
+
+type validateFunc func(r io.Reader) error
+
+var mapValidate = map[Validator]validateFunc{
+ ValidatorMediaTypeImageConfig: validateConfig,
+ ValidatorMediaTypeDescriptor: validateDescriptor,
+ ValidatorMediaTypeImageIndex: validateIndex,
+ ValidatorMediaTypeManifest: validateManifest,
+}
+
+// ValidationError contains all the errors that happened during validation.
+type ValidationError struct {
+ Errs []error
+}
+
+func (e ValidationError) Error() string {
+ return fmt.Sprintf("%v", e.Errs)
+}
+
+// Validate validates the given reader against the schema of the wrapped media type.
+func (v Validator) Validate(src io.Reader) error {
+ buf, err := io.ReadAll(src)
+ if err != nil {
+ return errors.Wrap(err, "unable to read the document file")
+ }
+
+ if f, ok := mapValidate[v]; ok {
+ if f == nil {
+ return fmt.Errorf("internal error: mapValidate[%q] is nil", v)
+ }
+ err = f(bytes.NewReader(buf))
+ if err != nil {
+ return err
+ }
+ }
+
+ sl := newFSLoaderFactory(schemaNamespaces, FileSystem()).New(specs[v])
+ ml := gojsonschema.NewStringLoader(string(buf))
+
+ result, err := gojsonschema.Validate(sl, ml)
+ if err != nil {
+ return errors.Wrapf(
+ WrapSyntaxError(bytes.NewReader(buf), err),
+ "schema %s: unable to validate", v)
+ }
+
+ if result.Valid() {
+ return nil
+ }
+
+ errs := make([]error, 0, len(result.Errors()))
+ for _, desc := range result.Errors() {
+ errs = append(errs, fmt.Errorf("%s", desc))
+ }
+
+ return ValidationError{
+ Errs: errs,
+ }
+}
+
+type unimplemented string
+
+func (v unimplemented) Validate(_ io.Reader) error {
+ return fmt.Errorf("%s: unimplemented", v)
+}
+
+func validateManifest(r io.Reader) error {
+ header := v1.Manifest{}
+
+ buf, err := io.ReadAll(r)
+ if err != nil {
+ return errors.Wrapf(err, "error reading the io stream")
+ }
+
+ err = json.Unmarshal(buf, &header)
+ if err != nil {
+ return errors.Wrap(err, "manifest format mismatch")
+ }
+
+ if header.Config.MediaType != string(v1.MediaTypeImageConfig) {
+ fmt.Printf("warning: config %s has an unknown media type: %s\n", header.Config.Digest, header.Config.MediaType)
+ }
+
+ for _, layer := range header.Layers {
+ if layer.MediaType != string(v1.MediaTypeImageLayer) &&
+ layer.MediaType != string(v1.MediaTypeImageLayerGzip) &&
+ layer.MediaType != string(v1.MediaTypeImageLayerZstd) &&
+ layer.MediaType != string(v1.MediaTypeImageLayerNonDistributable) && //nolint:staticcheck
+ layer.MediaType != string(v1.MediaTypeImageLayerNonDistributableGzip) && //nolint:staticcheck
+ layer.MediaType != string(v1.MediaTypeImageLayerNonDistributableZstd) { //nolint:staticcheck
+ fmt.Printf("warning: layer %s has an unknown media type: %s\n", layer.Digest, layer.MediaType)
+ }
+ }
+ return nil
+}
+
+func validateDescriptor(r io.Reader) error {
+ header := v1.Descriptor{}
+
+ buf, err := io.ReadAll(r)
+ if err != nil {
+ return errors.Wrapf(err, "error reading the io stream")
+ }
+
+ err = json.Unmarshal(buf, &header)
+ if err != nil {
+ return errors.Wrap(err, "descriptor format mismatch")
+ }
+
+ err = header.Digest.Validate()
+ if err == digest.ErrDigestUnsupported {
+ // we ignore unsupported algorithms
+ fmt.Printf("warning: unsupported digest: %q: %v\n", header.Digest, err)
+ return nil
+ }
+ return err
+}
+
+func validateIndex(r io.Reader) error {
+ header := v1.Index{}
+
+ buf, err := io.ReadAll(r)
+ if err != nil {
+ return errors.Wrapf(err, "error reading the io stream")
+ }
+
+ err = json.Unmarshal(buf, &header)
+ if err != nil {
+ return errors.Wrap(err, "index format mismatch")
+ }
+
+ for _, manifest := range header.Manifests {
+ if manifest.MediaType != string(v1.MediaTypeImageManifest) {
+ fmt.Printf("warning: manifest %s has an unknown media type: %s\n", manifest.Digest, manifest.MediaType)
+ }
+ if manifest.Platform != nil {
+ checkPlatform(manifest.Platform.OS, manifest.Platform.Architecture)
+ checkArchitecture(manifest.Platform.Architecture, manifest.Platform.Variant)
+ }
+
+ }
+
+ return nil
+}
+
+func validateConfig(r io.Reader) error {
+ header := v1.Image{}
+
+ buf, err := io.ReadAll(r)
+ if err != nil {
+ return errors.Wrapf(err, "error reading the io stream")
+ }
+
+ err = json.Unmarshal(buf, &header)
+ if err != nil {
+ return errors.Wrap(err, "config format mismatch")
+ }
+
+ checkPlatform(header.OS, header.Architecture)
+ checkArchitecture(header.Architecture, header.Variant)
+
+ envRegexp := regexp.MustCompile(`^[^=]+=.*$`)
+ for _, e := range header.Config.Env {
+ if !envRegexp.MatchString(e) {
+ return errors.Errorf("unexpected env: %q", e)
+ }
+ }
+
+ return nil
+}
+
+func checkArchitecture(Architecture string, Variant string) {
+ validCombins := map[string][]string{
+ "arm": {"", "v6", "v7", "v8"},
+ "arm64": {"", "v8"},
+ "386": {""},
+ "amd64": {""},
+ "ppc64": {""},
+ "ppc64le": {""},
+ "mips64": {""},
+ "mips64le": {""},
+ "s390x": {""},
+ "riscv64": {""},
+ }
+ for arch, variants := range validCombins {
+ if arch == Architecture {
+ for _, variant := range variants {
+ if variant == Variant {
+ return
+ }
+ }
+ fmt.Printf("warning: combination of architecture %q and variant %q is not valid.\n", Architecture, Variant)
+ }
+ }
+ fmt.Printf("warning: architecture %q is not supported yet.\n", Architecture)
+}
+
+func checkPlatform(OS string, Architecture string) {
+ validCombins := map[string][]string{
+ "android": {"arm"},
+ "darwin": {"386", "amd64", "arm", "arm64"},
+ "dragonfly": {"amd64"},
+ "freebsd": {"386", "amd64", "arm"},
+ "linux": {"386", "amd64", "arm", "arm64", "ppc64", "ppc64le", "mips64", "mips64le", "s390x", "riscv64"},
+ "netbsd": {"386", "amd64", "arm"},
+ "openbsd": {"386", "amd64", "arm"},
+ "plan9": {"386", "amd64"},
+ "solaris": {"amd64"},
+ "windows": {"386", "amd64"}}
+ for os, archs := range validCombins {
+ if os == OS {
+ for _, arch := range archs {
+ if arch == Architecture {
+ return
+ }
+ }
+ fmt.Printf("warning: combination of os %q and architecture %q is invalid.\n", OS, Architecture)
+ }
+ }
+ fmt.Printf("warning: operating system %q of the bundle is not supported yet.\n", OS)
+}
diff --git a/spec.md b/spec.md
new file mode 100644
index 0000000..db2a4b7
--- /dev/null
+++ b/spec.md
@@ -0,0 +1,70 @@
+# Open Container Initiative
+
+## Image Format Specification
+
+This specification defines an OCI Image, consisting of an [image manifest](manifest.md), an [image index](image-index.md) (optional), a set of [filesystem layers](layer.md), and a [configuration](config.md).
+
+The goal of this specification is to enable the creation of interoperable tools for building, transporting, and preparing a container image to run.
+
+### Table of Contents
+
+- [Notational Conventions](#notational-conventions)
+- [Overview](#overview)
+ - [Understanding the Specification](#understanding-the-specification)
+ - [Media Types](media-types.md)
+- [Content Descriptors](descriptor.md)
+- [Image Layout](image-layout.md)
+- [Image Manifest](manifest.md)
+- [Image Index](image-index.md)
+- [Filesystem Layers](layer.md)
+- [Image Configuration](config.md)
+- [Annotations](annotations.md)
+- [Conversion](conversion.md)
+- [Considerations](considerations.md)
+ - [Extensibility](considerations.md#extensibility)
+ - [Canonicalization](considerations.md#canonicalization)
+
+## Notational Conventions
+
+The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" are to be interpreted as described in [RFC 2119](https://tools.ietf.org/html/rfc2119) (Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997).
+
+The key words "unspecified", "undefined", and "implementation-defined" are to be interpreted as described in the [rationale for the C99 standard][c99-unspecified].
+
+An implementation is not compliant if it fails to satisfy one or more of the MUST, MUST NOT, REQUIRED, SHALL, or SHALL NOT requirements for the protocols it implements.
+An implementation is compliant if it satisfies all the MUST, MUST NOT, REQUIRED, SHALL, and SHALL NOT requirements for the protocols it implements.
+
+## Overview
+
+At a high level the image manifest contains metadata about the contents and dependencies of the image including the content-addressable identity of one or more [filesystem layer changeset](layer.md) archives that will be unpacked to make up the final runnable filesystem.
+The image configuration includes information such as application arguments, environments, etc.
+The image index is a higher-level manifest which points to a list of manifests and descriptors.
+Typically, these manifests may provide different implementations of the image, possibly varying by platform or other attributes.
+
+![build diagram](img/build-diagram.png)
+
+Once built the OCI Image can then be discovered by name, downloaded, verified by hash, trusted through a signature, and unpacked into an [OCI Runtime Bundle](https://github.com/opencontainers/runtime-spec/blob/master/bundle.md).
+
+![runtime diagram](img/run-diagram.png)
+
+### Understanding the Specification
+
+The [OCI Image Media Types](media-types.md) document is a starting point to understanding the overall structure of the specification.
+
+The high-level components of the spec include:
+
+- [Image Manifest](manifest.md) - a document describing the components that make up a container image
+- [Image Index](image-index.md) - an annotated list of manifests
+- [Image Layout](image-layout.md) - a filesystem layout representing the contents of an image
+- [Filesystem Layer](layer.md) - a changeset that describes a container's filesystem
+- [Image Configuration](config.md) - a document determining layer ordering and configuration of the image suitable for translation into a [runtime bundle][runtime-spec]
+- [Conversion](conversion.md) - a document describing how this translation should occur
+- [Artifacts Guidance](artifacts-guidance.md) - a document describing how to use the spec for packaging content other than OCI images
+- [Descriptor](descriptor.md) - a reference that describes the type, metadata and content address of referenced content
+
+Future versions of this specification may include the following OPTIONAL features:
+
+- Signatures that are based on signing image content address
+- Naming that is federated based on DNS and can be delegated
+
+[c99-unspecified]: https://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf#page=18
+[runtime-spec]: https://github.com/opencontainers/runtime-spec
diff --git a/specs-go/v1/annotations.go b/specs-go/v1/annotations.go
new file mode 100644
index 0000000..581cf7c
--- /dev/null
+++ b/specs-go/v1/annotations.go
@@ -0,0 +1,62 @@
+// 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 v1
+
+const (
+ // AnnotationCreated is the annotation key for the date and time on which the image was built (date-time string as defined by RFC 3339).
+ AnnotationCreated = "org.opencontainers.image.created"
+
+ // AnnotationAuthors is the annotation key for the contact details of the people or organization responsible for the image (freeform string).
+ AnnotationAuthors = "org.opencontainers.image.authors"
+
+ // AnnotationURL is the annotation key for the URL to find more information on the image.
+ AnnotationURL = "org.opencontainers.image.url"
+
+ // AnnotationDocumentation is the annotation key for the URL to get documentation on the image.
+ AnnotationDocumentation = "org.opencontainers.image.documentation"
+
+ // AnnotationSource is the annotation key for the URL to get source code for building the image.
+ AnnotationSource = "org.opencontainers.image.source"
+
+ // AnnotationVersion is the annotation key for the version of the packaged software.
+ // The version MAY match a label or tag in the source code repository.
+ // The version MAY be Semantic versioning-compatible.
+ AnnotationVersion = "org.opencontainers.image.version"
+
+ // AnnotationRevision is the annotation key for the source control revision identifier for the packaged software.
+ AnnotationRevision = "org.opencontainers.image.revision"
+
+ // AnnotationVendor is the annotation key for the name of the distributing entity, organization or individual.
+ AnnotationVendor = "org.opencontainers.image.vendor"
+
+ // AnnotationLicenses is the annotation key for the license(s) under which contained software is distributed as an SPDX License Expression.
+ AnnotationLicenses = "org.opencontainers.image.licenses"
+
+ // AnnotationRefName is the annotation key for the name of the reference for a target.
+ // SHOULD only be considered valid when on descriptors on `index.json` within image layout.
+ AnnotationRefName = "org.opencontainers.image.ref.name"
+
+ // AnnotationTitle is the annotation key for the human-readable title of the image.
+ AnnotationTitle = "org.opencontainers.image.title"
+
+ // AnnotationDescription is the annotation key for the human-readable description of the software packaged in the image.
+ AnnotationDescription = "org.opencontainers.image.description"
+
+ // AnnotationBaseImageDigest is the annotation key for the digest of the image's base image.
+ AnnotationBaseImageDigest = "org.opencontainers.image.base.digest"
+
+ // AnnotationBaseImageName is the annotation key for the image reference of the image's base image.
+ AnnotationBaseImageName = "org.opencontainers.image.base.name"
+)
diff --git a/specs-go/v1/config.go b/specs-go/v1/config.go
new file mode 100644
index 0000000..36b0aeb
--- /dev/null
+++ b/specs-go/v1/config.go
@@ -0,0 +1,111 @@
+// 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 v1
+
+import (
+ "time"
+
+ digest "github.com/opencontainers/go-digest"
+)
+
+// ImageConfig defines the execution parameters which should be used as a base when running a container using an image.
+type ImageConfig struct {
+ // User defines the username or UID which the process in the container should run as.
+ User string `json:"User,omitempty"`
+
+ // ExposedPorts a set of ports to expose from a container running this image.
+ ExposedPorts map[string]struct{} `json:"ExposedPorts,omitempty"`
+
+ // Env is a list of environment variables to be used in a container.
+ Env []string `json:"Env,omitempty"`
+
+ // Entrypoint defines a list of arguments to use as the command to execute when the container starts.
+ Entrypoint []string `json:"Entrypoint,omitempty"`
+
+ // Cmd defines the default arguments to the entrypoint of the container.
+ Cmd []string `json:"Cmd,omitempty"`
+
+ // Volumes is a set of directories describing where the process is likely write data specific to a container instance.
+ Volumes map[string]struct{} `json:"Volumes,omitempty"`
+
+ // WorkingDir sets the current working directory of the entrypoint process in the container.
+ WorkingDir string `json:"WorkingDir,omitempty"`
+
+ // Labels contains arbitrary metadata for the container.
+ Labels map[string]string `json:"Labels,omitempty"`
+
+ // StopSignal contains the system call signal that will be sent to the container to exit.
+ StopSignal string `json:"StopSignal,omitempty"`
+
+ // ArgsEscaped
+ //
+ // Deprecated: This field is present only for legacy compatibility with
+ // Docker and should not be used by new image builders. It is used by Docker
+ // for Windows images to indicate that the `Entrypoint` or `Cmd` or both,
+ // contains only a single element array, that is a pre-escaped, and combined
+ // into a single string `CommandLine`. If `true` the value in `Entrypoint` or
+ // `Cmd` should be used as-is to avoid double escaping.
+ // https://github.com/opencontainers/image-spec/pull/892
+ ArgsEscaped bool `json:"ArgsEscaped,omitempty"`
+}
+
+// RootFS describes a layer content addresses
+type RootFS struct {
+ // Type is the type of the rootfs.
+ Type string `json:"type"`
+
+ // DiffIDs is an array of layer content hashes (DiffIDs), in order from bottom-most to top-most.
+ DiffIDs []digest.Digest `json:"diff_ids"`
+}
+
+// History describes the history of a layer.
+type History struct {
+ // Created is the combined date and time at which the layer was created, formatted as defined by RFC 3339, section 5.6.
+ Created *time.Time `json:"created,omitempty"`
+
+ // CreatedBy is the command which created the layer.
+ CreatedBy string `json:"created_by,omitempty"`
+
+ // Author is the author of the build point.
+ Author string `json:"author,omitempty"`
+
+ // Comment is a custom message set when creating the layer.
+ Comment string `json:"comment,omitempty"`
+
+ // EmptyLayer is used to mark if the history item created a filesystem diff.
+ EmptyLayer bool `json:"empty_layer,omitempty"`
+}
+
+// Image is the JSON structure which describes some basic information about the image.
+// This provides the `application/vnd.oci.image.config.v1+json` mediatype when marshalled to JSON.
+type Image struct {
+ // Created is the combined date and time at which the image was created, formatted as defined by RFC 3339, section 5.6.
+ Created *time.Time `json:"created,omitempty"`
+
+ // Author defines the name and/or email address of the person or entity which created and is responsible for maintaining the image.
+ Author string `json:"author,omitempty"`
+
+ // Platform describes the platform which the image in the manifest runs on.
+ Platform
+
+ // Config defines the execution parameters which should be used as a base when running a container using the image.
+ Config ImageConfig `json:"config,omitempty"`
+
+ // RootFS references the layer content addresses used by the image.
+ RootFS RootFS `json:"rootfs"`
+
+ // History describes the history of each layer.
+ History []History `json:"history,omitempty"`
+}
diff --git a/specs-go/v1/descriptor.go b/specs-go/v1/descriptor.go
new file mode 100644
index 0000000..1881b11
--- /dev/null
+++ b/specs-go/v1/descriptor.go
@@ -0,0 +1,80 @@
+// Copyright 2016-2022 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 v1
+
+import digest "github.com/opencontainers/go-digest"
+
+// Descriptor describes the disposition of targeted content.
+// This structure provides `application/vnd.oci.descriptor.v1+json` mediatype
+// when marshalled to JSON.
+type Descriptor struct {
+ // MediaType is the media type of the object this schema refers to.
+ MediaType string `json:"mediaType"`
+
+ // Digest is the digest of the targeted content.
+ Digest digest.Digest `json:"digest"`
+
+ // Size specifies the size in bytes of the blob.
+ Size int64 `json:"size"`
+
+ // URLs specifies a list of URLs from which this object MAY be downloaded
+ URLs []string `json:"urls,omitempty"`
+
+ // Annotations contains arbitrary metadata relating to the targeted content.
+ Annotations map[string]string `json:"annotations,omitempty"`
+
+ // Data is an embedding of the targeted content. This is encoded as a base64
+ // string when marshalled to JSON (automatically, by encoding/json). If
+ // present, Data can be used directly to avoid fetching the targeted content.
+ Data []byte `json:"data,omitempty"`
+
+ // Platform describes the platform which the image in the manifest runs on.
+ //
+ // This should only be used when referring to a manifest.
+ Platform *Platform `json:"platform,omitempty"`
+
+ // ArtifactType is the IANA media type of this artifact.
+ ArtifactType string `json:"artifactType,omitempty"`
+}
+
+// Platform describes the platform which the image in the manifest runs on.
+type Platform struct {
+ // Architecture field specifies the CPU architecture, for example
+ // `amd64` or `ppc64le`.
+ Architecture string `json:"architecture"`
+
+ // OS specifies the operating system, for example `linux` or `windows`.
+ OS string `json:"os"`
+
+ // OSVersion is an optional field specifying the operating system
+ // version, for example on Windows `10.0.14393.1066`.
+ OSVersion string `json:"os.version,omitempty"`
+
+ // OSFeatures is an optional field specifying an array of strings,
+ // each listing a required OS feature (for example on Windows `win32k`).
+ OSFeatures []string `json:"os.features,omitempty"`
+
+ // Variant is an optional field specifying a variant of the CPU, for
+ // example `v7` to specify ARMv7 when architecture is `arm`.
+ Variant string `json:"variant,omitempty"`
+}
+
+// DescriptorEmptyJSON is the descriptor of a blob with content of `{}`.
+var DescriptorEmptyJSON = Descriptor{
+ MediaType: MediaTypeEmptyJSON,
+ Digest: `sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a`,
+ Size: 2,
+ Data: []byte(`{}`),
+}
diff --git a/specs-go/v1/index.go b/specs-go/v1/index.go
new file mode 100644
index 0000000..e2bed9d
--- /dev/null
+++ b/specs-go/v1/index.go
@@ -0,0 +1,38 @@
+// 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 v1
+
+import "github.com/opencontainers/image-spec/specs-go"
+
+// Index references manifests for various platforms.
+// This structure provides `application/vnd.oci.image.index.v1+json` mediatype when marshalled to JSON.
+type Index struct {
+ specs.Versioned
+
+ // MediaType specifies the type of this document data structure e.g. `application/vnd.oci.image.index.v1+json`
+ MediaType string `json:"mediaType,omitempty"`
+
+ // ArtifactType specifies the IANA media type of artifact when the manifest is used for an artifact.
+ ArtifactType string `json:"artifactType,omitempty"`
+
+ // Manifests references platform specific manifests.
+ Manifests []Descriptor `json:"manifests"`
+
+ // Subject is an optional link from the image manifest to another manifest forming an association between the image manifest and the other manifest.
+ Subject *Descriptor `json:"subject,omitempty"`
+
+ // Annotations contains arbitrary metadata for the image index.
+ Annotations map[string]string `json:"annotations,omitempty"`
+}
diff --git a/specs-go/v1/layout.go b/specs-go/v1/layout.go
new file mode 100644
index 0000000..c5503cb
--- /dev/null
+++ b/specs-go/v1/layout.go
@@ -0,0 +1,32 @@
+// 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 v1
+
+const (
+ // ImageLayoutFile is the file name containing ImageLayout in an OCI Image Layout
+ ImageLayoutFile = "oci-layout"
+ // ImageLayoutVersion is the version of ImageLayout
+ ImageLayoutVersion = "1.0.0"
+ // ImageIndexFile is the file name of the entry point for references and descriptors in an OCI Image Layout
+ ImageIndexFile = "index.json"
+ // ImageBlobsDir is the directory name containing content addressable blobs in an OCI Image Layout
+ ImageBlobsDir = "blobs"
+)
+
+// ImageLayout is the structure in the "oci-layout" file, found in the root
+// of an OCI Image-layout directory.
+type ImageLayout struct {
+ Version string `json:"imageLayoutVersion"`
+}
diff --git a/specs-go/v1/manifest.go b/specs-go/v1/manifest.go
new file mode 100644
index 0000000..26fec52
--- /dev/null
+++ b/specs-go/v1/manifest.go
@@ -0,0 +1,41 @@
+// Copyright 2016-2022 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 v1
+
+import "github.com/opencontainers/image-spec/specs-go"
+
+// Manifest provides `application/vnd.oci.image.manifest.v1+json` mediatype structure when marshalled to JSON.
+type Manifest struct {
+ specs.Versioned
+
+ // MediaType specifies the type of this document data structure e.g. `application/vnd.oci.image.manifest.v1+json`
+ MediaType string `json:"mediaType,omitempty"`
+
+ // ArtifactType specifies the IANA media type of artifact when the manifest is used for an artifact.
+ ArtifactType string `json:"artifactType,omitempty"`
+
+ // Config references a configuration object for a container, by digest.
+ // The referenced configuration object is a JSON blob that the runtime uses to set up the container.
+ Config Descriptor `json:"config"`
+
+ // Layers is an indexed list of layers referenced by the manifest.
+ Layers []Descriptor `json:"layers"`
+
+ // Subject is an optional link from the image manifest to another manifest forming an association between the image manifest and the other manifest.
+ Subject *Descriptor `json:"subject,omitempty"`
+
+ // Annotations contains arbitrary metadata for the image manifest.
+ Annotations map[string]string `json:"annotations,omitempty"`
+}
diff --git a/specs-go/v1/mediatype.go b/specs-go/v1/mediatype.go
new file mode 100644
index 0000000..892ba3d
--- /dev/null
+++ b/specs-go/v1/mediatype.go
@@ -0,0 +1,75 @@
+// 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 v1
+
+const (
+ // MediaTypeDescriptor specifies the media type for a content descriptor.
+ MediaTypeDescriptor = "application/vnd.oci.descriptor.v1+json"
+
+ // MediaTypeLayoutHeader specifies the media type for the oci-layout.
+ MediaTypeLayoutHeader = "application/vnd.oci.layout.header.v1+json"
+
+ // MediaTypeImageManifest specifies the media type for an image manifest.
+ MediaTypeImageManifest = "application/vnd.oci.image.manifest.v1+json"
+
+ // MediaTypeImageIndex specifies the media type for an image index.
+ MediaTypeImageIndex = "application/vnd.oci.image.index.v1+json"
+
+ // MediaTypeImageLayer is the media type used for layers referenced by the manifest.
+ MediaTypeImageLayer = "application/vnd.oci.image.layer.v1.tar"
+
+ // MediaTypeImageLayerGzip is the media type used for gzipped layers
+ // referenced by the manifest.
+ MediaTypeImageLayerGzip = "application/vnd.oci.image.layer.v1.tar+gzip"
+
+ // MediaTypeImageLayerZstd is the media type used for zstd compressed
+ // layers referenced by the manifest.
+ MediaTypeImageLayerZstd = "application/vnd.oci.image.layer.v1.tar+zstd"
+
+ // MediaTypeImageLayerNonDistributable is the media type for layers referenced by
+ // the manifest but with distribution restrictions.
+ //
+ // Deprecated: Non-distributable layers are deprecated, and not recommended
+ // for future use. Implementations SHOULD NOT produce new non-distributable
+ // layers.
+ // https://github.com/opencontainers/image-spec/pull/965
+ MediaTypeImageLayerNonDistributable = "application/vnd.oci.image.layer.nondistributable.v1.tar"
+
+ // MediaTypeImageLayerNonDistributableGzip is the media type for
+ // gzipped layers referenced by the manifest but with distribution
+ // restrictions.
+ //
+ // Deprecated: Non-distributable layers are deprecated, and not recommended
+ // for future use. Implementations SHOULD NOT produce new non-distributable
+ // layers.
+ // https://github.com/opencontainers/image-spec/pull/965
+ MediaTypeImageLayerNonDistributableGzip = "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip"
+
+ // MediaTypeImageLayerNonDistributableZstd is the media type for zstd
+ // compressed layers referenced by the manifest but with distribution
+ // restrictions.
+ //
+ // Deprecated: Non-distributable layers are deprecated, and not recommended
+ // for future use. Implementations SHOULD NOT produce new non-distributable
+ // layers.
+ // https://github.com/opencontainers/image-spec/pull/965
+ MediaTypeImageLayerNonDistributableZstd = "application/vnd.oci.image.layer.nondistributable.v1.tar+zstd"
+
+ // MediaTypeImageConfig specifies the media type for the image configuration.
+ MediaTypeImageConfig = "application/vnd.oci.image.config.v1+json"
+
+ // MediaTypeEmptyJSON specifies the media type for an unused blob containing the value `{}`
+ MediaTypeEmptyJSON = "application/vnd.oci.empty.v1+json"
+)
diff --git a/specs-go/version.go b/specs-go/version.go
new file mode 100644
index 0000000..11e09b5
--- /dev/null
+++ b/specs-go/version.go
@@ -0,0 +1,32 @@
+// 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 specs
+
+import "fmt"
+
+const (
+ // VersionMajor is for an API incompatible changes
+ VersionMajor = 1
+ // VersionMinor is for functionality in a backwards-compatible manner
+ VersionMinor = 1
+ // VersionPatch is for backwards-compatible bug fixes
+ VersionPatch = 0
+
+ // VersionDev indicates development branch. Releases will be empty string.
+ VersionDev = "-rc.5"
+)
+
+// Version is the specification version that the package types support.
+var Version = fmt.Sprintf("%d.%d.%d%s", VersionMajor, VersionMinor, VersionPatch, VersionDev)
diff --git a/specs-go/versioned.go b/specs-go/versioned.go
new file mode 100644
index 0000000..58a1510
--- /dev/null
+++ b/specs-go/versioned.go
@@ -0,0 +1,23 @@
+// 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 specs
+
+// Versioned provides a struct with the manifest schemaVersion and mediaType.
+// Incoming content with unknown schema version can be decoded against this
+// struct to check the version.
+type Versioned struct {
+ // SchemaVersion is the image manifest schema that this image follows
+ SchemaVersion int `json:"schemaVersion"`
+}