pax_global_header00006660000000000000000000000064141425252020014507gustar00rootroot0000000000000052 comment=67d2d5658fe0476ab9bf414cec164077ebff3920 image-spec-1.0.2/000077500000000000000000000000001414252520200135215ustar00rootroot00000000000000image-spec-1.0.2/.gitignore000066400000000000000000000000521414252520200155060ustar00rootroot00000000000000/oci-validate-examples output header.html image-spec-1.0.2/.header000066400000000000000000000011261414252520200147520ustar00rootroot00000000000000// 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. image-spec-1.0.2/.pullapprove.yml000066400000000000000000000007541414252520200167010ustar00rootroot00000000000000version: 2 requirements: signed_off_by: required: true group_defaults: required: 2 approve_by_comment: enabled: true approve_regex: '^(Approved|lgtm|LGTM|:shipit:|:star:|:\+1:|:ship:)' reject_regex: ^Rejected reset_on_push: enabled: true author_approval: ignored: true always_pending: title_regex: ^WIP explanation: 'Work in progress...' conditions: branches: - master groups: image-spec: teams: - image-spec-maintainers image-spec-1.0.2/.tool/000077500000000000000000000000001414252520200145545ustar00rootroot00000000000000image-spec-1.0.2/.tool/check-license000077500000000000000000000004401414252520200171750ustar00rootroot00000000000000#!/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 image-spec-1.0.2/.tool/genheader.go000066400000000000000000000026271414252520200170340ustar00rootroot00000000000000// 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(`image-spec {{.Version}} `)) 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) } image-spec-1.0.2/.tool/lint000077500000000000000000000012401414252520200154450ustar00rootroot00000000000000#!/usr/bin/env bash set -o errexit set -o nounset set -o pipefail if [ ! $(command -v gometalinter) ]; then go get -u github.com/alecthomas/gometalinter gometalinter --install fi for d in $(find . -type d -not -iwholename '*.git*' -a -not -iname '.tool' -a -not -iwholename '*vendor*'); do gometalinter \ --exclude='error return value not checked.*(Close|Log|Print).*\(errcheck\)$' \ --exclude='.*_test\.go:.*error return value not checked.*\(errcheck\)$' \ --exclude='duplicate of.*_test.go.*\(dupl\)$' \ --exclude='schema/fs.go' \ --disable=aligncheck \ --disable=gotype \ --disable=gas \ --cyclo-over=35 \ --tests \ --deadline=60s "${d}" done image-spec-1.0.2/.travis.yml000066400000000000000000000006471414252520200156410ustar00rootroot00000000000000language: go go: - 1.7 sudo: required services: - docker before_script: - export PATH=$HOME/gopath/bin:$PATH before_install: - docker pull vbatts/pandoc - make install.tools - go get -u github.com/alecthomas/gometalinter - gometalinter --install - go get -t -d ./... install: true script: - env | grep TRAVIS_ - make .gitvalidation - make lint - make check-license - make test - make docs image-spec-1.0.2/GOVERNANCE.md000066400000000000000000000074311414252520200154770ustar00rootroot00000000000000# 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](#release-approval) 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 > [{project} VOTE]: {motion description} (closes {end of voting window}) For example: > [runtime-spec VOTE]: Tag 0647920 as 1.0.0-rc (closes 2016-06-03 20:00 UTC) ### Tallying results 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: > [runtime-spec adopted]: Tag 0647920 as 1.0.0-rc (+6 -0 #3) [charter]: https://www.opencontainers.org/about/governance image-spec-1.0.2/HACKING.md000066400000000000000000000045221414252520200151120ustar00rootroot00000000000000# 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 [gometalinter](https://github.com/alecthomas/gometalinter). Invocation: ``` $ make lint ``` ### Tests This target executes all Go based tests. Invocation: ``` $ make test $ make validate-examples ``` ### Virtual schema http/FileSystem The `schema` validator uses a virtual [http/FileSystem](https://golang.org/pkg/net/http/#FileSystem) to load the JSON schema files for validating OCI images and/or manifests. The virtual filesystem is generated using the `esc` tool and compiled into consumers of the `schema` package so the JSON schema files don't have to be distributed along with and consumer binaries. Whenever changes are being done in any of the `schema/*.json` files, one must refresh the generated virtual filesystem. Otherwise schema changes will not be visible inside `schema` consumers. Prerequisites: * [esc](https://github.com/mjibson/esc) Invocation: ``` $ make schema-fs ``` ### JSON schema formatting This target auto-formats all JSON files in the `schema` directory using the `jq` tool. Prerequisites: * [jq](https://stedolan.github.io/jq/) >=1.5 Invocation: ``` $ make fmt ``` ### OCI image specification PDF/HTML documentation files This target generates a PDF/HTML version of the OCI image specification. Prerequisites: * [Docker](https://www.docker.com/) Invocation: ``` $ make docs ``` ### License header check This target checks if the source code includes necessary headers. Invocation: ``` $ make check-license ``` ### Clean build artifacts This target cleans all generated/compiled artifacts. Invocation: ``` $ make clean ``` ### Create PNG images from dot files This target generates PNG image files from DOT source files in the `img` directory. Prerequisites: * [graphviz](http://www.graphviz.org/) Invocation: ``` $ make img/media-types.png ``` image-spec-1.0.2/LICENSE000066400000000000000000000250171414252520200145330ustar00rootroot00000000000000 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. image-spec-1.0.2/MAINTAINERS000066400000000000000000000006671414252520200152270ustar00rootroot00000000000000Brandon Philips (@philips) Brendan Burns (@brendandburns) Jason Bouzane (@jbouzane) John Starks (@jstarks) Jonathan Boulle (@jonboulle) Stephen Day (@stevvooe) Vincent Batts (@vbatts) Aleksa Sarai (@cyphar) Keyang Xie (@xiekeyang) image-spec-1.0.2/Makefile000066400000000000000000000076321414252520200151710ustar00rootroot00000000000000GO15VENDOREXPERIMENT=1 export GO15VENDOREXPERIMENT DOCKER ?= $(shell command -v docker 2>/dev/null) PANDOC ?= $(shell command -v pandoc 2>/dev/null) ifeq "$(strip $(PANDOC))" '' ifneq "$(strip $(DOCKER))" '' PANDOC = $(DOCKER) run \ -it \ --rm \ -v $(shell pwd)/:/input/:ro \ -v $(shell pwd)/$(OUTPUT_DIRNAME)/:/$(OUTPUT_DIRNAME)/ \ -u $(shell id -u) \ --workdir /input \ docker.io/vbatts/pandoc:1.17.0.3-2.fc25.x86_64 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 OUTPUT_DIRNAME ?= output/ DOC_FILENAME ?= oci-image-spec EPOCH_TEST_COMMIT ?= v0.2.0 TOOLS := esc gitvalidation glide glide-vc default: check-license lint test help: @echo "Usage: make " @echo @echo " * 'docs' - produce document in the $(OUTPUT_DIRNAME) directory" @echo " * 'fmt' - format the json with indentation" @echo " * 'validate-examples' - validate the examples in the specification markdown files" @echo " * 'schema-fs' - regenerate the virtual schema http/FileSystem" @echo " * 'check-license' - check license headers in source files" @echo " * 'lint' - Execute the source code linter" @echo " * 'test' - Execute the unit tests" @echo " * 'img/*.png' - Generate PNG from dot file" fmt: for i in schema/*.json ; do jq --indent 2 -M . "$${i}" > xx && cat xx > "$${i}" && rm xx ; done docs: $(OUTPUT_DIRNAME)/$(DOC_FILENAME).pdf $(OUTPUT_DIRNAME)/$(DOC_FILENAME).html 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 markdown_github -t latex --latex-engine=xelatex -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 markdown_github -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 > $@ validate-examples: schema/fs.go go test -run TestValidate ./schema schema/fs.go: $(wildcard schema/*.json) schema/gen.go cd schema && printf "%s\n\n%s\n" "$$(cat ../.header)" "$$(go generate)" > fs.go schema-fs: schema/fs.go @echo "generating schema fs" check-license: @echo "checking license headers" @./.tool/check-license lint: @echo "checking lint" @./.tool/lint test: schema/fs.go go test -race -cover $(shell go list ./... | grep -v /vendor/) img/%.png: img/%.dot dot -Tpng $^ > $@ # When this is running in travis, it will only check the travis commit range .gitvalidation: @which git-validation > /dev/null 2>/dev/null || (echo "ERROR: git-validation not found. Consider 'make install.tools' target" && false) ifdef TRAVIS_COMMIT_RANGE git-validation -q -run DCO,short-subject,dangling-whitespace else git-validation -v -run DCO,short-subject,dangling-whitespace -range $(EPOCH_TEST_COMMIT)..HEAD endif install.tools: $(TOOLS:%=.install.%) .install.esc: go get -u github.com/mjibson/esc .install.gitvalidation: go get -u github.com/vbatts/git-validation .install.glide: go get -u github.com/Masterminds/glide .install.glide-vc: go get -u github.com/sgotti/glide-vc clean: rm -rf *~ $(OUTPUT_DIRNAME) header.html .PHONY: \ $(TOOLS:%=.install.%) \ validate-examples \ check-license \ clean \ lint \ install.tools \ docs \ test \ .gitvalidation \ schema/fs.go \ schema-fs image-spec-1.0.2/README.md000066400000000000000000000175201414252520200150050ustar00rootroot00000000000000# OCI Image Format Specification 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](https://github.com/opencontainers/tob/blob/d2f9d68c1332870e40693fe077d311e0742bc73d/code-of-conduct.md) - [Roadmap](#roadmap) - [Releases](RELEASES.md) - [Project Documentation](project.md) The _optional_ and _base_ layers of all OCI projects are tracked in the [OCI Scope Table](https://www.opencontainers.org/about/oci-scope-table). ## 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). ## FAQ **Q: Why doesn't this project mention distribution?** A: Distribution, for example using HTTP as both Docker v2.2 and AppC do today, is currently out of scope on the [OCI Scope Table](https://www.opencontainers.org/about/oci-scope-table). There has been [some discussion on the TOB mailing list](https://groups.google.com/a/opencontainers.org/d/msg/tob/A3JnmI-D-6Y/tLuptPDHAgAJ) to make distribution an optional layer, but this topic is a work in progress. **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). ## Weekly Call The contributors and maintainers of all OCI projects have a weekly meeting Wednesdays at 2:00 PM (USA Pacific). Everyone is welcome to participate via [UberConference web][UberConference] or audio-only: +1-415-968-0849 (no PIN needed). An initial agenda will be posted to the [mailing list](#mailing-list) earlier in the week, and everyone is welcome to propose additional topics or suggest other agenda alterations there. Minutes are posted to the [mailing list](#mailing-list) and minutes from past calls are archived [here][minutes]. ## 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](http://developercertificate.org/)): ``` 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: Signed-off-by: Joe Smith 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](http://chris.beams.io/posts/git-commit/) or the Discussion section of [`git-commit(1)`](http://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: ...") [UberConference]: https://www.uberconference.com/opencontainers [irc-logs]: http://ircbot.wl.linuxfoundation.org/eavesdrop/%23opencontainers/ [minutes]: http://ircbot.wl.linuxfoundation.org/meetings/opencontainers/ image-spec-1.0.2/RELEASES.md000066400000000000000000000066741414252520200152630ustar00rootroot00000000000000# 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 compatibity 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://www.opencontainers.org/about/governance image-spec-1.0.2/annotations.md000066400000000000000000000107431414252520200164050ustar00rootroot00000000000000# 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) and image [manifest](manifest.md) authors: * **org.opencontainers.image.created** date and time on which the image was built (string, date-time as defined by [RFC 3339](https://tools.ietf.org/html/rfc3339#section-5.6)). * **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](http://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): ``` 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) ## Back-compatibility with Label Schema [Label Schema](https://label-schema.org) 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 | [spdx-license-expression]: https://spdx.org/spdx-specification-21-web-version#h.jxpfx0ykyb60 image-spec-1.0.2/config.md000066400000000000000000000316031414252520200153130ustar00rootroot00000000000000# 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: ``` 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: ``` 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: ``` 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]. - **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` from the container are applied. - **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 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`. - **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 be ignored 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 ", "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 } ] } ``` [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 image-spec-1.0.2/considerations.md000066400000000000000000000124311414252520200170700ustar00rootroot00000000000000# Extensibility Implementations that are reading/processing [manifests](manifest.md) or [image indexes](image-index.md) MUST NOT generate an error if they encounter an unknown property. Instead they MUST ignore unknown properties. # 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][] 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][]: [github.com/docker/go][], which claims to implement [canonical JSON][canonical-json] except for Unicode normalization. [canonical-json]: http://wiki.laptop.org/go/Canonical_JSON [github.com/docker/go]: https://github.com/docker/go/ [Go]: https://golang.org/ [JSON]: http://json.org/ # 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 abiguity 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: ``` 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: ``` 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: ``` 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: ``` 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: ``` 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 preceeding expression can be matched zero or more times. `+` indicates that the preceeding expression must be matched one or more times. These appear in the following form: ``` zeroormore ::= expression* oneormore ::= expression+ ``` Parentheses are used to group expressions into a larger expression: ``` 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 `|`. ``` 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 alernates, 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: ``` 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: ``` [a-z]+(?:/[a-z]+)* ``` [ebnf]: https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form [xmlebnf]: https://www.w3.org/TR/REC-xml/#sec-notation image-spec-1.0.2/conversion.md000066400000000000000000000211321414252520200162270ustar00rootroot00000000000000# 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 | | ------------------- | --------------- | ----- | | `author` | `annotations` | 1,2 | | `created` | `annotations` | 1,3 | | `Config.Labels` | `annotations` | | | `Config.StopSignal` | `annotations` | 1,4 | 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.author` in `annotations`. 3. The value of this field MUST be set as the value of `org.opencontainers.image.created` in `annotations`. 4. 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`. If the value of [`user`](config.md#properties) in `Config.User` is numeric, the converter SHOULD NOT modify `process.user.additionalGids`. 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](#config.exposedports). 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. image-spec-1.0.2/descriptor.md000066400000000000000000000224661414252520200162330ustar00rootroot00000000000000# 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. * 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). 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 The following field keys are reserved and MUST NOT be used by other specifications. - **`data`** *string* This key is RESERVED for future versions of the specification. All other fields may be included in other OCI specifications. 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](http://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): ``` 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](canonicalization.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 ``: ``` let ID(C) = Descriptor.digest let C = let D = ':' + 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: ``` D == ID(C) == ':' + 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 perfomant][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. ## 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" ] } ``` [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 [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 image-spec-1.0.2/identity/000077500000000000000000000000001414252520200153525ustar00rootroot00000000000000image-spec-1.0.2/identity/chainid.go000066400000000000000000000046141414252520200173050ustar00rootroot00000000000000// 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 } image-spec-1.0.2/identity/chainid_test.go000066400000000000000000000052771414252520200203520ustar00rootroot00000000000000// 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]) } }) } } image-spec-1.0.2/identity/helpers.go000066400000000000000000000023561414252520200173510ustar00rootroot00000000000000// 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) } image-spec-1.0.2/image-index.md000066400000000000000000000131611414252520200162340ustar00rootroot00000000000000# 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`. - **`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) 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 SHOULD be safely ignored. - **`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, values listed in the following table. 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` | - **`features`** *array of strings* This property is RESERVED for future versions of the specification. - **`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). ## 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, "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" } } ``` [go-environment2]: https://golang.org/doc/install/source#environment [matrix]: media-types.md#compatibility-matrix image-spec-1.0.2/image-layout.md000066400000000000000000000175351414252520200164530ustar00rootroot00000000000000## OCI Image Layout Specification * The OCI Image Layout is 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: ``` $ 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: ``` $ 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//` MUST match the digest `:` (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 `` and `` 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 ``` $ 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" } }, ... ``` ``` $ 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" }, ... ``` ``` $ cat ./blobs/sha256/5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270 | jq { "architecture": "amd64", "author": "Alyssa P. Hacker ", "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", ... ``` ``` $ 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 SHOULD be safely ignored. **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, "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 manifest references and an auxiliary mediatype for this image layout. [descriptors]: ./descriptor.md image-spec-1.0.2/img/000077500000000000000000000000001414252520200142755ustar00rootroot00000000000000image-spec-1.0.2/img/build-diagram.png000066400000000000000000000662241414252520200175160ustar00rootroot00000000000000PNG  IHDRՐl[IDATxpdW}{PQJTxVD-q7si)V*aDIDwEJZ9  #XQJPCFwӿ>v>_ͨ>s15ypRU`M|S'SO==U=ԁLxC|S<3׃JgyFկno3W/myO?4@;_ٕT}+޿˿!0ǟ۾J?3kO!D!Su/G?Y  MŒ|SrPȞ?~bi=2@B{s.Uz_z Z|K)^߆ >>ڝXK@/7y}Nϟ\ *6ޅ?\5{*ޜVfԷsH|ےo+NzԷzsWiqo˚i&4_*&֌7ծ$V {]?W!T#Oͦ" F:LzF͈oS*\:u_۵Q^җ~x 1>߰Ϲ馛S4\]kзn^ޛ8;bY<7ރPE;~[^9Y#ןT2{WsԩS_|=y/e/{ٗVoOvm^뮻݇z跾oyJ_w}oEd$kCC~L*v֤g⑕)Loxe ~?@U*OywgۂGꋛÏ^o#"3yΘD",y%^466~'L~핯|DAo~{D yGyS%9岫WAD{N)׿rB2!=WMz܌ q o H'x{y_7z{|ﱫ޿}C#Ԁ$8\mpԢ_&x'O"C g7~sY׷Wꍥ|R!a6H|ԩS˜V??WU;oy[fxVLj4D45y̓E/ 7TqH.;e>?y]ǵk7K3?. T{ɷm_>+9w=P H6`ݷ=ղ?%%}ig }Ѥg CmxtǾ=o=syuos[b:8|k/ MyN+   2@=zg*RD" 70G66e[2D%=÷#T-..C$|aVBI˩/G 3vv9/3ylm}򗷞u6پ>S`p L$A@ɌW,B{_ 2_v}Ϗ~%Y7t_&K6e\rZlB0RFY>veCmKz @SwXzgMĝWA]λ~]j /y}}Y{@~1]ᄋ`g=yTeRԡ>u{Ye39Mf/bz.ICIKp|mo=+f=}{<ē[^|!oS 3>hMw^4>1 Ԍt뭷>յ*5sv] JNz3Gg/H;?Z1Qv5_XX)Mf|Nz]??YPwGٻy/°%q @N!@I x{[ȅHޣ{ȯߧ7ߝ?&@Xluuu\6~#ɱ@8 ; HelU^SvGo馫&4醆:#v2}={o{_ucoOZ!oea@v@T$K{7e7soUҰd25?@;@qOC*?|7>)>Ev߱D.՚@`@;@@ZWo|}p}|z_{v7{wz뭟|ы^/[E0\!@ek3Y/w'yO=>}s[oSy_yk=[o r`A{:88oyo|2BR|<Cu6ۊIl0/ $6~}衇~ t0_җ~a? mI^}E}|Q.yQI/יm=$D}k?.>%u%ɦ2gȷ_ J!TwG$Poo  Hf , c?*iȬ[ndk7tGk۬o&] (|<CU($F|7BQl KO^~_{.iSZYSSS2xXD,z*@-#Q@Da)f˄l?/yK}_E)d[oGyix]]]2G4W̱K&o%y|EvXf pysb 2c/xP$Xd9>44;㝲L4(RMGfIAɓOf#yay{c&cm۷eF{#A-:r[I.$uѷSNO8d&b MD1%#Š5ZDp)ĬHfrMD,19a%w"L ZndKftJ-! |航E4i(e"ǐÔo ]|sSNo0m2ʷ4wVLzYiVx֤Cur#>tDF6N:&{&G؄s!6N'=|Ey"P >D!#S.-^m}-P)|8n2{ QMv >T{y5%e}-P)|@@_ tDy<&iWk:OC$ }-P)|G@G@\c^K:"<^|m÷ >T{yV}-P)|68hЊa}-P)|f8ק@G@\cp }-P)|]:"<v+@G@\c }-P)|g'5REk:OCG! Nurj3)F|뤚}-P)|<NP)|<CtDy"P >D!#S.i-C@ue}\"|!#^\mӷP!>tD*^?JMB u%QtuӶ~C@G@rI zk[h?!#Y zk[h?!#Y):i[h?!#Qȉ_k@MB uE)6j[h?!#Q,vPWm '>tD:+IZ):i[Jsדדz?U!:"|%|ORo--kտBo!:"]gV1+ж0*]mW۩v by4LF/!:"|E dHk˚T ;_`m2 [ylh[Jao ȗct|[ӿEX8fY'q-|:_b0h ˤϐY@[576? ж!].۔Izm٣gL3iA)DWM^v < իҦy{M8cZᙃږ<ʺ‡^.z b< Čl+75$1MYhTKM=MfCՠc7oh{_{(![G= <ۖO~w|?o҂P0q6o m)C/8y!P2sfE @gC+#zfdfkMFvٯ[U72l2"Ӑ8o {CtD**&"RW@x-Lf ל%r gCeFuIsx b@lJMC$ǝ VK ږJɣ&3flF9Qk_ە3zͪڊs?+ش׺Spg*&-kKDY&zdk >@G@rƒvSxh'!ݒ6A{&s͗>{d fӁ.:Ю^ӶT@=ldkREm^ M,.Pk\:8P"{d;&.4 @6(f6Z"{h eI"I/h[*!kٙo&z=ulߊj{r @w 3?>@G@r9$U_}B>_ hNfcE:{\+j5$.U,(i2{Si[* L@]3l{uB&|IEWLiCtD.@ɐ. @Ő3l2JŠd7nRX{rl@:_AT٥ӶT@8u`2Hio|Z#"5vMf9ESSf CtD(@5&-ps&ȦC͞ySlF~{!Ap0ove 2G#h[N"V1MMDPrV ۾Y|=6L|f!=VM>@G@r9${dh"%Ofusxb @M{2v'(%S@~~dk"\QKC.@R!y8(v\ӪiXPY !u{48d _\HѠl>W{_>@G@r3$h׃Θ'dŶ i \g dM}6!qI<7",{o7E<+ڸ3^^"ɬu‡ .EQC1vͷV=#%xEx‡ "Yف4>kL'37M~L>@G@\ Y&KX솺- >D;=$b4O"+Mq #S.p\d_HN(C‡h'!#S.P>D;ur C@G@\!,IG#Gw6֥'m@Z;YkP)2`[&>Ty8F6c5OLe-HkTsCAw0(%N_4Tr`mtRu44Yr( tD, VM~8w .TTr`ҶTps @5>ݷ}eHg) w>Tlg\ yY3=ZeKC98HxGMkl,5OyT:-2nRye&P\@i@QbbY'*$+F9 5o@Riy'kavo"~_3A @' mC%#K( BbbHKD0!S[,Hi- ֶ1^- rҿ aHAէm?dмoϙ6ߦ ^l;6[tQk)a[P,r3m8ݨ9PD\&4a 8۪6,6=y42+N^trE6 ;|#ÿ ڠ-}&I̮kf/̬mg2#PR1}Vmur !K3ۥ,A-8kx#itVK 8X_́gg:+I4=buǨ^c2K]Wo7mKq4,8b})ݱ̈(I}ث𡒳]vW8@Zݙ4qIC1!4(,\$ gUܹ_9*n0 1{(asr40P3GgKG&4 >l{1Q1!sxV+>3np:xT b1h[!xPsH\e>qO˖VJ[iL1FŐ}.IGC|q-8d'ZID, KÖɬ'Meq/&avr(hU(&Ԫߋd S:Mm(v`!:B̮9,ihAD@tDyʥ7AzASggMa}xPckθ/l9Vd{Ǭو4X2VAmjORy&@|CT#ek[ v"bg:g7LfN}m]W׿TXqeMbMHÄ>G_2OOj3&3gN-5n#ʊ3r(G5hIu*H^ק&DdL{DfŨz]Y4&>mKM& q ݦC @ 1#´="@s:.qsm z UW=N]lV ?KtAeٖ!́@$4{fMs7U> (ci[j!kGȵk&);&Kr>@GAc"\3C\aG1VdIXw:{؈QM̚j4&Hġi'0t,q@/aÇe>v=7$8 d.y3@-RA‡"8yϒ> &5p m`w.`9Ͼ?#&ަ㚾tZX0ؠߤ"EB^Buݦ2\kZ% @P< $EAB+?Q|A~;Y'%#vH<}[Lf}styI MZhJ9Q_3Gv@}z#>cM'ؽ\۩% hR-X9'4v=()8)O{,[k8e CeDOQMM3*HCBAzLRB5e. S) Ȳ)-I 5-]2Gg-,bHhlmtlV Ze=;%=MJM!Ϙ0~# ^!/l !s/h)8}mTP,4g9QiQc!F"lK R&QޡO82e.4fSrsI:O[!>,+9,Bj.C BM/ȕ2(Y|; ar5 J.imftx>X?HU6jڻ o#9 +5CygLi v1*G^7U__OYAw#D[!@ ЪIϘi2 ݽQg`)c88[!BDlf^)9s"f+Yämg m2ǀ6k~7Y9z݁^&|o!9bt ']-;F5 &Au:}pXAc@TI4i^آh1Q'~-[ijT:fw0dpEY[I$ESMNО2ۦ%f 3mNs1m2 '4GO IÂ#~5c92SnڝgX4, +5@׮0!eioL04i0ۤB>31Nl%Ⱥ# @w~IP }Yal|tۺ밎ZBCHt_~TfsP##ٍgLfC羐2@-*-C!wӸL}ub?錅U8&ܥM{vP!{4LQдi<$|f4c{_w5ė@}!O_ y^t:" )}*ԻYgc @b. uR%U˲TPC*ss5 u:h}Si;9%\)HD-#:Vկbo(B@Z_Bڊj!m|dm @ 9]fD ^iM΀\Jm==v o1JlLr҇ T#ՙ @7_1eR13:X.$SP h?r _Kl HgI4izW{1;l tDSDOq9abe3&aGgd2%<0N=&d^&EsW|2ˎ RtI^ ^7S棄 ;0On) 2;.2gDΠcPs@P(d9X6 o~#$*$֓qdfw%b @֯L%aej_҅OGTn浭7Bъ쉵ƈAJ癈z%d mvgZT4ڝe+am7;*5ke.G3&+z3c:ARw]`!x9Hw@T ç/h1(}ƬoVwQYE`ۃ|s1QOxiAm$$&ϧ&t c)'.9]hdr*}YmV 55{ww@ݦϰH^kvd~y@9 4Ks_Wv޵eޠ 33tL=3.ik+1ҐM2n]mE?x-l74ն~krb @A]Ӳ;h]`;PF=z[/C:2E#$.TaYiYg`d7t>C%Hr{h%Ҟ9zjCpCXۢ6ǜnښAnvwD6q]Ɉ6ͣmK7<$߳3f"Ȓ?CQk83HtbvSR. a_ 2]rw\ Ԯi-X"Kg(糎vڍYdk9ęO3K#.ksH#vZMfЫQC_)ˆLfK ^NwǶn7GюS"@GGIUaY y  b3; jzc5v;\781Θci,Z ;vd%"-g\p)w̴en0AlJ[|<@FhkMna?9+Tш6^mO(w%w4p]ѣyy\hHoUD0ptDP\1ٽ|f(<]AVdNu w5ٸ˝[9FZ @Nƞַ\WkDTy:A, 3ڥݓBŧMfTf2W[덑@;P *YL1P)t3ޕ%~K}+w| v_F?>TGy REuj=tB4G}x^἖*cJDE`6pڍ?C5yS_k鈀BrIY!{kB =TDCuǠ2lɠi[;ڶ,N8l*stG‡j/%ҩOl|A-K*G%hxM3Yf.3b =In,3zcP7ف.}>hgZA]6cg"ΙȞ C5a2@e K4,[4[ӿMQXVL &CNpMaCuǠb7wO6#|Y}hO? ]iYbgoӤ д_6쾩I cU:OTdž=׭2\wixDcЬ9| h0 m0v!Ƈc#BK>H{ "OئV,h?kz?;h$FStm Yv9FS3!9!B{3%ѥb]L[-e*{4S#餻)u@Фq"WxyeSG/ri oL$9MKvZB6nRLG#AA33fBPpEo.C!X)ygfJȚ0GOGdg^b:6O qb<P;+iHۍ.4,ꄕ؏YZq3&\ցvBԥ]?8 ;ڰw*c~w :J([7{z|vyQgJj8`|A-K2:_GCg(c|1l=zƑ3!>I^6t<#_{c1T^p6ı3q*}JaM499֭vܮ+uA>2hDIȲ-st3ȳ.9+y|8N){Ε`ՏBBil+] V{ \z~hk-iDT`UtzLWiWQ e޴Ѩb]Uɴ防o1ˁ)ҷMT*NUAzŘeGQ b}MNԛN :C NL~nYE F1ȵmJӓto:Bƶ#6ofHdLh9\pOicH;[ PTj{l<({tMg>e:G*~sAYըG{<|4Z0ƢdN< CsM>I{;6sЬg vgk7fjW P8Pgk WDƼ 'L_ ]k 6 lS4u+xl:sN]lhùk5wEa >@"ʾ?%syιT7e.G^/f.D |Gټyip[.AJ<ǩO>j]GEkpܙp&mȧ>F٦~-5d6\&5{4a@9qd]Pl7g;s'uDOn{h:ĦrE?7ϛ>L8k쬖iԲ)PqʤS3mɈ ߃z AG CS#@pGQs,9z}=% ME$\5#l`ץ'"s(뀺T|WdK>G @&q@SL|K\77qqjC#z=G$vʰdpٽi,#9srmIW#*쾈-vшs'Ӷm sN'/gi:vLf@Dڇ5NBnxd~{L(Y@xh)戼k!RS ډ*v9 ^Ϙwhˠwdfu$}݊Dq@e2L`g叅 /V1!d"[)ui٭j!=4$B>gzRȊo8'q{-/P|'fvhdT) Izb@zSm͆E)qbyCj6/D%MwO#  msڹk_ O\"4};-N?q`2{|ה)а9<;.a2NF9]@Qll׶N\Iˣ1Jku5-&3NdJ8m0N4GZ[isQ,i{%3Z"䖐o2r(b4%OŠ1KDg$3ޫ[p6t]FB9z 7Bڳy}tȽ 3{M?]:C<@̊r{/G;!TuD@:tvP:DR%HqC%G3z"iOCמWKNآy [rQvp,ͶMWG{ &7S0Ljvc!#;5N;ת;k&¨=L4@DI,ڞ|kR^Swv)p]17v"ROJj:{-=wWSg'C7c'ͥ%su/f02Tri h'˸dT3&z; (,&z9%_Y"<]qݠ%&dElZsM_ހ>'}ŒYKez>杠Jk{k-?ڃ 3t7uncԁ8de{E93W G{zwdP{pϦS?5G/jeq7vDvo\S#w ͂S!`>zLd0fM W13k c1D[VA)J'oDk NAp6;0^6@vl @@:>!{;6锹{sJ~9,4#S*/HB+}nyqEeěbir̔5 #k&S:;Gm|i2- ߟHguR|v#Ia "&eҿ'M Yv^;lK̥5mW P>iv FO'-\{}|K=''xn!׮ ;wTZGev6]3=ud@ _(Ҝ2Z6[-pt4"(%bTs%0Hi ]a#ߺK&Ҧϐ7{ 8LJh:[v Y3{MO;i>P񰇦 h~6n,=a3K9 Q?HNkt<&DYztDAcY^%L{Y1>ҵ݄/E7CmN 2/.ph(Fb7ϵ*9'}Ux`ף(p+@G #aKH"m>H{%@aǦLfcI|; ږ\3}d6?L@Nrt1Kzϵ3n_ vtNK: Uq+A\t`,\{j-5B*-&zI=.Ja{ʏYfC^X4c{:d0ng N8+I,@GtR4SQvi {#v[(d_=nG wkټ%@;]s}@c{:ߡ C;"CeG#]A V|iXw KT:rI@B=aTCDYnY.i23`풬&s{C Abgra]?'ic2hGiF@PIi\#S.!OXbT!#c ٮ2[gAbQUN^S'P"-6NIn҄عfgizvr#S5}P)(}/E>y",Y.IrIj0նdLfvX*@uDg51%%,\q?+ʜ1iAo^Da=c=T|ZH9h"E-O9}N'u.8|<Ӧ CuFr"e%2a5KF_5v&J1GZ1K I{4=B;#>7{Gf1:~ iL i[*!KQ*uS~d-%Q5a7P{U>KR&$&=-Icd?Al@#q xlt-i}]~a:t6*#\lmAvGK|'tDU,[Y1/tK7"\1|)$Upl=Qˣ].}au׶ P;Q5 UD|gQ)0Qdl:<^l(,o!`:O[0i[j@#\Pyy}b_X8MMۂPcLn{#pd֪]=h9&o/ru@i9 ~ k'x9sd)QS̉r @6}q]&4u"ofC8 >TD(^S<@mXudϦIoNoZ}h=stF=l,~l ZtH0T.*"@GT%J15V]s^ \?g]~@ߛ1eSPDz3#}/Kg6MF첯I:e!Ԧ};Ž6kA?aۈ:_;2uaPy@| U:tƸ6(-fAhP!"-2KFDΈ,Xd,}65ZY$A_KS-:,bEP7mK @OB@GT4iy ˺MzjGrPtuӶTmP^/K|'tD̘ީӚvu%eXPomK%B|(dw@G@\r2@ֶT$?lR#\ @-'Gw,ig>wTA/q$hgXK9ﱊ!:"| eͤa@}-'}5Q#Η\}b:"< :OCv:"<G|:OC#S.y舀:O\!ur|v:"<uCP)| >tDy"P)|<CtDy"ur#>@Gy<G| CcJr^KkkM( !:"<UWג6 !:"<UWS@@;@G@\!P)|<CtDy"ur\! | ԱNC@G@\!P)|<NP@G@\Zo}%@G@\̣v@_ tDy<&};@G@\cJzurj3)~@_ tDy< }-P)|nk:OC5߮6C:"<nLZj@G@\~.E5 Z#S.PQ=m0 >Tsy{R=k:OCǤ0Z#S.PM1ek:OC5ǔA:"<tSz}-P)|mJzurj/ U >TymZ#S.y#S.y >D!:"<G|( S. >D;ur! | >d:"<G  >D!:"<G|:OC#tDy! msyvڅn}[m_Glsyv6}kur vQ>NB9<@`G;Pɷom 9<@`G;P3O: @ `:ONTh0Vv6P); em0S.v̺+]syvi|շsyvj~_syvjsyvjLi#ur #Di#ur pL6P); *e`:?FQ%,vd) VΟ(vBF@MtfN·}MN@ݑ2QxK;!jqN@]2Q9c[35JҷkZ׻(; #$2QY[EoR(; #$2QiMfs\::%\GxEB`vwPY&Ju+0jFɈ? N@ݒk(m /j'`8- ;A!;; w-eз=eY>1 :: .N@}m(m=IZlKk Duv j(mCʤOs1ÑpȆ&˿?MC2Q,l7оf{uS4Pfu gP; #\&J.v'50(Iu֖ '@4ey">;\1,3 /lM(w @!eߧ(QAh]gLzm|-NAٴI! 8M! #384@!'dKU0pi 镐.?r{b# vbEa` ,M~LW܃`O3*+.2=!Mgz; #VFPaȒn>|!A2")ƢӬߋ#dƎ,ẒK͝LJ쪿@|ĪT2v2SH \Ӏ`Y?3EʤD "$U5YS*\6G7f3d׌~˰@H#̌ϵPgd?"9ij^1߆Lz":T AEE>K]lYⳮy?0aѲYzN8IT9W FIAΠTVҬiwZG#|8mU_l@3O- !뛜t@-0ۮ9ٲ~O b{.|Nw?k<}ݽ߹6s)s6_7T Sc=b5vYCXk$XS8!4B1Ad MINA:m9pͷYZD !&6/c{w \EEDD"@ V "Rx""@ V "DĊDD"bbbb"""W]]szޛ,)U3wH7}OFKP[㻁 i=r_x>q}G'w¾?QOZVoٖן9~J99$jrt/$XQ\?;ǫ|;Hy^>'V)C[Eh@Kmg>rHsi(eih@3&Cزh0۾rp;)6X4Ik9x]ɧR~C-R^^?)xW?騔8v*8/HrVαۻ'|>[{^3{ OGԣ~N@˽y]ŷ#A쳰LL]ΪD̓~NղJ> ۍyv[zVh++u$̱VHk)4~5jtq_u,;E^)U@_J9ɜ3cl R CYO{pa[?a7R7J?G}>[zbWW@5@[~vC0 Ͻ^=Zg̓42&'Nޖ)9GY;x3;ȳ$1ѱ̕V(9= `6YoЈ L]yI%aQ%ZNHTl4q`@aQO׍)im4g9ukKE9UNON&eԘ٧!Ǎń}Rh /$95*`!P7~6žѣF@V:л2Z<ޘQOԷGOafC>.|&us%=dƞ4mDC̡ICv« e@idISc [Ŀ)yh}jO`MV{~jWK f,6Olo'~5E@}s>E? 9'K5 }/3:5u\$4x5Isj(|NԷ9?2uWN4{:8şi_X4Wmv=H]w_}6clMx>Ч)s}/^j'`'*!`# ^ SZ2j5ɉ<>?OXiԥ. ڀ;h]za]E&xNJρU6γܸ:|w~>I<~q׺4'E(>$&k7&S{z?6zM&fzll+gՄ2~8 E\EC^@k>% $?nel`{4Z.[<:!E@a9q8Z9r2_z`P7x>I9x530ɳOݲi|e5Og?|;.ɲ;U$RqPk0wZd t>[!h 0c˼b_wg|a|PL{IeuIR{f<'jO7z귘<[ S̡_R?iy>Yj'*{k;JP?uH^q%p: 84ޫ5NŸOӷaHq\@{.K~kW1\N:I| 6.|.I;\]!KCBiN~üO-?[ixd$uE6W{/ߘrXǭ}nM?~x% - +Ix][{'X隸e8qipjY7C9xcn/ UION*Mr8f+vi<Ͽ(so[X N~~+߿۩cBhz_ v,Dɓ^Qs manifest [label="1..*"] manifest -> config [label="1..1"] manifest -> layer [label="1..*"] } image-spec-1.0.2/img/media-types.png000066400000000000000000001156451414252520200172400ustar00rootroot00000000000000PNG  IHDR8gjbKGD IDATx\?ǥ2+ߤ,u͏/!͔1W8F\niLC e sLcg3[dC~NDtç~]wۭuz~5"" """""2uP`CDDDDDFT]RRbbbJt;vT; "" U#G/T; "*ѰaCDEEK.jBD QDDD aoÇݻwW;$""ރCDDDO}AǏ"""zz&&&ؽ{7"1Qԯ_ILpҘQM I|=ׯ㫯*R6vXܺu GTT^z%#ڈ `aavVrr2: .S;gάYR,]۶mƨ%jDTe~W̝;cƌ]r,>Ē%KлwoXYY8gSϞ=_}&$$`ѢE1bBCCocѻwoH?ÇG~~aQ-}U1>իW Nj,<@֭5|5m۶Hw888`ÇQ~}8::ܹswm?"RQ_>< `ԯ_͚53DHzKII֭[ ^"0pb_~e'7P^"駟{+R""#we̘1ڵCxxVTjj*<==`v ?@׮]1p@8p̳kKDDՇ U,aȑ ]v-OOOX[[ɩRϟ̙ѣG_~S^/((U0|pL:=rF`L0ݻwǀ_qv___i;v,ЩS"qgggC?Odeei~w 0Cݻw6mڔz%66o6p1|سgSMOOG@@Ν V hZh̞=۷ǥK0dXZZSNعs' <<\91e,X"̞=z7<OF-믣G@^^^Q5"RÇÇ|p&MHdd9sF,_\ɓ@܊ϋt:177ggge˖ r劈,]TVUL8QKf$==]ȵkפaÆ@>IJJP ...""+...2qD)((?CLLLKy1eddHǎNrrrdذar"ȁC믿.T}Y`R,bgg'wޕSN)'GUl f@޽{zͻHqW\???iР˧~*Unm?"R*_jE\?y`٪U" F͋wʕ."%{{{155U^wqq"""@z)""G%|KǎݬY3[|'X2࠼OߘN:ү_?ϊw֭ȨQ/u(͛Eʷl"߿<)u6m$dĈߓBE{]}EPJJ,\PZ>#ԭ GDj%jDK׸qbu6m"ZCڵkd,[nH[8;;!C*=FKKKt:}۷/RN}S^0{lDEEյH{?3N<.{aŷ/ 8rHy011Q 4p…Rr*<>@>#55*R"z;v /"1|4jԨ!!!X~=fΜ VYf!((-ܽ{.]*v *yfǍ7~KoL"7oExwp\p?~|7Wna"TyssRU'.Ke{RyJy ;;;,Z޽{+]"z3فPVF||<~7Z v‚ `jj prrRnٳgbx}]uiڵxװa8qH}SSS9'ODdd$^ ggg 6L94u Ϝ<k (u\gnS{*ߓWJsMm۶X~=qQ5P8ZנmVHffR-cǎJ+ehҤ{JXX9|\zUz-`ٲe|r!IMMqIXX{^"%0o/HɩSD6m*deeɁq@.]WLqqq3f133Ǐ[ԩ#}uYZn]侘3ft:5111"֭RMHddݻw%##CH֭+_y.R,n!"rE?~I.]$<<\rssKԭGDj`CTj/_~E̙,?j(o֭[2k, u֕Q~eUӧKJJ|{Ν;"""M4=zHtt|Gbaa!r]vI֭BLMMԯ__6n(""/_C4o\&M$o߮XBBBŋKZZ^Z)3gdggˡCU5j$vvvxb۷LHZV^{5ٻwo+=nz׭jG#'U%ooo@DDʑ?>Hn1c zE Yxt]bb3˪ڈDS "2yawRhвeK vvv*Fƃwѳ Q9w`͚5}R~ ",,LЌ^ p'DDDՍ -[`̙ EvЧOx{{#66pttT;D@\~0}b+UރCTx :V#"5  &8DDDDDd4bɈ%K噗vTRRR矫QaCTK%$$`ѢE1bBCCֳgO2U9nܸbŊnX7!0!`ʕ%fggzk׮UK?۶m^{54o޼JگWZ-QPPP%}SNs=F &{0`GE>ۨx~w 0Cݻw6m`֭\~cǎR[ظq#&NĠgϞ4iabbLHBDUj2| {{{tbnn.Β/N  #GHDDXZZ _ʭ@ED$77Wv]LӉH~~l޼YݻˌUrJ GbjjޱcG yyyJYՈ##RHʕ+;wN+7o{oIhO(͛EmٲEq=NfͤnݺJaÆU$:R gΜ)6'be&8My~rJSRZjUl666bnn^򶯋ҳgO9z(o8 '::Zԩ#>8~ŋ%!!Ai߾(Rxr銵cmm-dݺuRPP s&8DރCTiZdffbڵwrrr^xُR6h SmڴQLMM1n8էÇ7n\_Fާh`ii۷o+e۶m'4hP$f???̘1Ν^^xԘl{߾}WNW3M({4%+x4 6m"mnݺرcرcy߿?GK9;;z_q<67^za駟>Yy\lV>|%fff[ƍԩSuVYT:v^|EchԨQﱰm۶RuT IIIIE x677׫N;v`ȑ^7n;v`ĈƍwV}MyVKy7$$ׯ̙3j1k,aѢEץKJW)??ߠ곍GDpMhrssٹsDDYq2)g///puu֭[ 9#"ɘ@cƌAnnrƥe>uԹsg%KHx֮]^{ 6l'/-gCػw/Ñ@}Έj4u#=&MٻwÇիW{ZbbbD>>>ҭ[7t""zRn4/!u"~ yW%$$D͛' &$22Rݻgggiݺuf̘!J̶@\t:9r:W^YlYkԩSbbb"M6^Ҹqc .] JƍŋzMy~rJꫴz@:v쨔 ~9xޭ[o%,[lZ:$"Fx;;; ƍ0y]_qbO\\xzz*3Fe}B?R눔@!]zr5&²tVZ={Z:v(%33~߿/.ZVe…C,^XdJٻ+Fc}roddkNZn-bjj*~qF| :T,--y2i$}2v}`ۨx/ڵ%•Zh!|M1볟駟dҤI@dŊrԩ>M@v*K.QFo$γ>4"|rQUYTCTY?>ѥkn1c h4prrꖧ*UԈ͛7˗/ǽ{2F-[W^S1ڡpa#!"3p*Qe;wf͚"K8q S+Zp X "=C233ׯO<"u e˖-9s&BCCѮ];ވEhh1m6\r&&&6m!t" 7oo&L!?"R!""z?ǏW; "`CDDDDDF  ރCT mjh[0aCTņvTzr$Tݛ߁DTL42DDT"""""2Lph0!""""""""""2Lph0!""""""""""2Lph0!""""""""""2Lph0!""""""""""2Lph0!""""""""""2Lph0!""""""Z&%%ڻ|""",&8DDč7[[[X`kjήt[}:h۶-n2hѢAj۶-Z-[XYYaРAy&7nl""#&&Oƞ={qF|7hժڡQ-*,22=z􀛛1sL 6 7nP;4""QXl\\\;wjFDDѳgҤI\hР6mڤrTDDD0p@={R%jDDT咓 Vjj*<==`6x0!"*AVV0rH"::]v-OOOX[[ Ǐ/ sѣѯ_?)`ժU>|8N{N9h<@pp0&LݻcEv킯/ڴiT;VVVԩҎ &&gφ 22SLp-L2SLAfff㋉AϞ=1i$Gll,`ʔ)X`A%4!  ۷oW; 2 GGGy+1??_.\ I&)gΜbkk+˗/49y77"=boo/"":NYy}ٲe@\"""K.j:'NҬY3IOO/q UsAA\vM6l(>$ """{n E.988狈$''ٷ}vnS"2~=&8Ƨ悂bZ*R@lllܼ{W\)"(񰷷SSSu )))""/gϞ""rQPϷ~[82;kYfRn2)3>kkk ֭%--MD#&8Dd(D=q4mEʵZ-kbɒ%A^^zn< ,gggȣ?D2d*1?ٶF%t:]D[5ԩSѧO\bCDT;_|?>5jT_3g΄VŬYE޽K.!++XlH @tt4\]]u*"*0fbРA-*<~ V®]`>ZI qgϞEHH*1>{ѡCݻC```999m|%6l*f@\@FFrÇ<:`bb?q}۷wAZZ ::m۶N|8*_źug@RRLMMaiiEѣ@޽q|dff`ˆ  4@&M8۰ѣGqMxyy}=ݻw/͛ypqqap%۷@6mvR䓓,d|7SSStׯWN.]`Ŋ_ c8qrϏ%ڴiS͛}!556l@ͱyfXZZyػw/~t{ř3gc𰄈*K#Uqh4ؾ};A?>Y[n1cRRRTzEDD`ĈUrI$.D͛7˗/W.%-[D^`ggbtDDD6.2@DTΝ;Xf n߾8q S+4""g"je̜9h׮ooo"44jHDD%jDDWի  &8DDDDDd4dDDD`ɒ%j+|%" .ǟv8%JIIvDD5"#EaĈ -ZϞ=T011ArrrC,Y{U$}笺+i3220h ߿@Vw^",ƍ-VXQ/cCd$`ʕ%fggzk׮UK?۶m^{54o޼JگWZ-QPPP%}$"prrܹs1nܸ* |z-agg{`|>r1`]"""0b~x*$55HHH~O<$[/?C^JDD5jpyxyyaΜ9=z4뇸81{lo.]!C`iiN:aΝz) &&gφRŋ3f̀>#/ ልL2 ,(t`ܹjVEjj*Dv킯/ڴiT;VVVԩ?^d,۶maaa}e˖h4XjR'""fffꫯn;;;Z7o"++mZLm09F׮]akk($&&prr*6em_(((U0|pL:=4<@pp0&Lݻc/uUQI)SuL)S 33ĠgϞ4iabb?%)oޱcGsYظq#&NXX~Q۷]{{{tbnn.Β/N  #GHDDXZZ _ʭU$6GGG͕ݻw)t&>>>/""7o{2-(}A,XKNN{ɵkהq}$@\\\K֭MU.\ &L8q!&&&RcycKMM-wKT/??_.\ I&)gΜbkk+˗/49y77"m}-[&ʕ+""tR ZV3qDIHHP.͚5QU1Wd[6%7>T$''HGiڇKR޾R^<ŋk3۷5oDD7 ShrJ GzbjjޱcG yyyJYψ#S/EV\)ܹsJ\ټyܻwOxK:z@ 7o,Ro˖-@v5k&uU<( 6"1wA<<<9sLy-O>DٳgP恖>cgҔTVZi"$%%EDDSDD=*Jo+4CŬ~T|?Y[N 4?g.+N/JBBӾ}{PDMk3&8Dd(z!Jjڵkq= //OylРA .]$+?ڴiqxƍ)<i4XZZJٶm 3fs`ggWYh߾}zu}>ckڴ)JSRʞGѠiӦHLL,R^֭;H;VxtI3N>W>֭[qaԩغu+֬Ysss/>\+ѪL3LDTxQ58v^|EchԨQﱰm۶RuTxCuYI$!))Hy t:ر#G,ڸq`nnر#FP7nܽ{Bgl̳Z۾!!!X~=fΜ VYf!((-h.]TJlH @tt4\]]u U}ܹs=]"GGG(R&JVX &"nLp1cq)|JYe-O'udɒ"zIJJž={*oNNN+R^|}c.|bI+C5jزe k 2D6 999cIt:My֧PF||<~7Z v‚ `j褿rΞ=*|>{ѡCݻC```O~>JÆPX &"v\GlCiҤ{JXX9|\zUf?&&FD]#ݺuN'"W,B@Zn-"oРW_}UBBBd޼yd\^nnn@"##%..N޽[,qvv֭[ƌlkk+Ew_N#Gos{U111e˖{O:%&&&ҴiS%++K8 7r%Yp4nX.^'笤J-cǎJ*o[bkk+eСC*"nV2n8 {OݕKGU\޶.m޽+N)g|WtҤIѣHPYp϶CuDO܃SX !"C7 Sh4DGGG}$)wUJGeڴiQ^/ӕW^-׮]Sn.,KOO8 iժ)7H={Z:v(%33~߿/.ZVe…C,^XdJٻ+Fc}roddkNZn-bjj*~qF| :T,--y2i$}2'q֭*yѢEn;wǨ(\觟~I& 133+VȩSJ4 NYcgs&8Dd(>24 oooo2/ѧQeb?Gݺu Č3rTNNN=41b㈨Ҹ)͛˗޽{JFA˖-ѫW/٩UV" 666*GBDTuQ PZOSΝ;XfMe~O8@@6-\`1!RQff&quӕgKTlٲ3gDhh(ڵk>} U!gm6\r&&&6m!UރC }QUڼy3}0aރCD{p1~x "D"""""2Lph0!"""""UԈȨ"77W0UԈO?[n0g}ǏƛovH5ݻ_{}:бcGâr \~]`CDW_}N:ܹs矱l2sATT_N:!44T`CDJzz:&O ooo <;vXzSNaر;v,q="""q"5~';M0tPC"1n8c4h! xÇ1w\G8s #ӧOx70yddggU3!"vҥKXb|}}W_}ɓ'e˖ C׮] QY?ԯ_'N`rS >N z쉠 U!"sU;F`` ϟ">c̙3ݺuChh(:tvXDDTxW_}.]8z(b~~~8~8ѭ[7lذA 1!"1ÇGLL uvXTCt GԩS1uTxyy!%%E 5"z=<`qi& 2D퐈ȀxY?puuř3gP^{5o&&O,"""aCDϤǏk׮X~=֭[4m))) ҇!ۢ[n۱cK8|pg}k0!gJ~~>ѧOj ō7[[[XR}-ن3g Ν\_Uo "zf$%%W_EPP-Zm۶dffBlj-Zڵk'o߾8A6gСCm"++ ?X鶉 "z&lݺ/"RSS9sN}9::EːmѳA111C.]fTvCKm۶Vŷ~ +++ 47oDƍ >`CD5ڝ;wwyǏѥK"# /ѣٳ$&} _qixW1w\t:Ub133DDe`CD` IDAT;wz bкuk"*yصk>SGW-""*"R`Æ ޽;ɓŖ6_Tt\\7n ~IDdXLpHx71m48|0*fK:\hqҥ2i?30|륮ffΝ;ѩS'9s?-[uV̓b̙$|ᇈU^MeK*ӧ-"055Ŝ9s+WSN/1~IDDHeLD6lѣ駟QFjETi<ܹs' },--VbCD"::GFFF6l؀aÆݻǏ)l7QEPP2!5p@Ƣ[n߿?vXDD Q9{,|||_S;$juVh׮ХKC""x p=znݺerCΘ1c+++իI~gâ?G MD.J0S2iiG$;ᅴd Q7eA 9&JdbbH%5*̎A|?Óٳֻ^ًkFu >ypQ8::;,ƌ_~8tcԨQ3cpcfΝxgp-aŊ0333vX qid2bV8GVZZiӦ&Mӧ1l0cX2de˖-3v1èbС0|pc{DGǎQVV|ifff< 2d dhciZLL:.]§~ 0ZB:t?#PPPmۢJ#HбcG\p}5rČ1fXu~? AAAS/@Nk޽{a0cƴiӰ`<|/0ւ?~SNL&CRRx cc1hƎgy jZ \HDDD`̘1ؼy3wnc1r//0\x>>>r 6n???cc1fPagΜO>ic1ƚApk_w}GŒ%KX0c#f0ܹx l۶ >Cb0yd@BB#a5G2v5)WWW}Rݸq{6v͛7+V@uucѾ}&ѫW0Xhƌ#G… 1&!&FC敟ip 111HLLZS/c"^ /g_n c }`ڴix,ٳCZh$&&?lܸPVV&Rhkdԩd2͛h >i_Z& XDDuV"ܹb 233#wwwy#e/2Z~x z7p@z_?!"{hݺudmmM&LB_ua~m׮]Ǔ u֍fϞM$(<<\ϏΜ93}Q.]h_ÃC:wyzz5уiժU޽{rJ!M*Raa!ik׮ ZPPߴ/c-,2 ߋuQVVBDsss߿/Ưe˖!((H寜coMc 011AΝK3 X=d:zjBD¹s Sk׮; X+0VOs͛7Uv֤ 99921c!>>qqqZ_7110x`?^^^M!c18t5A"RK$VVVpww'^u3ƚ|>|999XdiJJJ`mmm0Z?޽$̜9ء&Ə1&BUU&Or@"M61b.]ӧO cLEff&/_oool۶M#F ((1l ?? ?xxakkk2ԉm:D'.\}bAѣGaffD??133ӧ v 'N%K0l0a?-gذah 1kz)DEEi|޾"qFSꫯ[nɿ}JҺecfn[C?["LTZ빦 k>>֭[',n-^fdzg̙Kj- .ĢE J Tb^zӧO-,<}v;HIIA!H-333ܹStJChh(/^rTWĴ6s)SiiipqqA߾}/ ++ &L@.]T-u_扂hxyyaܹh׮ʣp}\3gİam7ubٻw/ HeTWWsss$%%錅ŋ(((ѣaoo:Wύ7vffǎ!1Z5///""gR.]hԨQt޽&/5'|ٙ)##:vH( ?N dccCȑ#z_H.Ӟ={T*++͍|||lBhϞ=:U8pqM唕#-]T8.??ޞƍB>ͥm۶>|jٳ'رCH۴iNHɡ3gB\.ÇӬYHPN&&&:SխXo;kj3M4W]]M999,--)99.^Ho߾j**))3grssSS]b?>S@RT8f֬Y)uڕJKKօW_,ӅlK^^޽ڶmK(""bcci̘1TTT1>]JL}tQEEEcg@#'18R`4 8EDTss@ TUU%)o7rb!{åP(TҢ]|Y8F.Ӗ-['}jQS/'$$Э[T%Rtڕڶm+Rǎ\%pBxbvՖg}FҥK*1:::OS71fh:NSZ=ΎTw~NݻDDty@#F "'N?{YRZZJo iaaa":~>h}bgm0ƘO?4<www;bR2 9CTUU +111^{5@NNc4QOKH355xŔs1JK/8~I$ؠ@H۾};&L0)˗/ׯ_ǠAƤwJJ _~*ǵiz1uܹ36ӔD"AΝ:NBrr2O.ѣpvvƅ DůX,--pDFFG|Y}OLjL_Y981rsrZSNaȐ!ppp@XXr޽{?1{5$^u\t博|*++)SyVVVAbb" (,,ML;/χT*E`` -[˗iWj'X?,,,f;v nnn@1cQz~UYc0rHܹ8< ޽G"$$a26mrrߤ~M7t~Knn.Wx>|Jr18pQN:صkƍ'*O%'''1jRYY)[LĴ AqIDGG#)) K.iC9NNN.]zCL_Ƽy_#::X+]y\X3i}9BsSNݻw;;v}B}LBDTLMhРA4rH>fϞM?c>7|[oEͣ|[FhРA*ϚIgop &!!A?o92<x,-- 8pΎбc³ʉ |||hСTYYID$raҽ{ٓ\B^yP&͍Prr2;w S^^NԳgO**ܷo_ L'k.Iee%M2ĄVXQ5}ygdd uܙOO?WQxx8YXXЕ+WDML;WQQAhB:mP{;qD۷/\bcci׮]ta*..&"= ___%KȀzhbQsuЁUαXm Tޫ)F%]J>u^Ӕύk5"O>yK>>4ydTw̕+Wh޼y5dڵt aB7k IDAT2Ν;GdmmM=z*))}K.4`I&i,ÃR)QxxpMA%%%vZ!?$DB:7((R :|0RNޞ"""_{|m̈&O g 'ڮy -<.TݻMWYY\\\pEo߾g/D;vCxkUǮ]VFAUpmYfΝ;XfMv؁'x~~~߅t333 c1 ʕ+1k,!M"{9r$<&"իȃl1֚v[Vx7#G\\\4SOkںwTTT91)W***zc{T/__^e_!!!kX9>}qqq={v͘^ϬK}۲(?!+++@666qF DD]t!;;;9vy5Zw-^XxwޡcǎKqq1͟?]vJ^^^i&aarׯ9::ǛlƔ 01[ݖGΝ;4w\jӦ _]eB={RΝUK%^^7o|c1أk۲rdؽ{w\\\ o_~nUUUxtoaa]vÇxwDc1 8jd27Iݿ,YΤ'|ݺuC׮] t7oX_|'xB)SO=-[ѣb,HHH@ddCi}K5ϻwj}XC1RQQ*ߘܿTTjQz՛۷o?DJJ YZZnܸӧ#99w֭[wȑ#qo&3foAhh(ϟ___ux0|pTWWSL^sppoA򈚥%o>3Bk7|rM+M<3cm۶6bK7&C $ 22?LH6׿KKK ߞX[[cر#fΜ "† ɓ' =zMо}{:tǏݻ`oƺx{{pww;#ᵎ;ǰa>cǎEDDvڅ3f憌 deeaҤI򂷷7\ .s`Ν k֬Y8|pVZ6XSO!**Jkχlrj۾};^}Ut oRYYY Mlm MWld2RX4}nZ[ ݘ#i-3P]Zd~$ (;b%%%puuťK V:"-Z䄬,0M>1㨨{ K"`ǎP%S#ZPZJe;ccEqq1&NuC9s rss1q&-`͌3x@ފ#j nˌ!;;&MBpp0NQFܹs "aׯ^q_QhCDHOOǂ )d2DDDpssúu$m@||<Ξ= 3g.]R,\-T*'R)ADHJJzbL>R۷cرFJJ wDhᘄaΝ󮨨T*BCCxb+dU71Ĝraʔ)puuEZZ\\\зo_/„ ХK899iK] ///̝;ڵD"~ ̜9Æ ;Ο?e޽D"-[9tBDŋѿ`Ѱ}*11SUƮiccooo̟?GnЦM:uN|kA}c52c74'|ٙ)##:vH( ?N dccCȑ#z_Pkd\N{QI$777j""ڲe ={WI}bM唕#-]T8.??ޞƍB>ͥm۶>|jٳʦx6m"w i9994sLR(4|p5k) ""DmgMm㪫)''%%''ŋ ۗVZE%%%t@nnn*y;+V ѧ~JH* ̚5233]RiiֺQ%22PzzMzcˣݻwS۶m EDDPll,34Ƨ_.6V ѱŬ'_5&}رcSf;:tΝ;Ѯ];!/]}$a1":u C ЩS'ﱶQCF HCU1UIWިޫKJ$&& +++ 11?}b&E_~C*"00˖-ԴիW5URPQcjn-,,f;v nnnMucƢ>Y|00p@PIZ ;J}gyAi5jQָ{-!''۷ B͚´i ˅n*X!)ou-*uO?4 22Re\۷^>|Pk9o3{t> bc>pãk:ubcck.7NTJNNNcԤRig1e[QQɓFRR.] SӚg]t 111fmmy믿Ftt4}]5h)GWT?7 6ԗ w{g#Gl\A\g֚K.￯9ÇG}d ]NVViF q}OhȑԦMNl59l-h̘1_Ђ Mގ7oޤ 277.͹㳴$t#;;;@ǎׯ +'R+ C 9\ht=@={$ +C'Ld:w)gϞ*sUUo߾@O\J2e imׯ Xk ܹ3߿駟~" @W^%"p+WvVo3Mei;Ѐ4@eeeuڠ< }wĉԷo_Zr%Ү]T\\LD5 R\\-Y<<ԳgO&SSS@:t]FǏ'֭͞= CS,**Km0!6md2 3ghQLR:t͞=^222tu.<<<( @k;;w$L@3f̠STT־*Ŵ6EABBFGݥ[>{{{k׮b-swV"5\0 *򐚚 ܽ{*++ .^7y-!'_D";ߡJ]ӧè%1 ŪUPTT$I$t#G}DDXz5ypSO|2e]wݥ/ɘ3g߿<̙3s{43.Ժ㫯¬Yt1gFPPLLL 4珵k[훐أR~~zW ..b9r;;;aMVvka$Qf!spxw㻻hBTwS4m-bj]7߿"""H666BN4=98׽{h³;v0Xi@ڵ#WWWM65 .P~ё?ޤe8|Mj0"_xwswi}јHBS髫6 u}wO.]mܸ oTRR)pcEQݥݥ/hj3}uզ1v.fffuݸq#|}}1w\lݺׯU'c1ZFûKw6>c.=ԗ؝5yW]54iΞ= OOO[n5Z{2ck.]]Z_{4V/1;/Ev~1 >>UUU 1Z{ƑDFF;!:?1v+--meݽ{7on2-g2wݥݥGc]JԎ(=.ԵD\:s*++=98ҥKk5|p裏 ʢ6mP^^A>}'4rHjӦAP'͚t=C{er>Sz ei#lc7oRPP?v\dwݥݥ6sNOeh8c-qKr;;;#GZbL8MZ3g'6il68q{,2.͚活*cT.^vv6&M`L:F¹s@DHKK Я_?\zƍ ц SHdu hqY9stR唖b…XhR)<==!JQ\\ "BRRЫW/c鰵qil߾cǎ5RRRн{wH$DGG $$$ ;wwEER)ŋk\Qigmm&#..SL+₾}_~AVV&L.]ɩN[:@2ܹsѮ];H$`̙31l0zMX +++H$lٲEcann$222xb=z4LfOl~i#1bfϞ @&l~Z_RR`񰱱s=To~PVV(4M}ZLԷ/~$&&,Oֽ>}PJ1QݥYsӜv6RWOOO4211g;$GԞ|Irpp Gٙ)##:vH( ?N G{/"ZRrڳgJZee%pɖ-[ٳGgJꏒh*iҥqHTTTD7n'Pnn.m۶ÅUWWSϞ=UPnXw i9994sLR(4|p5k`.KF_݊6DqՔCҒŋbAV:s  777<+V?O?%$Jcf͚uUX0F%22J""V٨\[,yyy{nj۶0g066ƌSg>XZLGFW&M|"RlVTThAAƼ/ܜڴiC0Qmi7{]U6Q>ԐimsmnS10Ƙ67oQFԩS6v8Z=('S255^0`*+oCTBPI"te\N[l~W/prs^(66PPPJjӵkWj۶J;vrOB… ŋuU[ޟ}K.ig6FqҔ+>ΎTw~NݻDDty@#F "'Nо(Qۣpս>}hƘQ͘13f0v T*L&"<|UUUwa*IDATG;LLL4~S}}J?3W^B)|}}E+cǎ,,,Tҕ{-?~\$ lllTܾ};&Lsss˗/ׯ_ǠAƤwJJ _~*)7FL:w @w;ǧ4#Hйsgdee;CũSӧ >zh5H9;;… E_, Dxx8"##aooG Ey>뢯4V47s֭[~z0kgii){„ ?>.]x1^WSvCGcWQck981ƚSNaȐ!ppp@XX:u=޽{?15$^u_ع*_njDbb"LR5___XYY!&&W'^S71l,oLL K̟?RXl/_^qrLc5kp1 ƌE {0i$={HKK+nZ|R֭O>OC+~׏hkjsciӦA. ߸( &k1vwwc=H!m߾}ÇZQEWIqFb>pãk:ubcck.7NTJʅ7cԤRig1e[QQɓFRR.] SӚSݵ]t 111fmmy믿Ftt4}]5hS߶n~]?Fqǣ !!!*l֗nϗe Bѐz/>CW׽Rf0X]KKK@8#t1~̶r"B :tpc˅J#ԳgO"ܜ+B111J~~~¤e}񺹹JNNsQaaarٙz2_ߟ\]]KD%)Sֶ~:Њ+꼦/ 211Ν;~' daaAW\]71fv\EEiʉeeeuڠ-}wĉԷo_Zr%Ү]T\\LD5-8Zd yxx'4CSҝ;wCrĢlL^M13կ=MC©S///ZJxF_x"R)/^ _SNZZ"""0}t 2&&&šC`ff?o:uֿ[:tHk[Xj6}yaҥ8t/^ ///aƌx쌫WO>ڵk077[7\^^O?N_]88x .YK.E||3fK.z̙3믿bÆ (//L&CUU:t 6`֬Y8t{n۷oaz=4ӧltG?Oܾ}{`BHR̙3...cg_{7ꫯb8 T>(,,XmoÆ ذaz- 0۷3>3ᛛ+W֭[ɓ']v_~())AUUUkĐ!Ck}ekCbJؾ}Y[f 3hͻ>u_l 6G~GѣNdM``~ij8hq10r9`kk 5!55{nUVV\x۷o{H$رCTQFA.7Y`1cUVl.Hн{w9Ma cFƋ 0EPʆأ|2`*Ku A\\\rvvvٳlƘfylTUgh[)S,TQ,AbaSAvaH Tk%ZQ)4E(@RYUA`غ2of:3N7y g.sڵk/_$e>lM}!v=HNN^oormܸЩSF'`@nn.FcaMk5v66G)Ǟcoknc1118{,jjj4ZÈ2 TTT8u3cikMB![-Çٳgs$s6Z憎;Q1gٷMqAk bϙ771ϻ!czh-c.1wZ:t6lXk5r#+**˜1cpܹ&-رc|2ƌӤ xXsQИm:u*rss~5FQBT^^Ǝ9s୷K/SN$>ٳgGtF|c!Gb UʐpDGG#88+WTN{ 8q;`ѢEv)))A||<Ν P $oߎHt EEE}hƍ1bx{{#'';wNCJJr͛ѲeKdffjλ?>͛r1u6-lϴayy91qD <F߾} 77aaah߾=kш73gD֭?p],[ ӦM /aÆӪfQ]v ///t:]Vy|);;ؾ}ú1o<7oСCkmfw&*b^=zDDD₲Zss…v_\\0.\ѣG޽{_|E"99"5BmmvزeqĨ-&L@ll,bccѩS'hGoY{B!ݸq8n8yꩧOXSSǏM6:t7op8p@) {M;,*++pԐ$׮]Kܱcݛ˒rJKKE)ݸq,,,d~~Ү>/_  PΫa׮]i&%"n۶MIpMFѨ)着*0ӧOh$I_tqqeWmEEEllu\MM /\@dVVϜ9Cb;vl.]?$,YB4 1ӧOs aǎYRRb-$kW./&=zTI Tru~lժ0))iii>|8 -5f*Ğ Wcǎ7HZ͊ yͼM0''k֬;[h'O$k4kR6Y;SsδYǨ|۪ͩ*&+DŽ|QkA!D Nrr2322H>rOWWW^z4l"Bl4-Ғ ϟWڵkUZrqiii8vӱcGjJIۻw/۴ir:ٓJZ||<Ϝ9S_gϞ=kQǀ_$MK?[=֥KZС,So߾M<}4p$_lٹsöXY.%%%o-X999bׯ;3 Zc8oߞzjF}x=%[1uQcG!c`0 9 q}TWW+pqqQ^}Uz-L~g@nݔ4WWWL2Es}sAE/ Y[t:͛Jƍwww: ::ϟ^Ǖ+W3حu999=zXעµ~=f]N___Zo~pdeeaJ:Gj#juĬYŋC_~QRxjyZ4T\k=[zjL23gj*xyym;?OOOaaaygʮk,j~4$#FԚə-V:nnnhܺu vB֭]k]{=8B:r}Ych۶9'|^X3.fZ/_lnZ}!3WYY-[`ĉ>2e -[`„ Nڵk6-ojj*֬YX ̚5 |>CҥK6*9a- |'8x /[ Y-*{0vX8q8|0;Vu޽^庢Vv}ƿgԥϟWTffɶmېD###Mj &feeԩS,((UNyy9صkW ~8]\\lҥK$DzxxŋۦVY KI3m.--ϫ1ce˖1--[n}XTTD{.z=pʔ)LOOϐev؊Oܺunnn6lk,εUGi{MUYYIOOOߟ$mM{w`` ̙3k)m #Ο?yyyLIIQ6gggf=ZK1TX4qt01P3[yץȜKh믿Nwww)pΜ9$mDŽG ك#78B Njj%\r%ƂeIHH`xx8Ǐw}wQP;ŋR6خXʦhSZII O:Pz{{K.Q6)kݻپ}{Ջ֭cYYrJKKǐ 111QԚbXBI{w^WCjyϙ3ܷo̶mR3))C 3?V?^6^Ye븼<Κ5تU+~7pEEEO-[[ni߬,vޝ]v7]]] nnn/I7GMvԉyvv؊Bպ9rQۜ111Q)322ǎ[G1S߸{%%%6c%K7ȑ#"󹙚j/''Fbpp0#""T\0 ann. pnܸݫuwjeۋ!gcњO?Ĉ`˖-|r?~aδ=$$111vcԜ ڼ$1&&111:u*{d1e}  !xƏo&4@prUUU ]v#,$quݻѸ}v׫}ř3gx5mGaӦMj"oQB!D?>>c*i:;wƠA뛼N$|r̚5KnnQZK2|q1BVxU7Y]ߏݻCHOOGDDD-1mc=r#LYYpU@TT;#DCIKKCll,6l؀ݻ_q lذAyUmSh׮\\\닌 B_cۦZ{dB<cBќۃ#G!Bl B!ِ!B!D!78B!BfC~G!|B!kr#nݺ!33֛`Ba+:w\+]^-B!h6dB!ِ!B!D!78B!BfG!B,P^'IENDB`image-spec-1.0.2/img/run-diagram.png000066400000000000000000000340401414252520200172120ustar00rootroot00000000000000PNG  IHDRb 7IDATx]}~ 3<{`X+jyzǿ5666iQF`?qV犲- [axQ.N}aۿ~s_w~~:s?|~cyyW޽mVyoo__XXXW;yrԧ>G|=\/ʙ, Zlin 9ͦP2~3_җy㿹v%K.ؿ/ާ?E8~!,ʽ\)ʉ:[V^={.n~ht'w׾7q'ܵ<կ~}v)O<y8?E-u ﯹvo,+zoCNEfYo{0;pGpݠ/ >4Zv}^E)O~o=mmUN os'~}|Xx/--uuE@w~z퓎ld_Y~?o= <g~gnU7,;xǹ  #wo(zRF@G߿#,=ٷ>Tqe={70>;|_'}u`@]9s?  d~ _Eɷ@z(#@֕}kWG$4`v}]e(oӧ}s ; *Soʗo|2S\li0|_w<⯿xwVBo.RpuXxe~~A#_||+ ĽGy͟Y 9/ȰŽ,H+leaaaivvuٜ+Z{[o}'?}gְyҥk?|c*ʓ\);rvGgK`uRkuz{955|o/ʨ׿v'&&mC-GyoAlF{Rk4LQnE^w*G?'>#Gn_gy42ɓU#yo/_O}~G|5JGx2 LpMF(>UEY.<S?؏|3g,P{|r;~V˕ڇ1IQu+7o6lIr8>XE9_EY@y(5z JQnr]=;'18o3y4u&lj])yVNU;egd;g`lt30cqFF;30tNhGqf:8 ::؎@Ǚ]@؎@ǙQOUt xUێ@ǙQEym̨ॢUcqfݢWcqfWcqfXr,18 pmn r]؎@ǙQEcqf0cqFv :ΌrƚKoCE/ʳ, 81Ng8cZyl - }1|./F~T=v 3/EH(ʕ5ޯ˅܋м?Pjұx\1;v<}1u?s (wǩԺ׵xɪ)vg{!s,Bxg"LÕsxyJ'l<~>^s(ޗ;U;pѓbj>yoji m;/.-Ul2H{??70V.#_O͇Aז.錧(ou_a xW(lm;xaڎ@Fv :؎@ǙQ bQ]^sّԺwpΜo4VYl L!W,ԺQ'J[J{S9av gr(S5r< {?^s8W*?_SG7䰚GF ZWyH|j߻oێ@Bf&3_`\ "fr:\?ݧE9Лb>'/>W_<Qj4Cm̈<Tjt қ9о} e[n/{c~Gm L! *هo>L9_tjv P曕鍥90+lj[s~χB<|T<>W Q51SȇC[|ɡz=kq.5i7%?y/Bdz[\r#WC|xbڸE`#lӎsvyUv/7d G^IOmភ};p>`J[X%_4*//ϣH}/'"(s=GXZW~YOyڴEQ(r#KxАˣ+?R7R吻P%Oxhe-_?awc bF};1#|xQQ߷ďe.vx\=ZԾJj_D-{*T~ YS7"M{+v`y :l\[d:d:?wC谌czFAr&ˣ,j-Ȯ7sIJ\ƿE.uϒ`nv;TyAM]ێ@F)C`T[LJZMͷ" y/" q>oXnt>Kk;Lu#[+H{ 0t~b]#{^0ϙy4z2xZiv4}e`#lr9!ޏWS\8FMOs/4ǟ<7GΟl&/WW]y=W+U-yK z&s:4AF;68 P{,Gb%.Wld-$V]WM#ķjFj_YeKFЛ_wu uQDj_8('*Ueb'7'18pĴGSk w^?3@iyrR ySN޻噏<_ ӛO,`;MN6r,^?g_V|J߀|!.x;I.rpmx.?~w>zC>Op:aR\ަ0~w\}CڇpNF ?NC :Ν-b9{g+|Sps:Dwa9H܌Y73>yJ_ (\00찎Jj>?މZAu~p=%NU?׋USƴfӛf/զ0D9ߛwy >cKp5L W|xftmt30c||+8@KQNuxO{|oჵI`Vbwcηzܥ@ǹb(7*[ yֽs(,'b'bMrX~ZkA/6V:R.2OwyԺQLI{:n&v :ν24|'F=b2;dzη\}MX7n:q=>m,d{نPb;|ǘ63AZr߄<[ѹߪryYnڐ:~'ܑ]x4|&BNs~ hκu䣠S9(v i^GܯyhD\:moc.˫QaRwyD~QNay`)  m#dOEډ?t]:3_ݪm;ƕha(o"v ,a\鉶ⵞo;!N},>4crnsE5YFƲߎ;yWC||XQM@,G~rxm[<˘CNF]]݈z\KwĴnzа'+L^7+u-m5Ui۱Tn&uDяӻP[9;ǩ<럳ޜujׁJlie4<ցʲ5gg*|:XWs̬.~ sЏ2g+<#L[`|rȎ'::wO|mwiΚhrﻵi݋>_W1^RP)~Nfj|=zɯR{*ŋ~%9?}x6e^0è9XGV:}*#rt=G(*>ϳn~ן~vl,x݅qt.^^{<.Gb9^x:|\fN#k"W:Ȧ_M>@m?ޣ-fcgbcy4ep)g\Ӆjox݅iOou;|Sz?ӇdXSzᇡ;1}mX#^ 矩%mZe>{WjF{!`>ֹW]~ZM[xQƇiNw*_V oP=^XC8̲ gr9ZMіk2N5N:Sytw)9](t *:T ?r<^s)XrogSkOux۰lXA\l{Cc?L {?oZ?M^{ӱυmDj #b?p<\cl%:0!˕0Yv:=S>_K>y|=m<6z!@t.;q|6Ux)> &g/sNx}f=mK/NuN}}:~\Y53<:Wǹo i3H=\?[>ϾZn֖/4|ʰڧls㥴~ۍ!#k ӽN՛qe㳱 zpC.׾^ 9D_nHVY:4z}8ᑘxCyIct-:#@e:Wgq-zy[3i8+s<70Q@tT\ W/Dmk!ov'S5/ӱ?갿i_Z线5czjjϰtgN{t]uQVj_c=a [Xt_G xLDzv9Ma4~[!3 rp6i:|F>҇Ilt]J j݊+wntCe×Zgd_gLF礼F~ן~jtjz~Xx,XC6az˲7wjnuxUrz|o0?Muqq 8zqzHjza8h oC [zpҹ| 'YƢUoOV˕Sټ$9!Hw*nF|j_cP'c~\;Q]k;"5NrӅguQGu6BǾc/"+㕺VJ <\:\W=Omv1~Y[ ):O~װn<7['Wh]۴g\D}ygZFa֟'?r|/LT.lg~4˿Pۿtxyvx=,*;ǵ;6n$~!:GGNZS%:B\v_Ege%:a|l|:|3GF_7t_EVsQOØ标Ey=ȓڗT̫beyz{r* S~מ\c<|\{ǴE]܍A\ꕨZwիX^ N#Cc`dx}ƆV nU9~_&Cht|'S>cLsS@8}wk&+>m"^5uqx?tT]^w:3=tvz?ЦT.6پ|{;t6l+SQ_s}ʲh3 #uu Ǻlcl>_#&g=eٺ?mZOj?Lݫl&^{a[K7ב֭3Ta?2=_,1]^7۰}-*lgm__n@Cߩ{WV5`>;Dmi}l+_ n/է釅# xڊL:MWic`W}JG{f W`Uv3?9S*uotlsN3|f˛?k$Cibȿ ѣ3]~0E!4 \MTyQCܿla |ȧ/F?F@>DtvW? }#.w4+7ӔG~a(/+ƸGGpLĶ4*6Ǟ_/S[[ G_|5;ǵect& QtPU0p5.QRب4b ·AߎF7êvc'ӇG%C My6V˝OUt~\??3a~ӕf?`'|<*|(Wx_R{kK m 4-ԺxUyR V t89aus}oam kE9WuηF5)iπWmv|!m)lthʣyrj>No^DQhԺo"螈׍4r8O@<*VhJ`CS%(I1zW6H<' !4[r\kpKp`rZ#``|o>9{;n#|+C+ )4|ū#Prw2 ]}M~~_tiXح)_X벶&m 4 M@hu>pyNuNooLck*w܇ӛ( pG"Uc>3uR^MzkVuk;X!, KBӕ@|<_ϣG4??VsK52{('}\zx({/3@loڔ#9ުV*{r/},SIm #E],2܎?#˃*){ 4NCs=VY;Lֳk BeG`xpw"(x>Ðe>4FӿYx.RWo5TxmMJszq2:;UUԺ0unkіB`7|Q%s1@H<c@H~#]JNF@sWԺ'4{Nbz]DX>-r(iRs)7 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: ``` 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`. ``` rootfs-c9d-v1/ ``` ### Populate Initial Filesystem Files and directories are then created: ``` 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: ``` ./ ./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)](http://linux.die.net/man/1/cp): `cp -a rootfs-c9d-v1/ rootfs-c9d-v1.s1/` * [rsync(1)](http://linux.die.net/man/1/rsync): `rsync -aHAX rootfs-c9d-v1/ rootfs-c9d-v1.s1/` * [tar(1)](http://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: ``` 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: ``` 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: ``` 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: ``` ./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)`](http://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: ``` 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`: ``` 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: ``` 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: ``` 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: ``` 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: ``` 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 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]: http://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) image-spec-1.0.2/manifest.md000066400000000000000000000121611414252520200156520ustar00rootroot00000000000000# 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`. - **`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 support at least the following media types: - [`application/vnd.oci.image.config.v1+json`](config.md) Manifests concerned with portability SHOULD use one of the above media types. - **`layers`** *array of objects* Each item in the array MUST be a [descriptor](descriptor.md). 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. Entries in this field will frequently use the `+gzip` types. - **`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, "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 7023, "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7" }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 32654, "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0" }, { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 16724, "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b" }, { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 73109, "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736" } ], "annotations": { "com.example.key1": "value1", "com.example.key2": "value2" } } ``` image-spec-1.0.2/media-types.md000066400000000000000000000075511414252520200162740ustar00rootroot00000000000000# 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.nondistributable.v1.tar`: ["Layer", as a tar archive with distribution restrictions](layer.md#non-distributable-layers) - `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] ## 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/docker/distribution/blob/master/docs/spec/manifest-v2-2.md#manifest-list) - mediaType is different ### application/vnd.oci.image.manifest.v1+json **Similar/related schema** - [application/vnd.docker.distribution.manifest.v2+json](https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-2.md#image-manifest-field-descriptions) ### 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/docker/docker/blob/master/image/spec/v1.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/docker/docker/blob/master/image/spec/v1.md#image-json-description) ## Relations The following figure shows how the above media types reference each other: ![](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 image-spec-1.0.2/project.md000066400000000000000000000003671414252520200155170ustar00rootroot00000000000000# 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. image-spec-1.0.2/schema/000077500000000000000000000000001414252520200147615ustar00rootroot00000000000000image-spec-1.0.2/schema/backwards_compatibility_test.go000066400000000000000000000304751414252520200232520ustar00rootroot00000000000000// 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" "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) } } } image-spec-1.0.2/schema/config-schema.json000066400000000000000000000053231414252520200203620ustar00rootroot00000000000000{ "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" }, "os": { "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" } } }, "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" ] } image-spec-1.0.2/schema/config_test.go000066400000000000000000000125041414252520200176160ustar00rootroot00000000000000// 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 "config.User" has numeric value, must be string { config: ` { "created": "2015-10-31T22:22:56.015925234Z", "author": "Alyssa P. Hacker ", "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 ", "architecture": "amd64", "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) } } } image-spec-1.0.2/schema/content-descriptor.json000066400000000000000000000020751414252520200215060ustar00rootroot00000000000000{ "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 ':'", "$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" }, "annotations": { "id": "https://opencontainers.org/schema/image/descriptor/annotations", "$ref": "defs-descriptor.json#/definitions/annotations" } }, "required": [ "mediaType", "size", "digest" ] } image-spec-1.0.2/schema/defs-descriptor.json000066400000000000000000000016321414252520200207530ustar00rootroot00000000000000{ "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 ':'", "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": { "id": "https://opencontainers.org/schema/image/descriptor/annotations", "$ref": "defs.json#/definitions/mapStringString" } } } image-spec-1.0.2/schema/defs.json000066400000000000000000000032061414252520200165760ustar00rootroot00000000000000{ "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" } ] }, "stringPointer": { "oneOf": [ { "type": "string" }, { "type": "null" } ] }, "mapStringString": { "type": "object", "patternProperties": { ".{1,}": { "type": "string" } } }, "mapStringObject": { "type": "object", "patternProperties": { ".{1,}": { "type": "object" } } } } } image-spec-1.0.2/schema/descriptor_test.go000066400000000000000000000167651414252520200205440ustar00rootroot00000000000000// 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 success: mediaType does not match pattern (type too long) { descriptor: ` { "mediaType": "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678/bar", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" } `, fail: true, }, // expected success: 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, }, { 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" }`, }, } { 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) } } } image-spec-1.0.2/schema/doc.go000066400000000000000000000013071414252520200160560ustar00rootroot00000000000000// 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 image-spec-1.0.2/schema/error.go000066400000000000000000000026021414252520200164410ustar00rootroot00000000000000// 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 ( "encoding/json" "io" "go4.org/errorutil" ) // 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 { line, col, _ := errorutil.HighlightBytePosition(r, serr.Offset) return &SyntaxError{serr.Error(), line, col, serr.Offset} } return err } image-spec-1.0.2/schema/fs.go000066400000000000000000000224321414252520200157230ustar00rootroot00000000000000// 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" "compress/gzip" "encoding/base64" "io/ioutil" "net/http" "os" "path" "sync" "time" ) type _escLocalFS struct{} var _escLocal _escLocalFS type _escStaticFS struct{} var _escStatic _escStaticFS type _escDirectory struct { fs http.FileSystem name string } type _escFile struct { compressed string size int64 modtime int64 local string isDir bool once sync.Once data []byte name string } func (_escLocalFS) Open(name string) (http.File, error) { f, present := _escData[path.Clean(name)] if !present { return nil, os.ErrNotExist } return os.Open(f.local) } func (_escStaticFS) prepare(name string) (*_escFile, error) { f, present := _escData[path.Clean(name)] if !present { return nil, os.ErrNotExist } var err error f.once.Do(func() { f.name = path.Base(name) if f.size == 0 { return } var gr *gzip.Reader b64 := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(f.compressed)) gr, err = gzip.NewReader(b64) if err != nil { return } f.data, err = ioutil.ReadAll(gr) }) if err != nil { return nil, err } return f, nil } func (fs _escStaticFS) Open(name string) (http.File, error) { f, err := fs.prepare(name) if err != nil { return nil, err } return f.File() } func (dir _escDirectory) Open(name string) (http.File, error) { return dir.fs.Open(dir.name + name) } func (f *_escFile) File() (http.File, error) { type httpFile struct { *bytes.Reader *_escFile } return &httpFile{ Reader: bytes.NewReader(f.data), _escFile: f, }, nil } func (f *_escFile) Close() error { return nil } func (f *_escFile) Readdir(count int) ([]os.FileInfo, error) { return nil, nil } func (f *_escFile) Stat() (os.FileInfo, error) { return f, nil } func (f *_escFile) Name() string { return f.name } func (f *_escFile) Size() int64 { return f.size } func (f *_escFile) Mode() os.FileMode { return 0 } func (f *_escFile) ModTime() time.Time { return time.Unix(f.modtime, 0) } func (f *_escFile) IsDir() bool { return f.isDir } func (f *_escFile) Sys() interface{} { return f } // _escFS returns a http.Filesystem for the embedded assets. If useLocal is true, // the filesystem's contents are instead used. func _escFS(useLocal bool) http.FileSystem { if useLocal { return _escLocal } return _escStatic } // _escDir returns a http.Filesystem for the embedded assets on a given prefix dir. // If useLocal is true, the filesystem's contents are instead used. func _escDir(useLocal bool, name string) http.FileSystem { if useLocal { return _escDirectory{fs: _escLocal, name: name} } return _escDirectory{fs: _escStatic, name: name} } // _escFSByte returns the named file from the embedded assets. If useLocal is // true, the filesystem's contents are instead used. func _escFSByte(useLocal bool, name string) ([]byte, error) { if useLocal { f, err := _escLocal.Open(name) if err != nil { return nil, err } b, err := ioutil.ReadAll(f) f.Close() return b, err } f, err := _escStatic.prepare(name) if err != nil { return nil, err } return f.data, nil } // _escFSMustByte is the same as _escFSByte, but panics if name is not present. func _escFSMustByte(useLocal bool, name string) []byte { b, err := _escFSByte(useLocal, name) if err != nil { panic(err) } return b } // _escFSString is the string version of _escFSByte. func _escFSString(useLocal bool, name string) (string, error) { b, err := _escFSByte(useLocal, name) return string(b), err } // _escFSMustString is the string version of _escFSMustByte. func _escFSMustString(useLocal bool, name string) string { return string(_escFSMustByte(useLocal, name)) } var _escData = map[string]*_escFile{ "/config-schema.json": { local: "config-schema.json", size: 2771, modtime: 1498025574, compressed: ` H4sIAAAJbogA/+RWQW/bPAy9+1cYbo9t/R2+U67dbgMyINh2KIZAsemEnSVqFD3MGPLfB8vJZtmym3XI aScDFB/f4xMl60eSplkJrmC0gmSyVZqtLZhHMqLQAKePZCrcpxsLBVZYKJ9118FuXXEArTrIQcSu8vzZ kbnvow/E+7xkVcn9f//nfeymx2F5hrhVnpMFU5zZnIf12TlqtYe88Pw9UloLHZZ2z1BIH7NMFlgQXLZK u3bSNCsYlED5KzCAOmE0fTkfr4i1km6lVAL3ghoyv3bsUzLVyIF4oVSYzcUBBQppGC7FkLs08+RFJHvg iI9HXPHxDw44iMwwDlh9ztvvlhyU74nFjfG3DJU3ECr30I3ATV5ChQa7UXG5VnbjK697jfH65tucLMWs 2uxuuIQCeixjoZE0Pc6QCreW0MiYmwysu56eAoKQblHigswXpIZyR5IXVZimrsNKwzqfoxY86vKf7f0j 1Y2GyThf2P9rp/7aXX0i/oJm/wZfdc7fqR3U17ZkE9n4a1qyEbIb3BtVX2xJMvyer18mkip6WV96/ZZY VVssJwZf/6475S91H9CCafRkx7NatcAuizuejFgzhq8Nsv8PP0U8GKtLhhXPnh/QCXEbMz00K2LU3PbM b1D07fCyW0vviMlWxN4UcYpZ/Enjdtf+RQ3SGiZ/vj8oANpKu/UTMV9kR1SDMjPzGZ6y5MQwnZvwWfX7 2RSey6SbnWPyMwAA//9KY9sL0woAAA== `, }, "/content-descriptor.json": { local: "content-descriptor.json", size: 1085, modtime: 1498025574, compressed: ` H4sIAAAJbogA/5yTwW7UMBCG73mKUVqpl27NoeIQVb3AnQPcEAevPY6nbGwznlW1oL47mniXJoAo3Vsy +r+Zz8n4RwfQe6yOqQjl1A/QfyiY3uUklhIy6BMmgffHUGb4WNBRIGdn4lpbXFYXcbKKR5EyGPNQc9q0 6k3m0Xi2QTZvbk2rXTSO/AmpgzG5YHKnyXXGWtr4X9MbJ4eCSubtAzpptcK5IAth7QfQgwH0E3qyn1q4 lf48r0SEOadNIQfQAmNAxuTQw2LGjF8yBuU8hrp5FrvRE18Yj4ESae9qnqfP7FNr0Vf6/pKPRoASbA+C 9ZVOfxGhJG9v1xKeRqzygobjQ5E8si2RHLiI7mvdT9DYk1ZzuVZdfS1WBDnB1Z3djZlJ4nQ/3OmP9ejv r875jkfXlf+ed/Uf9hZ21BQ1CIHzBI+RXASJVI/OMNkDbBF8fky7bD36c+xmk5WbTSnLfDtWiv+77DTZ ERcrb5b9zhBc4s2zO7r2jN/2xKhin3+/McttXS9NB/Cle+p+BgAA///HjexwPQQAAA== `, }, "/defs-descriptor.json": { local: "defs-descriptor.json", size: 922, modtime: 1498025574, compressed: ` H4sIAAAJbogA/6STX2/TMBTF3/spLl7FgDZN4QFp0Ria2DsP42lTV93ZN/Ed8R/ZrqYy9bsjJ1naFYFA PCSyj67Pub8b52kCIBRFGdgndlZUIK6oZst5F8FjSCw3LQZIDr56sl+cTciWAlwNx1yAa0+Sa5bYecx7 09FFVJBzAIQhxfht62mUAASrnKpT8rEqS+fJyueMuHChKaPUZLBkgw2Vakwt927zZ6/Ue4uYAttmr3tM iUKHd3d7Wdxg8WNZnK32y1cn09fF3XoxWz0t5+8/fNyVf1c2FV3Erk8SihuK6ZDuaLhJE8iw9ck1Ab1m CVKT/B43Bvqz4GrIRe7+gWSaA9tuOwDA6Tm2jQuctLmozvOoFKmL03+cwMA1e/O5up0t1sVqVN6+q/L6 srhZFmef1sVqdkS4CW38Ax9Cyz1ELoQ6OAOPmqWGpDkOVGBwC/cEyj3a1qEi9Wv/GAJu9zInMoe5vycF ELULBvNXEJvAYtB3LzDQWpfw5fX8n7t46Dc2PQ1UZz9FdVw8RGdPyoPfojTor7ve+/cw50l+dpOfAQAA //8aH/C2mgMAAA== `, }, "/defs.json": { local: "defs.json", size: 1670, modtime: 1498025574, compressed: ` H4sIAAAJbogA/7STza6bMBCF9zzFyO2S9oJtbGDb7hMpy6oLSiaJq2AjY6RWEe9e8RNChFuJKneRgGc8 3zmeMbcAgByxKa2qnTKa5EC+4klp1a8aaBs8grtY054vpnXgLgi7GvUXo12hNFo41FiqkyqLoTwceTOA 5NBLABClXTqvAIj7XWOvprTDM9qhckhUSquqrUgOn2KaPsLFrykcUzkEu3Amx2IrmlEpfPA+vsIzuhVP Yy55ygT3aczJlZDgW4UyShmTNGIiTbiUIooij6Jn15N0+x/T8enQJFlxN8/GBxZJwtbozXPxoTnNeCYk zdb8zePw8eOUcyE5jySTUZYk1Nf8WOxNz7VLQaNxdyI5fJsCMKeG9EeLfZZ8eFt8cG9Ty+eNXeivvp9G t9frYvf09t3Ti1c6FPy1DhtnlT5vd3jXGOtf66kq6sOAHf99V8n8+Imle9ykunAOrd5bU6N1CptFEQD5 fIvD7in0ryMEy+fK1G6UfmdTE+tvpoL+1wV/AgAA//96IpqyhgYAAA== `, }, "/image-index-schema.json": { local: "image-index-schema.json", size: 2993, modtime: 1498025574, compressed: ` H4sIAAAJbogA/6yWv27bMBDGdz/FQQmQJYmKIuhgBFnaJVOHBl2KDAx5ki61SPVIJ3ELv3tBMrIlUXZt 1Zt95H33+07892cGkCm0kqlxZHQ2h+xrg/qz0U6QRob7WpQI91rhG3xrUFJBUoSplz733MoKa+HzKuea eZ4/W6OvYvTacJkrFoW7+nCTx9hZzCPVpth5npsGtWxL2pAWZ+fky+fky8dEt2rQp5qnZ5Quxho2DbIj tNkcvCWALOZ/R7bRVgynbh8qslAQLhTYaA8tuAohVIZQGaIYvEQ1EBaEBtIOS+SAEJQneMr7mBup1mVS oyZN9bLO5vBxGxNvbSyE1nEkq4WmAq2zXfutsmAWqw67w7o772g7bbEv7+01W+jxr/Y+wvhrSYy+1o9N 1MOjIvHg0y67YUu/BxFFJVqXbUKPHfGRhZHI9wfSBeLXQpjtPYApwuJgLJBRS1SQWAoi54yFz1ZY2Cu1 6cm13x1nucKCNPkKNt+SdBTWqelDOP1EIA1PK4d2EusIIGn36WY33Hv/D8GTvGqcKVk0FUmQFcqfdllD VGhxI+Olt+H/NsI5ZA0Xt2JRGiZX1XfzW78WFaq7i+l9H66boa8lL4arJnUlYEER3U+Hgk0NrxXJCpw/ V6IXqMUKnhCUedULIxSq6dSBaidzsxCuMFyn3Mdt5o3OgHPnNoY9WzmMCZYVOZRuyTjIA8hMz1NvD8Pe fZxqp+OT3ed7oTvtsI5Jl9lgwnrM5inxjD0N1PVLckueAm4jexrIAoX/Dqdu4VZ3D2b/suyWTa7Ng00C rP9p+0UwCZ0erof0cLbrX//IEFobFx50I6fdcV3dHlx5V3XyWdcVmY15aX+te8+ecUeTXmdjNv7HgAcN mOlZmY29BDtPuBnA42w9+xsAAP//IKe/nbELAAA= `, }, "/image-layout-schema.json": { local: "image-layout-schema.json", size: 439, modtime: 1498025574, compressed: ` H4sIAAAJbogA/2yPQUvEMBCF7/0VQ/Sg4DYVPOW6pwVhD4IX8VDTaTvLNonJVFik/12SaRXRU5g38+W9 91kBqA6TjRSYvFMG1DGg23vHLTmMcJjaAeGxvfiZ4cmOOLXqLlPXSQYDamQORutT8m4nau3joLvY9rxr HrRoV8JRtyHJaO0DOruZpYLJtaZsrM/FWEi+BMysfzuhXbUQfcDIhEkZyG2yQyYl8TPGJLVk97fth1yA 74FHhOP+8LvyDbmy8JZ2EgZ6OuNtsS8fbrESR3LDj45unpSBl3UGUPd1UzdqnV/Lu1QAS2kS8X2miN03 8l+PKnNL9RUAAP//k31n5bcBAAA= `, }, "/image-manifest-schema.json": { local: "image-manifest-schema.json", size: 921, modtime: 1498025574, compressed: ` H4sIAAAJbogA/5ySMW8iMRCF+/0VI0MJ+O501bZXUZxSJEoTpXB2x7uDWNsZmygo4r9HtnHAkCKifTvv zTdv/dEAiB59x+QCWSNaEHcOzT9rgiKDDOtJDQj/lSGNPsC9w440dSpNL6J97rsRJxWtYwiulXLjrVlm dWV5kD0rHZa//sqszbKP+mLxrZTWoenKVp9seVpSJJDTkSB7w95hdNuXDXZHzbF1yIHQixbiYQAiRzwi +3xclq9vfhjJgybc9uDzheghjAhpOZTlkPPgLQeC8qAMkAk4ICeKFH7bZbKG/Uort16tmcjQtJtEC39O mnovWpIO+YvorNE0nDcwZ9QxNqKhCcvSiOVV/H+ism/VHtmf2wuVYlb7imkdcIqjv099HJVi/ul2gENF oYyxIb28CuXGus/TFpet9Kj9JdRM9qjJULJU9qawJlLB+Lojxoj19N07rP9JXXED8Nwcms8AAAD//7u3 Dj+ZAwAA `, }, "/": { isDir: true, local: "/", }, } image-spec-1.0.2/schema/gen.go000066400000000000000000000015131414252520200160610ustar00rootroot00000000000000// 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 // Generates an embbedded http.FileSystem for all schema files // using esc (https://github.com/mjibson/esc). // This should generally be invoked with `make schema-fs` //go:generate esc -private -pkg=schema -include=.*\.json$ . image-spec-1.0.2/schema/image-index-schema.json000066400000000000000000000056611414252520200213110ustar00rootroot00000000000000{ "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 }, "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 ':'", "$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" ] } image-spec-1.0.2/schema/image-layout-schema.json000066400000000000000000000006671414252520200215200ustar00rootroot00000000000000{ "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" ] } image-spec-1.0.2/schema/image-manifest-schema.json000066400000000000000000000016311414252520200220010ustar00rootroot00000000000000{ "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 }, "config": { "$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" ] } image-spec-1.0.2/schema/imageindex_test.go000066400000000000000000000115401414252520200204620ustar00rootroot00000000000000// 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, "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, "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, "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, "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, "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, "manifests": [ { "mediaType": "", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", "platform": { "architecture": "amd64", "os": "linux" } } ] } `, fail: true, }, // valid image index, with optional fields { imageIndex: ` { "schemaVersion": 2, "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, "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, "manifests": [ { "mediaType": "application/customized.manifest+json", "size": 7143, "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", "platform": { "architecture": "ppc64le", "os": "linux" } } ] } `, fail: false, }, } { 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) } } } image-spec-1.0.2/schema/imagelayout_test.go000066400000000000000000000024421414252520200206710ustar00rootroot00000000000000// 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) } } } image-spec-1.0.2/schema/manifest_test.go000066400000000000000000000140401414252520200201540ustar00rootroot00000000000000// 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, "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, "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, "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, "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, "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, "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, "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256+b64:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, "layers": [ { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256+foo-bar:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256.foo-bar:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "multihash+base58:QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8" } ] } `, }, // expected failure: push bounds of algorithm field in digest too far. { manifest: ` { "schemaVersion": 2, "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256+b64:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, "layers": [ { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256+foo+-b:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" } ] } `, fail: true, }, } { 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) } } } image-spec-1.0.2/schema/schema.go000066400000000000000000000035151414252520200165540ustar00rootroot00000000000000// 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 ( "net/http" "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 "/". fs = _escFS(false) // specs maps OCI schema media types to schema files. specs = map[Validator]string{ ValidatorMediaTypeDescriptor: "content-descriptor.json", ValidatorMediaTypeLayoutHeader: "image-layout-schema.json", ValidatorMediaTypeManifest: "image-manifest-schema.json", ValidatorMediaTypeImageIndex: "image-index-schema.json", ValidatorMediaTypeImageConfig: "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 fs } image-spec-1.0.2/schema/spec_test.go000066400000000000000000000111401414252520200172760ustar00rootroot00000000000000// 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" "io/ioutil" "net/url" "os" "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") } // 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 { // TOOD(stevvooe): This is nearly useless without file, line no. 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 := ioutil.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")) } image-spec-1.0.2/schema/validator.go000066400000000000000000000134521414252520200173020ustar00rootroot00000000000000// 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" "io/ioutil" "regexp" digest "github.com/opencontainers/go-digest" "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 := ioutil.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 := gojsonschema.NewReferenceLoaderFileSystem("file:///"+specs[v], fs) 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(src io.Reader) error { return fmt.Errorf("%s: unimplemented", v) } func validateManifest(r io.Reader) error { header := v1.Manifest{} buf, err := ioutil.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.MediaTypeImageLayerNonDistributable) && layer.MediaType != string(v1.MediaTypeImageLayerNonDistributableGzip) { 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 := ioutil.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 := ioutil.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) } } return nil } func validateConfig(r io.Reader) error { header := v1.Image{} buf, err := ioutil.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) envRegexp := regexp.MustCompile(`^[^=]+=.*$`) for _, e := range header.Config.Env { if !envRegexp.MatchString(e) { return errors.Errorf("unexpected env: %q", e) } } return nil } 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"}, "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 %q and %q is invalid.", OS, Architecture) } } fmt.Printf("warning: operating system %q of the bundle is not supported yet.", OS) } image-spec-1.0.2/spec.md000066400000000000000000000100261414252520200147740ustar00rootroot00000000000000# Open Container Initiative ## Image Format Specification This specification defines an OCI Image, consisting of a [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 - [Introduction](spec.md) - [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](http://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. ![](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). ![](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 index of image 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 * [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]: http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf#page=18 [runtime-spec]: https://github.com/opencontainers/runtime-spec image-spec-1.0.2/specs-go/000077500000000000000000000000001414252520200152415ustar00rootroot00000000000000image-spec-1.0.2/specs-go/v1/000077500000000000000000000000001414252520200155675ustar00rootroot00000000000000image-spec-1.0.2/specs-go/v1/annotations.go000066400000000000000000000054401414252520200204560ustar00rootroot00000000000000// 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" ) image-spec-1.0.2/specs-go/v1/config.go000066400000000000000000000100451414252520200173630ustar00rootroot00000000000000// 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"` } // 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"` // Architecture is the CPU architecture which the binaries in this image are built to run on. Architecture string `json:"architecture"` // OS is the name of the operating system which the image is built to run on. OS string `json:"os"` // 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"` } image-spec-1.0.2/specs-go/v1/descriptor.go000066400000000000000000000046331414252520200203020ustar00rootroot00000000000000// 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 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,omitempty"` // 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"` // 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"` } // 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 `ppc64`. 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"` } image-spec-1.0.2/specs-go/v1/index.go000066400000000000000000000023421414252520200172260ustar00rootroot00000000000000// 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 specificies the type of this document data structure e.g. `application/vnd.oci.image.index.v1+json` MediaType string `json:"mediaType,omitempty"` // Manifests references platform specific manifests. Manifests []Descriptor `json:"manifests"` // Annotations contains arbitrary metadata for the image index. Annotations map[string]string `json:"annotations,omitempty"` } image-spec-1.0.2/specs-go/v1/layout.go000066400000000000000000000017271414252520200174420ustar00rootroot00000000000000// 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 of oci image layout file ImageLayoutFile = "oci-layout" // ImageLayoutVersion is the version of ImageLayout ImageLayoutVersion = "1.0.0" ) // 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"` } image-spec-1.0.2/specs-go/v1/manifest.go000066400000000000000000000026301414252520200177250ustar00rootroot00000000000000// 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" // Manifest provides `application/vnd.oci.image.manifest.v1+json` mediatype structure when marshalled to JSON. type Manifest struct { specs.Versioned // MediaType specificies the type of this document data structure e.g. `application/vnd.oci.image.manifest.v1+json` MediaType string `json:"mediaType,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"` // Annotations contains arbitrary metadata for the image manifest. Annotations map[string]string `json:"annotations,omitempty"` } image-spec-1.0.2/specs-go/v1/mediatype.go000066400000000000000000000041451414252520200201030ustar00rootroot00000000000000// 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" // MediaTypeImageLayerNonDistributable is the media type for layers referenced by // the manifest but with distribution restrictions. 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. MediaTypeImageLayerNonDistributableGzip = "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip" // MediaTypeImageConfig specifies the media type for the image configuration. MediaTypeImageConfig = "application/vnd.oci.image.config.v1+json" ) image-spec-1.0.2/specs-go/version.go000066400000000000000000000021521414252520200172550ustar00rootroot00000000000000// 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 = 0 // VersionPatch is for backwards-compatible bug fixes VersionPatch = 2 // VersionDev indicates development branch. Releases will be empty string. VersionDev = "" ) // Version is the specification version that the package types support. var Version = fmt.Sprintf("%d.%d.%d%s", VersionMajor, VersionMinor, VersionPatch, VersionDev) image-spec-1.0.2/specs-go/versioned.go000066400000000000000000000016521414252520200175720ustar00rootroot00000000000000// 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"` }