pax_global_header00006660000000000000000000000064145550654370014530gustar00rootroot0000000000000052 comment=e7f7c0ca69b21688c3cea7c87a04e4503e6099e2 image-spec-1.1.0/000077500000000000000000000000001455506543700135415ustar00rootroot00000000000000image-spec-1.1.0/.github/000077500000000000000000000000001455506543700151015ustar00rootroot00000000000000image-spec-1.1.0/.github/PULL_REQUEST_TEMPLATE/000077500000000000000000000000001455506543700203605ustar00rootroot00000000000000image-spec-1.1.0/.github/PULL_REQUEST_TEMPLATE/maintainer_nomination.md000066400000000000000000000011121455506543700252570ustar00rootroot00000000000000# Nomination for a New Maintainer ## Nominating Maintainer Name of the existing OCI maintainer with GitHub username ## New Maintainer Name of the new maintainer with GitHub username ## Justification Highlight any work contributed by the new maintainer. Examples of contributions may be: - Community involvement in mailing lists and meetings - Involvement in any OCI working groups - Contributions to any of the OCI git repositories Other considerations may be: - Diversity of organizations - Time involved in the community - Personal experience working with the new maintainer image-spec-1.1.0/.github/workflows/000077500000000000000000000000001455506543700171365ustar00rootroot00000000000000image-spec-1.1.0/.github/workflows/docs-and-linting.yml000066400000000000000000000022241455506543700230130ustar00rootroot00000000000000name: Render and Lint Documentation on: pull_request: branches_ignore: [] jobs: build: runs-on: ubuntu-latest strategy: matrix: go: ['1.19', '1.20', '1.21'] name: Documentation and Linting steps: - uses: actions/checkout@v3 with: path: go/src/github.com/opencontainers/image-spec - uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} - name: Render and Lint env: GOPATH: /home/runner/work/image-spec/image-spec/go # do not automatically upgrade go to a different version: https://go.dev/doc/toolchain GOTOOLCHAIN: local run: | export PATH=$GOPATH/bin:$PATH cd go/src/github.com/opencontainers/image-spec make install.tools go get -t -d ./... ls ../ make make .gitvalidation make lint make check-license make test make docs - name: documentation artifacts uses: actions/upload-artifact@v3 with: name: oci-docs path: go/src/github.com/opencontainers/image-spec/output image-spec-1.1.0/.gitignore000066400000000000000000000000521455506543700155260ustar00rootroot00000000000000/oci-validate-examples output header.html image-spec-1.1.0/.golangci.yml000066400000000000000000000007301455506543700161250ustar00rootroot00000000000000run: timeout: 10m linters: disable-all: true enable: - dupl - errorlint - gofmt - goimports - gomodguard - gosimple - govet - ineffassign - misspell - nakedret - revive - unused - staticcheck linters-settings: gofmt: simplify: true gomodguard: blocked: modules: - github.com/pkg/errors: recommendations: - errors - fmt dupl: threshold: 400 image-spec-1.1.0/.header000066400000000000000000000011261455506543700147720ustar00rootroot00000000000000// 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.1.0/.markdownlint.yml000066400000000000000000000004751455506543700170610ustar00rootroot00000000000000# all lists use a `-` MD004: style: dash # allow tabs in code blocks (for Go) MD010: code_blocks: false # disable line length, prefer one sentence per line for PRs MD013: false # emphasis with underscore (`_emphasis_`) MD049: style: "underscore" # bold with asterisk (`**bold**`) MD050: style: "asterisk" image-spec-1.1.0/.tool/000077500000000000000000000000001455506543700145745ustar00rootroot00000000000000image-spec-1.1.0/.tool/check-license000077500000000000000000000004401455506543700172150ustar00rootroot00000000000000#!/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.1.0/.tool/genheader.go000066400000000000000000000026271455506543700170540ustar00rootroot00000000000000// 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.1.0/CODEOWNERS000066400000000000000000000001311455506543700151270ustar00rootroot00000000000000 * @jonjohnsonjr @jonboulle @stevvooe @sudo-bmitch @sajayantony @tianon @vbatts @cyphar image-spec-1.1.0/EMERITUS.md000066400000000000000000000005231455506543700153200ustar00rootroot00000000000000# Emeritus We would like to acknowledge previous OCI image spec maintainers and their huge contributions to our collective success: - Brandon Philips (@philips) - Brendan Burns (@brendandburns) - Jason Bouzane (@jbouzane) - John Starks (@jstarks) - Keyang Xie (@xiekeyang) We thank these members for their service to the OCI community. image-spec-1.1.0/GOVERNANCE.md000066400000000000000000000074761455506543700155300ustar00rootroot00000000000000# 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 mailing list (except [security issues](#security-issues)) with another maintainer as a co-sponsor. ## Voting Voting on a proposed motion SHOULD happen on the mailing list (except [security issues](#security-issues)) with maintainers posting LGTM or REJECT. Maintainers MAY also explicitly not vote by posting ABSTAIN (which is useful to revert a previous vote). Maintainers MAY post multiple times (e.g. as they revise their position based on feedback), but only their final post counts in the tally. A proposed motion is adopted if two-thirds of votes cast, a quorum having voted, are in favor of the release. Voting SHOULD remain open for a week to collect feedback from the wider community and allow the maintainers to digest the proposed motion. Under exceptional conditions (e.g. non-major security fix releases) proposals which reach quorum with unanimous support MAY be adopted earlier. A maintainer MAY choose to reply with REJECT. A maintainer posting a REJECT MUST include a list of concerns or links to written documentation for those concerns (e.g. GitHub issues or mailing-list threads). The maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a motion MAY be adopted with REJECTs, as outlined in the previous paragraphs. ## Quorum A quorum is established when at least two-thirds of maintainers have voted. For projects that are not specifications, a [motion to release](#proposing-a-motion) MAY be adopted if the tally is at least three LGTMs and no REJECTs, even if three votes does not meet the usual two-thirds quorum. ## Security issues Motions with sensitive security implications MUST be proposed on the mailing list instead of , but should otherwise follow the standard [proposal](#proposing-a-motion) process. The mailing list includes all members of the TOB. The TOB will contact the project maintainers and provide a channel for discussing and voting on the motion, but voting will otherwise follow the standard [voting](#voting) and [quorum](#quorum) rules. The TOB and project maintainers will work together to notify affected parties before making an adopted motion public. ## Amendments The [project governance](#project-governance) rules and procedures MAY be amended or replaced using the procedures themselves. The MAINTAINERS of this project governance document is the total set of MAINTAINERS from all Open Containers projects (runC, runtime-spec, and image-spec). ## Subject templates Maintainers are busy and get lots of email. To make project proposals recognizable, proposed motions SHOULD use the following subject templates. ### Proposing a motion template > [{project} VOTE]: {motion description} (closes {end of voting window}) For example: > [image-spec VOTE]: Tag 0647920 as 1.0.0-rc (closes 2016-06-03 20:00 UTC) ### Tallying results template After voting closes, a maintainer SHOULD post a tally to the motion thread with a subject template like: > [{project} {status}]: {motion description} (+{LGTMs} -{REJECTs} #{ABSTAINs}) Where `{status}` is either `adopted` or `rejected`. For example: > [image-spec adopted]: Tag 0647920 as 1.0.0-rc (+6 -0 #3) [charter]: https://github.com/opencontainers/tob/blob/main/CHARTER.md image-spec-1.1.0/HACKING.md000066400000000000000000000034101455506543700151250ustar00rootroot00000000000000# Hacking Guide ## Overview This guide contains instructions for building artifacts contained in this repository. ### Go This spec includes several Go packages, and a command line tool considered to be a reference implementation of the OCI image specification. Prerequisites: - Go - current release only, earlier releases are not supported - make The following make targets are relevant for any work involving the Go packages. ### Linting The included Go source code is being examined for any linting violations not included in the standard Go compiler. Linting is done using [golangci-lint][golangci-lint]. Invocation: ```shell make lint ``` ### Tests This target executes all Go based tests. Invocation: ```shell make test make validate-examples ``` ### JSON schema formatting This target auto-formats all JSON files in the `schema` directory using the `jq` tool. Prerequisites: - [jq][jq] >=1.5 Invocation: ```shell make fmt ``` ### OCI image specification PDF/HTML documentation files This target generates a PDF/HTML version of the OCI image specification. Prerequisites: - [Docker][docker] Invocation: ```shell make docs ``` ### License header check This target checks if the source code includes necessary headers. Invocation: ```shell make check-license ``` ### Clean build artifacts This target cleans all generated/compiled artifacts. Invocation: ```shell make clean ``` ### Create PNG images from dot files This target generates PNG image files from DOT source files in the `img` directory. Prerequisites: - [graphviz][graphviz] Invocation: ```shell make img/media-types.png ``` [docker]: https://www.docker.com/ [golangci-lint]: https://github.com/golangci/golangci-lint [graphviz]: https://www.graphviz.org/ [jq]: https://stedolan.github.io/jq/ image-spec-1.1.0/LICENSE000066400000000000000000000250171455506543700145530ustar00rootroot00000000000000 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.1.0/MAINTAINERS000066400000000000000000000006111455506543700152340ustar00rootroot00000000000000Brandon Mitchell (@sudo-bmitch) Jon Johnson (@jonjohnsonjr) Jonathan Boulle (@jonboulle) Sajay Antony (@sajayantony) Stephen Day (@stevvooe) Tianon Gravi (@tianon) Vincent Batts (@vbatts) Aleksa Sarai (@cyphar) image-spec-1.1.0/Makefile000066400000000000000000000104361455506543700152050ustar00rootroot00000000000000EPOCH_TEST_COMMIT ?= v0.2.0 DOCKER ?= $(shell command -v docker 2>/dev/null) PANDOC ?= $(shell command -v pandoc 2>/dev/null) GOPATH:=$(shell go env GOPATH) OUTPUT_DIRNAME ?= output DOC_FILENAME ?= oci-image-spec PANDOC_CONTAINER ?= ghcr.io/opencontainers/pandoc:2.9.2.1-8.fc33.x86_64@sha256:5d81ff930a043295a557be8b003ece2a33d14e91b28c50d368413b83372f8d28 ifeq "$(strip $(PANDOC))" '' ifneq "$(strip $(DOCKER))" '' PANDOC = $(DOCKER) run \ --rm \ -v $(shell pwd)/:/input/:ro \ -v $(shell pwd)/$(OUTPUT_DIRNAME)/:/$(OUTPUT_DIRNAME)/ \ -u $(shell id -u) \ --workdir /input \ $(PANDOC_CONTAINER) PANDOC_SRC := /input/ PANDOC_DST := / endif endif # These docs are in an order that determines how they show up in the PDF/HTML docs. DOC_FILES := \ spec.md \ media-types.md \ descriptor.md \ image-layout.md \ manifest.md \ image-index.md \ layer.md \ config.md \ annotations.md \ conversion.md \ considerations.md \ implementations.md FIGURE_FILES := \ img/media-types.png MARKDOWN_LINT_VER?=v0.8.1 TOOLS := gitvalidation default: check-license lint test .PHONY: fmt fmt: ## format the json with indentation for i in schema/*.json ; do jq --indent 2 -M . "$${i}" > xx && cat xx > "$${i}" && rm xx ; done .PHONY: docs docs: $(OUTPUT_DIRNAME)/$(DOC_FILENAME).pdf $(OUTPUT_DIRNAME)/$(DOC_FILENAME).html ## generate a PDF/HTML version of the OCI image specification ifeq "$(strip $(PANDOC))" '' $(OUTPUT_DIRNAME)/$(DOC_FILENAME).pdf: $(DOC_FILES) $(FIGURE_FILES) $(error cannot build $@ without either pandoc or docker) else $(OUTPUT_DIRNAME)/$(DOC_FILENAME).pdf: $(DOC_FILES) $(FIGURE_FILES) @mkdir -p $(OUTPUT_DIRNAME)/ && \ $(PANDOC) -f gfm -t latex --pdf-engine=xelatex -V geometry:margin=0.5in,bottom=0.8in -V block-headings -o $(PANDOC_DST)$@ $(patsubst %,$(PANDOC_SRC)%,$(DOC_FILES)) ls -sh $(realpath $@) $(OUTPUT_DIRNAME)/$(DOC_FILENAME).html: header.html $(DOC_FILES) $(FIGURE_FILES) @mkdir -p $(OUTPUT_DIRNAME)/ && \ cp -ap img/ $(shell pwd)/$(OUTPUT_DIRNAME)/&& \ $(PANDOC) -f gfm -t html5 -H $(PANDOC_SRC)header.html --standalone -o $(PANDOC_DST)$@ $(patsubst %,$(PANDOC_SRC)%,$(DOC_FILES)) ls -sh $(realpath $@) endif header.html: .tool/genheader.go specs-go/version.go go run .tool/genheader.go > $@ .PHONY: validate-examples validate-examples: schema/schema.go ## validate examples in the specification markdown files go test -run TestValidate ./schema .PHONY: check-license check-license: ## check license headers in source files @echo "checking license headers" @./.tool/check-license .PHONY: lint .PHONY: lint lint: lint-go lint-md ## Run all linters .PHONY: lint-go lint-go: .install.lint ## lint check of Go files using golangci-lint @echo "checking Go lint" @GO111MODULE=on $(GOPATH)/bin/golangci-lint run .PHONY: lint-md lint-md: ## Run linting for markdown docker run --rm -v "$(PWD):/workdir:ro" docker.io/davidanson/markdownlint-cli2:$(MARKDOWN_LINT_VER) \ "**/*.md" "#vendor" .PHONY: test test: ## run the unit tests go test -race -cover $(shell go list ./... | grep -v /vendor/) img/%.png: img/%.dot ## generate PNG from dot file dot -Tpng $^ > $@ # When this is running in GitHub, it will only check the commit range .PHONY: .gitvalidation .gitvalidation: @which git-validation > /dev/null 2>/dev/null || (echo "ERROR: git-validation not found. Consider 'make install.tools' target" && false) ifdef GITHUB_SHA $(GOPATH)/bin/git-validation -q -run DCO,short-subject,dangling-whitespace -range $(GITHUB_SHA)..HEAD else $(GOPATH)/bin/git-validation -v -run DCO,short-subject,dangling-whitespace -range $(EPOCH_TEST_COMMIT)..HEAD endif .PHONY: .install.tools install.tools: $(TOOLS:%=.install.%) .PHONY: .install.lint .install.lint: case "$$(go env GOVERSION)" in \ go1.18.*) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.47.3;; \ go1.19.*) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.1;; \ *) go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest;; \ esac .PHONY: .install.gitvalidation .install.gitvalidation: go install github.com/vbatts/git-validation@latest .PHONY: clean clean: ## clean all generated and compiled artifacts rm -rf *~ $(OUTPUT_DIRNAME) header.html .PHONY: help help: # Display help @awk -F ':|##' '/^[^\t].+?:.*?##/ { printf "\033[36m%-30s\033[0m %s\n", $$1, $$NF }' $(MAKEFILE_LIST) image-spec-1.1.0/README.md000066400000000000000000000166051455506543700150300ustar00rootroot00000000000000# OCI Image Format Specification ![GitHub Actions for Docs and Linting](https://img.shields.io/github/actions/workflow/status/opencontainers/image-spec/docs-and-linting.yml?branch=main&label=GHA%20docs%20and%20linting) ![License](https://img.shields.io/github/license/opencontainers/image-spec) [![Go Reference](https://pkg.go.dev/badge/github.com/opencontainers/image-spec.svg)](https://pkg.go.dev/github.com/opencontainers/image-spec) The OCI Image Format project creates and maintains the software shipping container image format spec (OCI Image Format). **[The specification can be found here](spec.md).** This repository also provides [Go types](specs-go), [intra-blob validation tooling, and JSON Schema](schema). The Go types and validation should be compatible with the current Go release; earlier Go releases are not supported. Additional documentation about how this group operates: - [Code of Conduct][code-of-conduct] - [Roadmap](#roadmap) - [Releases](RELEASES.md) - [Project Documentation](project.md) ## Running an OCI Image The OCI Image Format partner project is the [OCI Runtime Spec project](https://github.com/opencontainers/runtime-spec). The Runtime Specification outlines how to run a "[filesystem bundle](https://github.com/opencontainers/runtime-spec/blob/master/bundle.md)" that is unpacked on disk. At a high-level an OCI implementation would download an OCI Image then unpack that image into an OCI Runtime filesystem bundle. At this point the OCI Runtime Bundle would be run by an OCI Runtime. This entire workflow supports the UX that users have come to expect from container engines like Docker and rkt: primarily, the ability to run an image with no additional arguments: - docker run example.com/org/app:v1.0.0 - rkt run example.com/org/app,version=v1.0.0 To support this UX the OCI Image Format contains sufficient information to launch the application on the target platform (e.g. command, arguments, environment variables, etc). ## Distributing an OCI Image The [OCI Distribution Spec Project](https://github.com/opencontainers/distribution-spec/) defines an API protocol to facilitate and standardize the distribution of content. This API includes support for pushing and pulling OCI images to an OCI conformant registry. ## FAQ **Q: What happens to AppC or Docker Image Formats?** A: Existing formats can continue to be a proving ground for technologies, as needed. The OCI Image Format project strives to provide a dependable open specification that can be shared between different tools and be evolved for years or decades of compatibility; as the deb and rpm format have. Find more [FAQ on the OCI site](https://www.opencontainers.org/faq). ## Roadmap The [GitHub milestones](https://github.com/opencontainers/image-spec/milestones) lay out the path to the future improvements. ## Contributing Development happens on GitHub for the spec. Issues are used for bugs and actionable items and longer discussions can happen on the [mailing list](#mailing-list). The specification and code is licensed under the Apache 2.0 license found in the `LICENSE` file of this repository. ### Discuss your design The project welcomes submissions, but please let everyone know what you are working on. Before undertaking a nontrivial change to this specification, send mail to the [mailing list](#mailing-list) to discuss what you plan to do. This gives everyone a chance to validate the design, helps prevent duplication of effort, and ensures that the idea fits. It also guarantees that the design is sound before code is written; a GitHub pull-request is not the place for high-level discussions. Typos and grammatical errors can go straight to a pull-request. When in doubt, start on the [mailing-list](#mailing-list). ### Meetings Please see the [OCI org repository README](https://github.com/opencontainers/org#meetings) for the most up-to-date information on OCI contributor and maintainer meeting schedules. You can also find links to meeting agendas and minutes for all prior meetings. ### Mailing List You can subscribe and join the mailing list on [Google Groups](https://groups.google.com/a/opencontainers.org/forum/#!forum/dev). ### IRC OCI discussion happens on #opencontainers on Freenode ([logs][irc-logs]). ### Markdown style To keep consistency throughout the Markdown files in the Open Container spec all files should be formatted one sentence per line. This fixes two things: it makes diffing easier with git and it resolves fights about line wrapping length. For example, this paragraph will span three lines in the Markdown source. ### Git commit #### Sign your work The sign-off is a simple line at the end of the explanation for the patch, which certifies that you wrote it or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: if you can certify the below (from [developercertificate.org](https://developercertificate.org/)): ```text Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 660 York Street, Suite 102, San Francisco, CA 94110 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ``` then you just add a line to every git commit message: ```text Signed-off-by: Joe Smith ``` using your real name (sorry, no pseudonyms or anonymous contributions.) You can add the sign off when creating the git commit via `git commit -s`. ### Commit Style Simple house-keeping for clean git history. Read more on [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) or the Discussion section of [`git-commit(1)`](https://git-scm.com/docs/git-commit). 1. Separate the subject from body with a blank line 2. Limit the subject line to 50 characters 3. Capitalize the subject line 4. Do not end the subject line with a period 5. Use the imperative mood in the subject line 6. Wrap the body at 72 characters 7. Use the body to explain what and why vs. how - If there was important/useful/essential conversation or information, copy or include a reference 8. When possible, one keyword to scope the change in the subject (i.e. "README: ...", "runtime: ...") [code-of-conduct]: https://github.com/opencontainers/org/blob/master/CODE_OF_CONDUCT.md [irc-logs]: http://ircbot.wl.linuxfoundation.org/eavesdrop/%23opencontainers/ image-spec-1.1.0/RELEASES.md000066400000000000000000000140721455506543700152720ustar00rootroot00000000000000# Releases The release process hopes to encourage early, consistent consensus-building during project development. The mechanisms used are regular community communication on the mailing list about progress, scheduled meetings for issue resolution and release triage, and regularly paced and communicated releases. Releases are proposed and adopted or rejected using the usual [project governance](GOVERNANCE.md) rules and procedures. An anti-pattern that we want to avoid is heavy development or discussions "late cycle" around major releases. We want to build a community that is involved and communicates consistently through all releases instead of relying on "silent periods" as a judge of stability. ## Parallel releases A single project MAY consider several motions to release in parallel. However each motion to release after the initial 0.1.0 MUST be based on a previous release that has already landed. For example, runtime-spec maintainers may propose a v1.0.0-rc2 on the 1st of the month and a v0.9.1 bugfix on the 2nd of the month. They may not propose a v1.0.0-rc3 until the v1.0.0-rc2 is accepted (on the 7th if the vote initiated on the 1st passes). ## Specifications The OCI maintains three categories of projects: specifications, applications, and conformance-testing tools. However, specification releases have special restrictions in the [OCI charter][charter]: - They are the target of backwards compatibility (§7.g), and - They are subject to the OFWa patent grant (§8.d and e). To avoid unfortunate side effects (onerous backwards compatibility requirements or Member resignations), the following additional procedures apply to specification releases: ### Planning a release Every OCI specification project SHOULD hold meetings that involve maintainers reviewing pull requests, debating outstanding issues, and planning releases. This meeting MUST be advertised on the project README and MAY happen on a phone call, video conference, or on IRC. Maintainers MUST send updates to the 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. ). 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. ## Checklist Releases usually follow a few steps: - [ ] prepare a pull-request for the release - [ ] a commit updating `./ChangeLog` - [ ] `git log --oneline --no-merges --decorate --name-status v1.0.1..HEAD | vim -` - [ ] `:% s/(pr\/\(\d*\))\(.*\)/\2 (#\1)/` to move the PR to the end of line and match previous formatting - [ ] review `(^M|^A|^D)` for impact of the commit - [ ] group commits to `Additions:`, `Minor fixes and documentation:`, `Breaking changes:` - [ ] delete the `(^M|^A|^D)` lines, `:%!grep -vE '(^M|^A|^D)'` - [ ] merge multi-commit PRs (so each line has a `(#num)` suffix) - [ ] drop hash and indent, `:'<,'> s/^\w* /^I* /` - [ ] a commit bumping `./specs-go/version.go` to next version and empty the `VersionDev` variable - [ ] a commit adding back the "+dev" to `VersionDev` - [ ] send email to - [ ] copy the exact commit hash for bumping the version from the pull-request (since master always stays as "-dev") - [ ] count the PRs since last release (that this version is tracking, in the cases of multiple branching), like `git log --pretty=oneline --no-merges --decorate $priorTag..$versionBumpCommit | grep \(pr\/ | wc -l` - [ ] get the date for a week from now, like `TZ=UTC date --date='next week'` - [ ] OPTIONAL find a cute animal gif to attach to the email, and subsequently the release description - [ ] subject line like `[image-spec VOTE] tag $versionBumpCommit as $version (closes $dateWeekFromNowUTC)` - [ ] email body like ```text Hey everyone, There have been $numPRs PRs merged since $priorTag release (https://github.com/opencontainers/image-spec/compare/$priorTag...$versionBumpCommit). $linkToPullRequest Please respond LGTM or REJECT (with reasoning). $sig ``` - [ ] edit/update the pull-request to link to the VOTE thread, from - [ ] a week later, if the vote passes, merge the PR - [ ] `git tag -s $version $versionBumpCommit` - [ ] `git push --tags` - [ ] produce release documents - [ ] git checkout the release tag, like `git checkout $version` - [ ] `make docs` - [ ] rename the output PDF and HTML file to include version, like `mv output/oci-image-spec.pdf output/oci-image-spec-$version.pdf`` - [ ] attach these docs to the release on - [ ] link to the the VOTE thread and include the passing vote count - [ ] link to the pull request that merged the release - [ ] add release notes to the website [charter]: https://github.com/opencontainers/tob/blob/main/CHARTER.md image-spec-1.1.0/annotations.md000066400000000000000000000136171455506543700164300ustar00rootroot00000000000000# Annotations Several components of the specification, like [Image Manifests](manifest.md) and [Descriptors](descriptor.md), feature an optional annotations property, whose format is common and defined in this section. This property contains arbitrary metadata. ## Rules - Annotations MUST be a key-value map where both the key and value MUST be strings. - While the value MUST be present, it MAY be an empty string. - Keys MUST be unique within this map, and best practice is to namespace the keys. - Keys SHOULD be named using a reverse domain notation - e.g. `com.example.myKey`. - The prefix `org.opencontainers` is reserved for keys defined in Open Container Initiative (OCI) specifications and MUST NOT be used by other specifications and extensions. - Keys using the `org.opencontainers.image` namespace are reserved for use in the OCI Image Specification and MUST NOT be used by other specifications and extensions, including other OCI specifications. - If there are no annotations then this property MUST either be absent or be an empty map. - Consumers MUST NOT generate an error if they encounter an unknown annotation key. ## Pre-Defined Annotation Keys This specification defines the following annotation keys, intended for but not limited to [image index](image-index.md), image [manifest](manifest.md), and [descriptor](descriptor.md) authors. - **org.opencontainers.image.created** date and time on which the image was built, conforming to [RFC 3339][rfc3339]. - **org.opencontainers.image.authors** contact details of the people or organization responsible for the image (freeform string) - **org.opencontainers.image.url** URL to find more information on the image (string) - **org.opencontainers.image.documentation** URL to get documentation on the image (string) - **org.opencontainers.image.source** URL to get source code for building the image (string) - **org.opencontainers.image.version** version of the packaged software - The version MAY match a label or tag in the source code repository - version MAY be [Semantic versioning-compatible](https://semver.org/) - **org.opencontainers.image.revision** Source control revision identifier for the packaged software. - **org.opencontainers.image.vendor** Name of the distributing entity, organization or individual. - **org.opencontainers.image.licenses** License(s) under which contained software is distributed as an [SPDX License Expression][spdx-license-expression]. - **org.opencontainers.image.ref.name** Name of the reference for a target (string). - SHOULD only be considered valid when on descriptors on `index.json` within [image layout](image-layout.md). - Character set of the value SHOULD conform to alphanum of `A-Za-z0-9` and separator set of `-._:@/+` - The reference must match the following [grammar](considerations.md#ebnf): ```ebnf ref ::= component ("/" component)* component ::= alphanum (separator alphanum)* alphanum ::= [A-Za-z0-9]+ separator ::= [-._:@+] | "--" ``` - **org.opencontainers.image.title** Human-readable title of the image (string) - **org.opencontainers.image.description** Human-readable description of the software packaged in the image (string) - **org.opencontainers.image.base.digest** [Digest](descriptor.md#digests) of the image this image is based on (string) - This SHOULD be the immediate image sharing zero-indexed layers with the image, such as from a Dockerfile `FROM` statement. - This SHOULD NOT reference any other images used to generate the contents of the image (e.g., multi-stage Dockerfile builds). - **org.opencontainers.image.base.name** Image reference of the image this image is based on (string) - This SHOULD be image references in the format defined by [distribution/distribution][distribution-reference]. - This SHOULD be a fully qualified reference name, without any assumed default registry. (e.g., `registry.example.com/my-org/my-image:tag` instead of `my-org/my-image:tag`). - This SHOULD be the immediate image sharing zero-indexed layers with the image, such as from a Dockerfile `FROM` statement. - This SHOULD NOT reference any other images used to generate the contents of the image (e.g., multi-stage Dockerfile builds). - If the `image.base.name` annotation is specified, the `image.base.digest` annotation SHOULD be the digest of the manifest referenced by the `image.ref.name` annotation. ## Back-compatibility with Label Schema [Label Schema][label-schema] defined a number of conventional labels for container images, and these are now superceded by annotations with keys starting **org.opencontainers.image**. While users are encouraged to use the **org.opencontainers.image** keys, tools MAY choose to support compatible annotations using the **org.label-schema** prefix as follows. | `org.opencontainers.image` prefix | `org.label-schema` prefix | Compatibility notes | |---------------------------|-------------------------|---------------------| | `created` | `build-date` | Compatible | | `url` | `url` | Compatible | | `source` | `vcs-url` | Compatible | | `version` | `version` | Compatible | | `revision` | `vcs-ref` | Compatible | | `vendor` | `vendor` | Compatible | | `title` | `name` | Compatible | | `description` | `description` | Compatible | | `documentation` | `usage` | Value is compatible if the documentation is located by a URL | | `authors` | | No equivalent in Label Schema | | `licenses` | | No equivalent in Label Schema | | `ref.name` | | No equivalent in Label Schema | | | `schema-version`| No equivalent in the OCI Image Spec | | | `docker.*`, `rkt.*` | No equivalent in the OCI Image Spec | [distribution-reference]: https://github.com/distribution/distribution/blob/d0deff9cd6c2b8c82c6f3d1c713af51df099d07b/reference/reference.go [label-schema]: https://github.com/label-schema/label-schema.org/blob/gh-pages/rc1.md [rfc3339]: https://tools.ietf.org/html/rfc3339#section-5.6 [spdx-license-expression]: https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/ image-spec-1.1.0/artifacts-guidance.md000066400000000000000000000011331455506543700176160ustar00rootroot00000000000000# Guidance for Artifacts Authors Content other than OCI container images MAY be packaged using the image manifest. When this is done, the `config.mediaType` value should not be a known OCI image config [media type](media-types.md). Historically, due to registry limitations, some tools have created non-OCI conformant artifacts using the `application/vnd.oci.image.config.v1+json` value for `config.mediaType` and values specific to the artifact in `layer[*].mediaType`. Implementation details and examples are provided in the [image manifest specification](manifest.md#guidelines-for-artifact-usage). image-spec-1.1.0/config.md000066400000000000000000000361651455506543700153430ustar00rootroot00000000000000# OCI Image Configuration An OCI _Image_ is an ordered collection of root filesystem changes and the corresponding execution parameters for use within a container runtime. This specification outlines the JSON format describing images for use with a container runtime and execution tool and its relationship to filesystem changesets, described in [Layers](layer.md). This section defines the `application/vnd.oci.image.config.v1+json` [media type](media-types.md). ## Terminology This specification uses the following terms: ### [Layer](layer.md) - Image filesystems are composed of _layers_. - Each layer represents a set of filesystem changes in a tar-based [layer format](layer.md), recording files to be added, changed, or deleted relative to its parent layer. - Layers do not have configuration metadata such as environment variables or default arguments - these are properties of the image as a whole rather than any particular layer. - Using a layer-based or union filesystem such as AUFS, or by computing the diff from filesystem snapshots, the filesystem changeset can be used to present a series of image layers as if they were one cohesive filesystem. ### Image JSON - Each image has an associated JSON structure which describes some basic information about the image such as date created, author, as well as execution/runtime configuration like its entrypoint, default arguments, networking, and volumes. - The JSON structure also references a cryptographic hash of each layer used by the image, and provides history information for those layers. - This JSON is considered to be immutable, because changing it would change the computed [ImageID](#imageid). - Changing it means creating a new derived image, instead of changing the existing image. ### Layer DiffID A layer DiffID is the digest over the layer's uncompressed tar archive and serialized in the descriptor digest format, e.g., `sha256:a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9`. Layers SHOULD be packed and unpacked reproducibly to avoid changing the layer DiffID, for example by using [tar-split][] to save the tar headers. NOTE: Do not confuse DiffIDs with [layer digests](manifest.md#image-manifest-property-descriptions), often referenced in the manifest, which are digests over compressed or uncompressed content. ### Layer ChainID For convenience, it is sometimes useful to refer to a stack of layers with a single identifier. While a layer's `DiffID` identifies a single changeset, the `ChainID` identifies the subsequent application of those changesets. This ensures that we have handles referring to both the layer itself, as well as the result of the application of a series of changesets. Use in combination with `rootfs.diff_ids` while applying layers to a root filesystem to uniquely and safely identify the result. #### Definition The `ChainID` of an applied set of layers is defined with the following recursion: ```text ChainID(L₀) = DiffID(L₀) ChainID(L₀|...|Lₙ₋₁|Lₙ) = Digest(ChainID(L₀|...|Lₙ₋₁) + " " + DiffID(Lₙ)) ``` For this, we define the binary `|` operation to be the result of applying the right operand to the left operand. For example, given base layer `A` and a changeset `B`, we refer to the result of applying `B` to `A` as `A|B`. Above, we define the `ChainID` for a single layer (`L₀`) as equivalent to the `DiffID` for that layer. Otherwise, the `ChainID` for a set of applied layers (`L₀|...|Lₙ₋₁|Lₙ`) is defined as the recursion `Digest(ChainID(L₀|...|Lₙ₋₁) + " " + DiffID(Lₙ))`. #### Explanation Let's say we have layers A, B, C, ordered from bottom to top, where A is the base and C is the top. Defining `|` as a binary application operator, the root filesystem may be `A|B|C`. While it is implied that `C` is only useful when applied to `A|B`, the identifier `C` is insufficient to identify this result, as we'd have the equality `C = A|B|C`, which isn't true. The main issue is when we have two definitions of `C`, `C = C` and `C = A|B|C`. If this is true (with some handwaving), `C = x|C` where `x = any application`. This means that if an attacker can define `x`, relying on `C` provides no guarantee that the layers were applied in any order. The `ChainID` addresses this problem by being defined as a compound hash. **We differentiate the changeset `C`, from the order-dependent application `A|B|C` by saying that the resulting rootfs is identified by ChainID(A|B|C), which can be calculated by `ImageConfig.rootfs`.** Let's expand the definition of `ChainID(A|B|C)` to explore its internal structure: ```text ChainID(A) = DiffID(A) ChainID(A|B) = Digest(ChainID(A) + " " + DiffID(B)) ChainID(A|B|C) = Digest(ChainID(A|B) + " " + DiffID(C)) ``` We can replace each definition and reduce to a single equality: ```text ChainID(A|B|C) = Digest(Digest(DiffID(A) + " " + DiffID(B)) + " " + DiffID(C)) ``` Hopefully, the above is illustrative of the _actual_ contents of the `ChainID`. Most importantly, we can easily see that `ChainID(C) != ChainID(A|B|C)`, otherwise, `ChainID(C) = DiffID(C)`, which is the base case, could not be true. ### ImageID Each image's ID is given by the SHA256 hash of its [configuration JSON](#image-json). It is represented as a hexadecimal encoding of 256 bits, e.g., `sha256:a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9`. Since the [configuration JSON](#image-json) that gets hashed references hashes of each layer in the image, this formulation of the ImageID makes images content-addressable. ## Properties Note: Any OPTIONAL field MAY also be set to null, which is equivalent to being absent. - **created** _string_, OPTIONAL An combined date and time at which the image was created, formatted as defined by [RFC 3339, section 5.6][rfc3339-s5.6]. - **author** _string_, OPTIONAL Gives the name and/or email address of the person or entity which created and is responsible for maintaining the image. - **architecture** _string_, REQUIRED The CPU architecture which the binaries in this image are built to run on. Configurations SHOULD use, and implementations SHOULD understand, values listed in the Go Language document for [`GOARCH`][go-environment]. - **os** _string_, REQUIRED The name of the operating system which the image is built to run on. Configurations SHOULD use, and implementations SHOULD understand, values listed in the Go Language document for [`GOOS`][go-environment]. - **os.version** _string_, OPTIONAL This OPTIONAL property specifies the version of the operating system targeted by the referenced blob. Implementations MAY refuse to use manifests where `os.version` is not known to work with the host OS version. Valid values are implementation-defined. e.g. `10.0.14393.1066` on `windows`. - **os.features** _array of strings_, OPTIONAL This OPTIONAL property specifies an array of strings, each specifying a mandatory OS feature. When `os` is `windows`, image indexes SHOULD use, and implementations SHOULD understand the following values: - `win32k`: image requires `win32k.sys` on the host (Note: `win32k.sys` is missing on Nano Server) - **variant** _string_, OPTIONAL The variant of the specified CPU architecture. Configurations SHOULD use, and implementations SHOULD understand, `variant` values listed in the [Platform Variants](image-index.md#platform-variants) table. - **config** _object_, OPTIONAL The execution parameters which SHOULD be used as a base when running a container using the image. This field can be `null`, in which case any execution parameters should be specified at creation of the container. - **User** _string_, OPTIONAL The username or UID which is a platform-specific structure that allows specific control over which user the process run as. This acts as a default value to use when the value is not specified when creating a container. For Linux based systems, all of the following are valid: `user`, `uid`, `user:group`, `uid:gid`, `uid:group`, `user:gid`. If `group`/`gid` is not specified, the default group and supplementary groups of the given `user`/`uid` in `/etc/passwd` and `/etc/group` from the container are applied. If `group`/`gid` is specified, supplementary groups from the container are ignored. - **ExposedPorts** _object_, OPTIONAL A set of ports to expose from a container running this image. Its keys can be in the format of: `port/tcp`, `port/udp`, `port` with the default protocol being `tcp` if not specified. These values act as defaults and are merged with any specified when creating a container. **NOTE:** This JSON structure value is unusual because it is a direct JSON serialization of the Go type `map[string]struct{}` and is represented in JSON as an object mapping its keys to an empty object. - **Env** _array of strings_, OPTIONAL Entries are in the format of `VARNAME=VARVALUE`. These values act as defaults and are merged with any specified when creating a container. - **Entrypoint** _array of strings_, OPTIONAL A list of arguments to use as the command to execute when the container starts. These values act as defaults and may be replaced by an entrypoint specified when creating a container. - **Cmd** _array of strings_, OPTIONAL Default arguments to the entrypoint of the container. These values act as defaults and may be replaced by any specified when creating a container. If an `Entrypoint` value is not specified, then the first entry of the `Cmd` array SHOULD be interpreted as the executable to run. - **Volumes** _object_, OPTIONAL A set of directories describing where the process is likely to write data specific to a container instance. **NOTE:** This JSON structure value is unusual because it is a direct JSON serialization of the Go type `map[string]struct{}` and is represented in JSON as an object mapping its keys to an empty object. - **WorkingDir** _string_, OPTIONAL Sets the current working directory of the entrypoint process in the container. This value acts as a default and may be replaced by a working directory specified when creating a container. - **Labels** _object_, OPTIONAL The field contains arbitrary metadata for the container. This property MUST use the [annotation rules](annotations.md#rules). - **StopSignal** _string_, OPTIONAL The field contains the system call signal that will be sent to the container to exit. The signal can be a signal name in the format `SIGNAME`, for instance `SIGKILL` or `SIGRTMIN+3`. - **ArgsEscaped** _boolean_, OPTIONAL `[Deprecated]` - This field is present only for legacy compatibility with Docker and should not be used by new image builders. It is used by Docker for Windows images to indicate that the `Entrypoint` or `Cmd` or both, contains only a single element array, that is a pre-escaped, and combined into a single string `CommandLine`. If `true` the value in `Entrypoint` or `Cmd` should be used as-is to avoid double escaping. Note, the exact behavior of `ArgsEscaped` is complex and subject to implementation details in Moby project. - **Memory** _integer_, OPTIONAL This property is _reserved_ for use, to [maintain compatibility](media-types.md#compatibility-matrix). - **MemorySwap** _integer_, OPTIONAL This property is _reserved_ for use, to [maintain compatibility](media-types.md#compatibility-matrix). - **CpuShares** _integer_, OPTIONAL This property is _reserved_ for use, to [maintain compatibility](media-types.md#compatibility-matrix). - **Healthcheck** _object_, OPTIONAL This property is _reserved_ for use, to [maintain compatibility](media-types.md#compatibility-matrix). - **rootfs** _object_, REQUIRED The rootfs key references the layer content addresses used by the image. This makes the image config hash depend on the filesystem hash. - **type** _string_, REQUIRED MUST be set to `layers`. Implementations MUST generate an error if they encounter a unknown value while verifying or unpacking an image. - **diff_ids** _array of strings_, REQUIRED An array of layer content hashes (`DiffIDs`), in order from first to last. - **history** _array of objects_, OPTIONAL Describes the history of each layer. The array is ordered from first to last. The object has the following fields: - **created** _string_, OPTIONAL A combined date and time at which the layer was created, formatted as defined by [RFC 3339, section 5.6][rfc3339-s5.6]. - **author** _string_, OPTIONAL The author of the build point. - **created_by** _string_, OPTIONAL The command which created the layer. - **comment** _string_, OPTIONAL A custom message set when creating the layer. - **empty_layer** _boolean_, OPTIONAL This field is used to mark if the history item created a filesystem diff. It is set to true if this history item doesn't correspond to an actual layer in the rootfs section (for example, Dockerfile's [ENV](https://docs.docker.com/engine/reference/builder/#/env) command results in no change to the filesystem). Any extra fields in the Image JSON struct are considered implementation specific and MUST NOT generate an error by any implementations which are unable to interpret them. Whitespace is OPTIONAL and implementations MAY have compact JSON with no whitespace. ## Example Here is an example image configuration JSON document: ```json,title=Image%20JSON&mediatype=application/vnd.oci.image.config.v1%2Bjson { "created": "2015-10-31T22:22:56.015925234Z", "author": "Alyssa P. Hacker ", "architecture": "amd64", "os": "linux", "config": { "User": "alice", "ExposedPorts": { "8080/tcp": {} }, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "FOO=oci_is_a", "BAR=well_written_spec" ], "Entrypoint": [ "/bin/my-app-binary" ], "Cmd": [ "--foreground", "--config", "/etc/my-app.d/default.cfg" ], "Volumes": { "/var/job-result-data": {}, "/var/log/my-app-logs": {} }, "WorkingDir": "/home/alice", "Labels": { "com.example.project.git.url": "https://example.com/project.git", "com.example.project.git.commit": "45a939b2999782a3f005621a8d0f29aa387e1d6b" } }, "rootfs": { "diff_ids": [ "sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1", "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" ], "type": "layers" }, "history": [ { "created": "2015-10-31T22:22:54.690851953Z", "created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /" }, { "created": "2015-10-31T22:22:55.613815829Z", "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]", "empty_layer": true }, { "created": "2015-10-31T22:22:56.329850019Z", "created_by": "/bin/sh -c apk add curl" } ] } ``` [rfc3339-s5.6]: https://tools.ietf.org/html/rfc3339#section-5.6 [go-environment]: https://golang.org/doc/install/source#environment [tar-split]: https://github.com/vbatts/tar-split image-spec-1.1.0/considerations.md000066400000000000000000000137511455506543700171160ustar00rootroot00000000000000# Considerations ## Extensibility Implementations storing or copying content MUST NOT modify or alter the content in a way that would change the digest of the content. Examples of these implementations include: - A [registry implementing the distribution specification][distribution-spec], including local registries, caching proxies - An application which copies content to disk or between registries Implementations processing content SHOULD NOT generate an error if they encounter an unknown property in a known media type. Examples of these implementations include: - A [runtime implementing the runtime specification][runtime-spec] - An implementation using OCI to retrieve and utilize artifacts, e.g.: a WASM runtime ## Canonicalization - OCI Images are [content-addressable](https://en.wikipedia.org/wiki/Content-addressable_storage). See [descriptors](descriptor.md) for more. - One benefit of content-addressable storage is easy deduplication. - Many images might depend on a particular [layer](layer.md), but there will only be one blob in the [store](image-layout.md). - With a different serialization, that same semantic layer would have a different hash, and if both versions of the layer are referenced there will be two blobs with the same semantic content. - To allow efficient storage, implementations serializing content for blobs SHOULD use a canonical serialization. - This increases the chance that different implementations can push the same semantic content to the store without creating redundant blobs. ### JSON [JSON][JSON] content SHOULD be serialized as [canonical JSON][canonical-json]. Of the [OCI Image Format Specification media types](media-types.md), all the types ending in `+json` contain JSON content. Implementations: - [Go][Go]: [github.com/docker/go][docker-go], which claims to implement [canonical JSON][canonical-json] except for Unicode normalization. ## EBNF For field formats described in this specification, we use a limited subset of [Extended Backus-Naur Form][ebnf], similar to that used by the [XML specification][xmlebnf]. Grammars present in the OCI specification are regular and can be converted to a single regular expressions. However, regular expressions are avoided to limit ambiguity between regular expression syntax. By defining a subset of EBNF used here, the possibility of variation, misunderstanding or ambiguities from linking to a larger specification can be avoided. Grammars are made up of rules in the following form: ```ebnf symbol ::= expression ``` We can say we have the production identified by symbol if the input is matched by the expression. Whitespace is completely ignored in rule definitions. ### Expressions The simplest expression is the literal, surrounded by quotes: ```ebnf literal ::= "matchthis" ``` The above expression defines a symbol, "literal", that matches the exact input of "matchthis". Character classes are delineated by brackets (`[]`), describing either a set, range or multiple range of characters: ```ebnf set := [abc] range := [A-Z] ``` The above symbol "set" would match one character of either "a", "b" or "c". The symbol "range" would match any character, "A" to "Z", inclusive. Currently, only matching for 7-bit ascii literals and character classes is defined, as that is all that is required by this specification. Multiple character ranges and explicit characters can be specified in a single character classes, as follows: ```ebnf multipleranges := [a-zA-Z=-] ``` The above matches the characters in the range `A` to `Z`, `a` to `z` and the individual characters `-` and `=`. Expressions can be made up of one or more expressions, such that one must be followed by the other. This is known as an implicit concatenation operator. For example, to satisfy the following rule, both `A` and `B` must be matched to satisfy the rule: ```ebnf symbol ::= A B ``` Each expression must be matched once and only once, `A` followed by `B`. To support the description of repetition and optional match criteria, the postfix operators `*` and `+` are defined. `*` indicates that the preceding expression can be matched zero or more times. `+` indicates that the preceding expression must be matched one or more times. These appear in the following form: ```ebnf zeroormore ::= expression* oneormore ::= expression+ ``` Parentheses are used to group expressions into a larger expression: ```ebnf group ::= (A B) ``` Like simpler expressions above, operators can be applied to groups, as well. To allow for alternates, we also define the infix operator `|`. ```ebnf oneof ::= A | B ``` The above indicates that the expression should match one of the expressions, `A` or `B`. ### Precedence The operator precedence is in the following order: - Terminals (literals and character classes) - Grouping `()` - Unary operators `+*` - Concatenation - Alternates `|` The precedence can be better described using grouping to show equivalents. Concatenation has higher precedence than alternates, such `A B | C D` is equivalent to `(A B) | (C D)`. Unary operators have higher precedence than alternates and concatenation, such that `A+ | B+` is equivalent to `(A+) | (B+)`. ### Examples The following combines the previous definitions to match a simple, relative path name, describing the individual components: ```ebnf path ::= component ("/" component)* component ::= [a-z]+ ``` The production "component" is one or more lowercase letters. A "path" is then at least one component, possibly followed by zero or more slash-component pairs. The above can be converted into the following regular expression: ```regex [a-z]+(?:/[a-z]+)* ``` [canonical-json]: https://wiki.laptop.org/go/Canonical_JSON [distribution-spec]: https://github.com/opencontainers/distribution-spec/blob/main/spec.md [docker-go]: https://github.com/docker/go/ [ebnf]: https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form [Go]: https://golang.org/ [JSON]: https://json.org/ [runtime-spec]: https://github.com/opencontainers/runtime-spec/blob/main/spec.md [xmlebnf]: https://www.w3.org/TR/REC-xml/#sec-notation image-spec-1.1.0/conversion.md000066400000000000000000000226331455506543700162560ustar00rootroot00000000000000# Conversion to OCI Runtime Configuration When extracting an OCI Image into an [OCI Runtime bundle][oci-runtime-bundle], two orthogonal components of the extraction are relevant: 1. Extraction of the root filesystem from the set of [filesystem layers](layer.md). 2. Conversion of the [image configuration blob](config.md) to an [OCI Runtime configuration blob][oci-runtime-config]. This section defines how to convert an `application/vnd.oci.image.config.v1+json` blob to an [OCI runtime configuration blob][oci-runtime-config] (the latter component of extraction). The former component of extraction is defined [elsewhere](layer.md) and is orthogonal to configuration of a runtime bundle. The values of runtime configuration properties not specified by this document are implementation-defined. A converter MUST rely on the OCI image configuration to build the OCI runtime configuration as described by this document; this will create the "default generated runtime configuration". The "default generated runtime configuration" MAY be overridden or combined with externally provided inputs from the caller. In addition, a converter MAY have its own implementation-defined defaults and extensions which MAY be combined with the "default generated runtime configuration". The restrictions in this document refer only to combining implementation-defined defaults with the "default generated runtime configuration". Externally provided inputs are considered to be a modification of the `application/vnd.oci.image.config.v1+json` used as a source, and such modifications have no restrictions. For example, externally provided inputs MAY cause an environment variable to be added, removed or changed. However an implementation-defined default SHOULD NOT result in an environment variable being removed or changed. [oci-runtime-bundle]: https://github.com/opencontainers/runtime-spec/blob/v1.0.0/bundle.md [oci-runtime-config]: https://github.com/opencontainers/runtime-spec/blob/v1.0.0/config.md ## Verbatim Fields Certain image configuration fields have an identical counterpart in the runtime configuration. Some of these are purely annotation-based fields, and have been extracted into a [separate subsection](#annotation-fields). A compliant configuration converter MUST extract the following fields verbatim to the corresponding field in the generated runtime configuration: | Image Field | Runtime Field | Notes | | ------------------- | --------------- | ----- | | `Config.WorkingDir` | `process.cwd` | | | `Config.Env` | `process.env` | 1 | | `Config.Entrypoint` | `process.args` | 2 | | `Config.Cmd` | `process.args` | 2 | 1. The converter MAY add additional entries to `process.env` but it SHOULD NOT add entries that have variable names present in `Config.Env`. 2. If both `Config.Entrypoint` and `Config.Cmd` are specified, the converter MUST append the value of `Config.Cmd` to the value of `Config.Entrypoint` and set `process.args` to that combined value. ### Annotation Fields These fields all affect the `annotations` of the runtime configuration, and are thus subject to [precedence](#annotations). | Image Field | Runtime Field | Notes | | ------------------- | --------------- | ----- | | `os` | `annotations` | 1,2 | | `architecture` | `annotations` | 1,3 | | `variant` | `annotations` | 1,4 | | `os.version` | `annotations` | 1,5 | | `os.features` | `annotations` | 1,6 | | `author` | `annotations` | 1,7 | | `created` | `annotations` | 1,8 | | `Config.Labels` | `annotations` | | | `Config.StopSignal` | `annotations` | 1,9 | 1. If a user has explicitly specified this annotation with `Config.Labels`, then the value specified in this field takes lower [precedence](#annotations) and the converter MUST instead use the value from `Config.Labels`. 2. The value of this field MUST be set as the value of `org.opencontainers.image.os` in `annotations`. 3. The value of this field MUST be set as the value of `org.opencontainers.image.architecture` in `annotations`. 4. The value of this field MUST be set as the value of `org.opencontainers.image.variant` in `annotations`. 5. The value of this field MUST be set as the value of `org.opencontainers.image.os.version` in `annotations`. 6. The value of this field MUST be set as the value of `org.opencontainers.image.os.features` in `annotations`. 7. The value of this field MUST be set as the value of `org.opencontainers.image.author` in `annotations`. 8. The value of this field MUST be set as the value of `org.opencontainers.image.created` in `annotations`. 9. The value of this field MUST be set as the value of `org.opencontainers.image.stopSignal` in `annotations`. ## Parsed Fields Certain image configuration fields have a counterpart that must first be translated. A compliant configuration converter SHOULD parse all of these fields and set the corresponding fields in the generated runtime configuration: | Image Field | Runtime Field | | ------------------- | --------------- | | `Config.User` | `process.user.*` | The method of parsing the above image fields are described in the following sections. ### `Config.User` If the values of [`user` or `group`](config.md#properties) in `Config.User` are numeric (`uid` or `gid`) then the values MUST be copied verbatim to `process.user.uid` and `process.user.gid` respectively. If the values of [`user` or `group`](config.md#properties) in `Config.User` are not numeric (`user` or `group`) then a converter SHOULD resolve the user information using a method appropriate for the container's context. For Unix-like systems, this MAY involve resolution through NSS or parsing `/etc/passwd` from the extracted container's root filesystem to determine the values of `process.user.uid` and `process.user.gid`. In addition, a converter SHOULD set the value of `process.user.additionalGids` to a value corresponding to the user in the container's context described by `Config.User`. For Unix-like systems, this MAY involve resolution through NSS or parsing `/etc/group` and determining the group memberships of the user specified in `process.user.uid`. The converter SHOULD NOT modify `process.user.additionalGids` if the value of [`user`](config.md#properties) in `Config.User` is numeric or if `Config.User` specifies a group. If `Config.User` is not defined, the converted `process.user` value is implementation-defined. If `Config.User` does not correspond to a user in the container's context, the converter MUST return an error. ## Optional Fields Certain image configuration fields are not applicable to all conversion use cases, and thus are optional for configuration converters to implement. A compliant configuration converter SHOULD provide a way for users to extract these fields into the generated runtime configuration: | Image Field | Runtime Field | Notes | | --------------------- | ------------------ | ----- | | `Config.ExposedPorts` | `annotations` | 1 | | `Config.Volumes` | `mounts` | 2 | 1. The runtime configuration does not have a corresponding field for this image field. However, converters SHOULD set the [`org.opencontainers.image.exposedPorts` annotation](#configexposedports). 2. Implementations SHOULD provide mounts for these locations such that application data is not written to the container's root filesystem. If a converter implements conversion for this field using mountpoints, it SHOULD set the `destination` of the mountpoint to the value specified in `Config.Volumes`. An implementation MAY seed the contents of the mount with data in the image at the same location. If a _new_ image is created from a container based on the image described by this configuration, data in these paths SHOULD NOT be included in the _new_ image. The other `mounts` fields are platform and context dependent, and thus are implementation-defined. Note that the implementation of `Config.Volumes` need not use mountpoints, as it is effectively a mask of the filesystem. ### `Config.ExposedPorts` The OCI runtime configuration does not provide a way of expressing the concept of "container exposed ports". However, converters SHOULD set the **org.opencontainers.image.exposedPorts** annotation, unless doing so will [cause a conflict](#annotations). **org.opencontainers.image.exposedPorts** is the list of values that correspond to the [keys defined for `Config.ExposedPorts`](config.md) (string, comma-separated values). ## Annotations There are three ways of annotating an OCI image in this specification: 1. `Config.Labels` in the [configuration](config.md) of the image. 2. `annotations` in the [manifest](manifest.md) of the image. 3. `annotations` in the [image index](image-index.md) of the image. In addition, there are also implicit annotations that are defined by this section which are determined from the values of the image configuration. A converter SHOULD NOT attempt to extract annotations from [manifests](manifest.md) or [image indices](image-index.md). If there is a conflict (same key but different value) between an implicit annotation (or annotation in [manifests](manifest.md) or [image indices](image-index.md)) and an explicitly specified annotation in `Config.Labels`, the value specified in `Config.Labels` MUST take precedence. A converter MAY add annotations which have keys not specified in the image. A converter MUST NOT modify the values of annotations specified in the image. image-spec-1.1.0/descriptor.md000066400000000000000000000272121455506543700162450ustar00rootroot00000000000000# OCI Content Descriptors - An OCI image consists of several different components, arranged in a [Merkle Directed Acyclic Graph (DAG)](https://en.wikipedia.org/wiki/Merkle_tree). - References between components in the graph are expressed through _Content Descriptors_. - A Content Descriptor (or simply _Descriptor_) describes the disposition of the targeted content. - A Content Descriptor includes the type of the content, a content identifier (_digest_), and the byte-size of the raw content. Optionally, it includes the type of artifact it is describing. - Descriptors SHOULD be embedded in other formats to securely reference external content. - Other formats SHOULD use descriptors to securely reference external content. This section defines the `application/vnd.oci.descriptor.v1+json` [media type](media-types.md). ## Properties A descriptor consists of a set of properties encapsulated in key-value fields. The following fields contain the primary properties that constitute a Descriptor: - **`mediaType`** *string* This REQUIRED property contains the media type of the referenced content. Values MUST comply with [RFC 6838][rfc6838], including the [naming requirements in its section 4.2][rfc6838-s4.2]. The OCI image specification defines [several of its own MIME types](media-types.md) for resources defined in the specification. - **`digest`** *string* This REQUIRED property is the _digest_ of the targeted content, conforming to the requirements outlined in [Digests](#digests). Retrieved content SHOULD be verified against this digest when consumed via untrusted sources. - **`size`** *int64* This REQUIRED property specifies the size, in bytes, of the raw content. This property exists so that a client will have an expected size for the content before processing. If the length of the retrieved content does not match the specified length, the content SHOULD NOT be trusted. - **`urls`** *array of strings* This OPTIONAL property specifies a list of URIs from which this object MAY be downloaded. Each entry MUST conform to [RFC 3986][rfc3986]. Entries SHOULD use the `http` and `https` schemes, as defined in [RFC 7230][rfc7230-s2.7]. - **`annotations`** *string-string map* This OPTIONAL property contains arbitrary metadata for this descriptor. This OPTIONAL property MUST use the [annotation rules](annotations.md#rules). - **`data`** *string* This OPTIONAL property contains an embedded representation of the referenced content. Values MUST conform to the Base 64 encoding, as defined in [RFC 4648][rfc4648-s4]. The decoded data MUST be identical to the referenced content and SHOULD be verified against the [`digest`](#digests) and `size` fields by content consumers. See [Embedded Content](#embedded-content) for when this is appropriate. - **`artifactType`** *string* This OPTIONAL property contains the type of an artifact when the descriptor points to an artifact. This is the value of the config descriptor `mediaType` when the descriptor references an [image manifest](manifest.md). If defined, the value MUST comply with [RFC 6838][rfc6838], including the [naming requirements in its section 4.2][rfc6838-s4.2], and MAY be registered with [IANA][iana]. Descriptors pointing to [`application/vnd.oci.image.manifest.v1+json`](manifest.md) SHOULD include the extended field `platform`, see [Image Index Property Descriptions](image-index.md#image-index-property-descriptions) for details. ### Reserved Extended _Descriptor_ field additions proposed in other OCI specifications SHOULD first be considered for addition into this specification. ## Digests The _digest_ property of a Descriptor acts as a content identifier, enabling [content addressability](https://en.wikipedia.org/wiki/Content-addressable_storage). It uniquely identifies content by taking a [collision-resistant hash](https://en.wikipedia.org/wiki/Cryptographic_hash_function) of the bytes. If the _digest_ can be communicated in a secure manner, one can verify content from an insecure source by recalculating the digest independently, ensuring the content has not been modified. The value of the `digest` property is a string consisting of an _algorithm_ portion and an _encoded_ portion. The _algorithm_ specifies the cryptographic hash function and encoding used for the digest; the _encoded_ portion contains the encoded result of the hash function. A digest string MUST match the following [grammar](considerations.md#ebnf): ```ebnf digest ::= algorithm ":" encoded algorithm ::= algorithm-component (algorithm-separator algorithm-component)* algorithm-component ::= [a-z0-9]+ algorithm-separator ::= [+._-] encoded ::= [a-zA-Z0-9=_-]+ ``` Note that _algorithm_ MAY impose algorithm-specific restriction on the grammar of the _encoded_ portion. See also [Registered Algorithms](#registered-algorithms). Some example digest strings include the following: | digest | algorithm | Registered | |---------------------------------------------------------------------------|-----------------------------|------------| | `sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b` | [SHA-256](#sha-256) | Yes | | `sha512:401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b372742...` | [SHA-512](#sha-512) | Yes | | `multihash+base58:QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8` | Multihash | No | | `sha256+b64u:LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564` | SHA-256 with urlsafe base64 | No | Please see [Registered Algorithms](#registered-algorithms) for a list of registered algorithms. Implementations SHOULD allow digests with unrecognized algorithms to pass validation if they comply with the above grammar. While `sha256` will only use hex encoded digests, separators in _algorithm_ and alphanumerics in _encoded_ are included to allow for extensions. As an example, we can parameterize the encoding and algorithm as `multihash+base58:QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8`, which would be considered valid but unregistered by this specification. ### Verification Before consuming content targeted by a descriptor from untrusted sources, the byte content SHOULD be verified against the digest string. Before calculating the digest, the size of the content SHOULD be verified to reduce hash collision space. Heavy processing before calculating a hash SHOULD be avoided. Implementations MAY employ [canonicalization](considerations.md#canonicalization) of the underlying content to ensure stable content identifiers. ### Digest calculations A _digest_ is calculated by the following pseudo-code, where `H` is the selected hash algorithm, identified by string ``: ```text 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: ```text 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 performant][sha256-vs-sha512] than [SHA-256](#sha-256) on some CPUs. Implementations MAY implement SHA-512 digest verification for use in descriptors. When the _algorithm identifier_ is `sha512`, the _encoded_ portion MUST match `/[a-f0-9]{128}/`. Note that `[A-F]` MUST NOT be used here. ## Embedded Content In many contexts, such as when downloading content over a network, resolving a descriptor to its content has a measurable fixed "roundtrip" latency cost. For large blobs, the fixed cost is usually inconsequential, as the majority of time will be spent actually fetching the content. For very small blobs, the fixed cost can be quite significant. Implementations MAY choose to embed small pieces of content directly within a descriptor to avoid roundtrips. Implementations MUST NOT populate the `data` field in situations where doing so would modify existing content identifiers. For example, a registry MUST NOT arbitrarily populate `data` fields within uploaded manifests, as that would modify the content identifier of those manifests. In contrast, a client MAY populate the `data` field before uploading a manifest, because the manifest would not yet have a content identifier in the registry. Implementations SHOULD consider portability when deciding whether to embed data, as some providers are known to refuse to accept or parse manifests that exceed a certain size. ## Examples The following example describes a [_Manifest_](manifest.md#image-manifest) with a content identifier of "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" and a size of 7682 bytes: ```json,title=Content%20Descriptor&mediatype=application/vnd.oci.descriptor.v1%2Bjson { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" } ``` In the following example, the descriptor indicates that the referenced manifest is retrievable from a particular URL: ```json,title=Content%20Descriptor&mediatype=application/vnd.oci.descriptor.v1%2Bjson { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", "urls": [ "https://example.com/example-manifest" ] } ``` In the following example, the descriptor indicates the type of artifact it is referencing: ```json,title=Content%20Descriptor&mediatype=application/vnd.oci.descriptor.v1%2Bjson { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 123, "digest": "sha256:87923725d74f4bfb94c9e86d64170f7521aad8221a5de834851470ca142da630", "artifactType": "application/vnd.example.sbom.v1" } ``` [rfc3986]: https://tools.ietf.org/html/rfc3986 [rfc4634-s4.1]: https://tools.ietf.org/html/rfc4634#section-4.1 [rfc4634-s4.2]: https://tools.ietf.org/html/rfc4634#section-4.2 [rfc4648-s4]: https://tools.ietf.org/html/rfc4648#section-4 [rfc6838]: https://tools.ietf.org/html/rfc6838 [rfc6838-s4.2]: https://tools.ietf.org/html/rfc6838#section-4.2 [rfc7230-s2.7]: https://tools.ietf.org/html/rfc7230#section-2.7 [sha256-vs-sha512]: https://groups.google.com/a/opencontainers.org/forum/#!topic/dev/hsMw7cAwrZE [iana]: https://www.iana.org/assignments/media-types/media-types.xhtml image-spec-1.1.0/go.mod000066400000000000000000000010031455506543700146410ustar00rootroot00000000000000module github.com/opencontainers/image-spec go 1.18 require ( github.com/opencontainers/go-digest v1.0.0 github.com/russross/blackfriday v1.6.0 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 github.com/xeipuuv/gojsonschema v1.2.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/stretchr/testify v1.7.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) image-spec-1.1.0/go.sum000066400000000000000000000043021455506543700146730ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= image-spec-1.1.0/identity/000077500000000000000000000000001455506543700153725ustar00rootroot00000000000000image-spec-1.1.0/identity/chainid.go000066400000000000000000000046141455506543700173250ustar00rootroot00000000000000// 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.1.0/identity/chainid_test.go000066400000000000000000000052771455506543700203720ustar00rootroot00000000000000// 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.1.0/identity/helpers.go000066400000000000000000000023561455506543700173710ustar00rootroot00000000000000// 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.1.0/image-index.md000066400000000000000000000174561455506543700162670ustar00rootroot00000000000000# OCI Image Index Specification The image index is a higher-level manifest which points to specific [image manifests](manifest.md), ideal for one or more platforms. While the use of an image index is OPTIONAL for image providers, image consumers SHOULD be prepared to process them. This section defines the `application/vnd.oci.image.index.v1+json` [media type](media-types.md). For the media type(s) that this document is compatible with, see the [matrix][matrix]. ## _Image Index_ Property Descriptions - **`schemaVersion`** *int* This REQUIRED property specifies the image manifest schema version. For this version of the specification, this MUST be `2` to ensure backward compatibility with older versions of Docker. The value of this field will not change. This field MAY be removed in a future version of the specification. - **`mediaType`** *string* This property SHOULD be used and [remain compatible][matrix] with earlier versions of this specification and with other similar external formats. When used, this field MUST contain the media type `application/vnd.oci.image.index.v1+json`. This field usage differs from the [descriptor](descriptor.md#properties) use of `mediaType`. - **`artifactType`** *string* This OPTIONAL property contains the type of an artifact when the manifest is used for an artifact. If defined, the value MUST comply with [RFC 6838][rfc6838], including the [naming requirements in its section 4.2][rfc6838-s4.2], and MAY be registered with [IANA][iana]. - **`manifests`** *array of objects* This REQUIRED property contains a list of [manifests](manifest.md) for specific platforms. While this property MUST be present, the size of the array MAY be zero. Each object in `manifests` includes a set of [descriptor properties](descriptor.md#properties) with the following additional properties and restrictions: - **`mediaType`** *string* This [descriptor property](descriptor.md#properties) has additional restrictions for `manifests`. Implementations MUST support at least the following media types: - [`application/vnd.oci.image.manifest.v1+json`](manifest.md) Also, implementations SHOULD support the following media types: - `application/vnd.oci.image.index.v1+json` (nested index) Image indexes concerned with portability SHOULD use one of the above media types. Future versions of the spec MAY use a different mediatype (i.e. a new versioned format). An encountered `mediaType` that is unknown to the implementation MUST NOT generate an error. - **`platform`** *object* This OPTIONAL property describes the minimum runtime requirements of the image. This property SHOULD be present if its target is platform-specific. - **`architecture`** *string* This REQUIRED property specifies the CPU architecture. Image indexes SHOULD use, and implementations SHOULD understand, values listed in the Go Language document for [`GOARCH`][go-environment2]. - **`os`** *string* This REQUIRED property specifies the operating system. Image indexes SHOULD use, and implementations SHOULD understand, values listed in the Go Language document for [`GOOS`][go-environment2]. - **`os.version`** *string* This OPTIONAL property specifies the version of the operating system targeted by the referenced blob. Implementations MAY refuse to use manifests where `os.version` is not known to work with the host OS version. Valid values are implementation-defined. e.g. `10.0.14393.1066` on `windows`. - **`os.features`** *array of strings* This OPTIONAL property specifies an array of strings, each specifying a mandatory OS feature. When `os` is `windows`, image indexes SHOULD use, and implementations SHOULD understand the following values: - `win32k`: image requires `win32k.sys` on the host (Note: `win32k.sys` is missing on Nano Server) When `os` is not `windows`, values are implementation-defined and SHOULD be submitted to this specification for standardization. - **`variant`** *string* This OPTIONAL property specifies the variant of the CPU. Image indexes SHOULD use, and implementations SHOULD understand, `variant` values listed in the [Platform Variants](#platform-variants) table. - **`features`** *array of strings* This property is RESERVED for future versions of the specification. If multiple manifests match a client or runtime's requirements, the first matching entry SHOULD be used. - **`subject`** *[descriptor](descriptor.md)* This OPTIONAL property specifies a [descriptor](descriptor.md) of another manifest. This value defines a weak association to a separate [Merkle Directed Acyclic Graph (DAG)][dag] structure, and is used by the [`referrers` API][referrers-api] to include this manifest in the list of responses for the subject digest. - **`annotations`** *string-string map* This OPTIONAL property contains arbitrary metadata for the image index. This OPTIONAL property MUST use the [annotation rules](annotations.md#rules). See [Pre-Defined Annotation Keys](annotations.md#pre-defined-annotation-keys). ## Platform Variants When the variant of the CPU is not listed in the table, values are implementation-defined and SHOULD be submitted to this specification for standardization. | ISA/ABI | `architecture` | `variant` | |-----------------|----------------|-------------| | ARM 32-bit, v6 | `arm` | `v6` | | ARM 32-bit, v7 | `arm` | `v7` | | ARM 32-bit, v8 | `arm` | `v8` | | ARM 64-bit, v8 | `arm64` | `v8` | ## Example Image Index *Example showing a simple image index pointing to image manifests for two platforms:* ```json,title=Image%20Index&mediatype=application/vnd.oci.image.index.v1%2Bjson { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7143, "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", "platform": { "architecture": "ppc64le", "os": "linux" } }, { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", "platform": { "architecture": "amd64", "os": "linux" } } ], "annotations": { "com.example.key1": "value1", "com.example.key2": "value2" } } ``` ## Example Image Index with multiple media types *Example showing an image index pointing to manifests with multiple media types:* ```json,title=Image%20Index&mediatype=application/vnd.oci.image.index.v1%2Bjson { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7143, "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", "platform": { "architecture": "ppc64le", "os": "linux" } }, { "mediaType": "application/vnd.oci.image.index.v1+json", "size": 7682, "digest": "sha256:601570aaff1b68a61eb9c85b8beca1644e698003e0cdb5bce960f193d265a8b7" } ], "annotations": { "com.example.key1": "value1", "com.example.key2": "value2" } } ``` [dag]: https://en.wikipedia.org/wiki/Merkle_tree [go-environment2]: https://golang.org/doc/install/source#environment [iana]: https://www.iana.org/assignments/media-types/media-types.xhtml [matrix]: media-types.md#compatibility-matrix [referrers-api]: https://github.com/opencontainers/distribution-spec/blob/main/spec.md#listing-referrers [rfc6838]: https://tools.ietf.org/html/rfc6838 [rfc6838-s4.2]: https://tools.ietf.org/html/rfc6838#section-4.2 image-spec-1.1.0/image-layout.md000066400000000000000000000204531455506543700164640ustar00rootroot00000000000000# OCI Image Layout Specification - The OCI Image Layout is the directory structure for OCI content-addressable blobs and [location-addressable](https://en.wikipedia.org/wiki/Content-addressable_storage#Content-addressed_vs._location-addressed) references (refs). - This layout MAY be used in a variety of different transport mechanisms: archive formats (e.g. tar, zip), shared filesystem environments (e.g. nfs), or networked file fetching (e.g. http, ftp, rsync). Given an image layout and a ref, a tool can create an [OCI Runtime Specification bundle](https://github.com/opencontainers/runtime-spec/blob/v1.0.0/bundle.md) by: - Following the ref to find a [manifest](manifest.md#image-manifest), possibly via an [image index](image-index.md) - [Applying the filesystem layers](layer.md#applying) in the specified order - Converting the [image configuration](config.md) into an [OCI Runtime Specification `config.json`](https://github.com/opencontainers/runtime-spec/blob/v1.0.0/config.md) ## Content The image layout is as follows: - `blobs` directory - Contains content-addressable blobs - A blob has no schema and SHOULD be considered opaque - Directory MUST exist and MAY be empty - See [blobs](#blobs) section - `oci-layout` file - It MUST exist - It MUST be a JSON object - It MUST contain an `imageLayoutVersion` field - See [oci-layout file](#oci-layout-file) section - It MAY include additional fields - `index.json` file - It MUST exist - It MUST be an [image index](image-index.md) JSON object. - See [index.json](#indexjson-file) section ## Example Layout This is an example image layout: ```shell $ cd example.com/app/ $ find . -type f ./index.json ./oci-layout ./blobs/sha256/3588d02542238316759cbf24502f4344ffcc8a60c803870022f335d1390c13b4 ./blobs/sha256/4b0bc1c4050b03c95ef2a8e36e25feac42fd31283e8c30b3ee5df6b043155d3c ./blobs/sha256/7968321274dc6b6171697c33df7815310468e694ac5be0ec03ff053bb135e768 ``` Blobs are named by their contents: ```shell $ shasum -a 256 ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 ``` ## Blobs - Object names in the `blobs` subdirectories are composed of a directory for each hash algorithm, the children of which will contain the actual content. - The content of `blobs//` 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 ```shell $ cat ./blobs/sha256/9b97579de92b1c195b85bb42a11011378ee549b02d7fe9c17bf2a6b35d5cb079 | jq { "schemaVersion": 2, "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7143, "digest": "sha256:afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51", "platform": { "architecture": "ppc64le", "os": "linux" } }, ... ``` ```shell $ cat ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 | jq { "schemaVersion": 2, "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 7023, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 32654, "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0" }, ... ``` ```shell $ cat ./blobs/sha256/5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270 | jq { "architecture": "amd64", "author": "Alyssa P. Hacker ", "config": { "Hostname": "8dfe43d80430", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": null, "Image": "sha256:6986ae504bbf843512d680cc959484452034965db15f75ee8bdd1b107f61500b", ... ``` ```shell $ cat ./blobs/sha256/9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0 [gzipped tar stream] ``` ## oci-layout file This JSON object serves as a marker for the base of an Open Container Image Layout and to provide the version of the image-layout in use. The `imageLayoutVersion` value will align with the OCI Image Specification version at the time changes to the layout are made, and will pin a given version until changes to the image layout are required. This section defines the `application/vnd.oci.layout.header.v1+json` [media type](media-types.md). ### oci-layout Example ```json,title=OCI%20Layout&mediatype=application/vnd.oci.layout.header.v1%2Bjson { "imageLayoutVersion": "1.0.0" } ``` ## index.json file This REQUIRED file is the entry point for references and descriptors of the image-layout. The [image index](image-index.md) is a multi-descriptor entry point. This index provides an established path (`/index.json`) to have an entry point for an image-layout and to discover auxiliary descriptors. - No semantic restriction is given for the "org.opencontainers.image.ref.name" annotation of descriptors. - In general the `mediaType` of each [descriptor][descriptors] object in the `manifests` field will be either `application/vnd.oci.image.index.v1+json` or `application/vnd.oci.image.manifest.v1+json`. - Future versions of the spec MAY use a different mediatype (i.e. a new versioned format). - An encountered `mediaType` that is unknown MUST NOT generate an error. **Implementor's Note:** A common use case of descriptors with a "org.opencontainers.image.ref.name" annotation is representing a "tag" for a container image. For example, an image may have a tag for different versions or builds of the software. In the wild you often see "tags" like "v1.0.0-vendor.0", "2.0.0-debug", etc. Those tags will often be represented in an image-layout repository with matching "org.opencontainers.image.ref.name" annotations like "v1.0.0-vendor.0", "2.0.0-debug", etc. ### Index Example ```json,title=Image%20Index&mediatype=application/vnd.oci.image.index.v1%2Bjson { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.index.v1+json", "size": 7143, "digest": "sha256:0228f90e926ba6b96e4f39cf294b2586d38fbb5a1e385c05cd1ee40ea54fe7fd", "annotations": { "org.opencontainers.image.ref.name": "stable-release" } }, { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7143, "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", "platform": { "architecture": "ppc64le", "os": "linux" }, "annotations": { "org.opencontainers.image.ref.name": "v1.0" } }, { "mediaType": "application/xml", "size": 7143, "digest": "sha256:b3d63d132d21c3ff4c35a061adf23cf43da8ae054247e32faa95494d904a007e", "annotations": { "org.freedesktop.specifications.metainfo.version": "1.0", "org.freedesktop.specifications.metainfo.type": "AppStream" } } ], "annotations": { "com.example.index.revision": "r124356" } } ``` This illustrates an index that provides two named references and an auxiliary mediatype for this image layout. The first named reference (`stable-release`) points to another index that might contain multiple references with distinct platforms and annotations. Note that the [`org.opencontainers.image.ref.name` annotation](annotations.md) SHOULD only be considered valid when on descriptors on `index.json`. The second named reference (`v1.0`) points to a manifest that is specific to the linux/ppc64le platform. [descriptors]: ./descriptor.md image-spec-1.1.0/img/000077500000000000000000000000001455506543700143155ustar00rootroot00000000000000image-spec-1.1.0/img/build-diagram.png000066400000000000000000001244761455506543700175420ustar00rootroot00000000000000PNG  IHDRՐgAMA a cHRMz&u0`:pQ<bKGDtIME%corNTϢwIDATxTS/6]++=;4xxG@/x!JxC:pG+OoO xCfm\93g37)ADDDDDDDDKs@DDDDDDDDK@DDDDDDDDK@DDDDDDDDK@DDDDDDDDK@DDDDDDDDK@DDDDDDDDK@DDDDDDDDK@DDDDDDDDK@DDDDDDDDKh ѣ?$: )˱|9/y$|7Q'O~?0&lsx96f'J0&o*WDB 0DDDDDQwFlI_%:gw# CØ/o/ " """"?Ht1-[T{_Ot(DDD-qlDDDǏCBcB/,a-JL~=n޼0bZsH7WeC7^`zgNZ_4  E4V A)M_RRʗ^mxD oPJt1-_ gEx<|xDdؤBT*VjT*hVRjR/DLs$)) Sk4D'AXMe %3B4n|pxK@TBBVCTBEt:tL-B/<ɚy0\ |i"lsXﮝmTDz'z6&bxo>@ҋ3J ի`"* )))f~M·~+'v;vJBjj*F#RSS~z"""g@DD'O|W1$'R'O~Çi㉘?UȚ5k}i  lݺU6 Ӄٌ͛7###-l~c?_|> `Aa(NqFV 6 H^~YSLCKK zzzp8"QZ7nDJJ y օyHv`4A(%l6v;z{{ tؼy3n݊DL͗{.bsE н/']$ɴgja+D ό(.OBOO?+œ'?c1C'jsUPP-:233CD JBVV>On1ۋףZؾ};FcC_-[_ /{. ZWڈe^~)?hz饗ޤˌM 3Vww7 I.9^۷O @DDh(**Bnn.kдjرCn!p8p%ttt`ddըNCqq1vg_`Gބˌm/hN5Okk+nwwhsa$a4Y~~{`(`I%!9Y(}]&7}V0DDDFWW״N&" #Q  AYY***`Xo>.環|DO$%^GKK*Y@o͐;b=f/0csrrDќllt9&MV1))Iʑ<\KBBx"55'N@__Ӄ&455aƍ8|0fsC]r.p1xJ)oXВR qO؜.ѻ@4!XRR ^j'\FX.O+ ,m=WK/I0)##x|s?,[6WyWhv0DDDq/%/ò ?y$ѻIF# 77ϟDZc֜ 0h"" ae{OBOD6фn7vAPVVǏCxJEDDD VhBG 󲽟'ѻMMMMEΝlNthDD ʿP%: @DD4@?%: Q((--ő#GRQBhqpE(Z|L""""bpitww# BX,9rdV/N2ӺkOt1%%%a͚虧R)R)z=?jjo[DK@DDDD<.\f  , > N^*Wz=ݑ#G[DDD,&hoo$: ZB<ىAyNCqq1 V&ScϿ/D|^@qq1FFF7bÆ P*9=;B )S*AAA222*ѬbcF 4,C" /񠡡 )))r2d2AP$@҂3<x<]TvON^^Ֆ T(~L&%{n7 ^oEz>믿NDS?~?nUi4OJLk* **oA"=MHJҌ%%ux<B^`pFەADjx俉h0DDD'J82RJf<ǃ`0#O[ۊIVCBV_(,""""J&fBQ-sTc٥j|kߢ^YL]Ո(@DDD RH4ͦtvNѳ """"""ZTjjjn:X,ܸq#- FDDDDDDJ}}=L&>3h4DC(-Ҩt(NLѢkDL"@DDDDDDDDK@DDDDDDTD@0DDDDDDD_ hQa.`0`Æ hQ0DDDDDD(B֭[^M[Ѣعs'BP!ZT2%"""""EaǎP( fsC"Z45kc1h"@DDDDDDD@0DDDDDDDD1DDDDDDDFItD@DDDDDDh( T*l6DCh0DDDDDDDJii)l6^z%&:EѢr_| zDC(0DDDDDDDllNtD-qL-qL-qLт v  ђ-(]]]xW ݞp&hA)..VEss3z}!""Z8 <-n>ؽ{w!""Z2J&8&hx<hIbj ђ-^jxwђ-_BP$:""% """"Z022DBDD0DDDDD Q^^bD4+*++QYY5ҥK페vQYYK.%:tY-6l@(W_}Ph ZZQӻaZc&V+:ѬDR~C!% Vrrr "DQ."V+FcC'uLт!%fÑ#GbNFvvvtQ'| b.`DDDDDDDDK@DDDD`(JPC!""ZR""""CVl6֭LtHDDDK@DDDD|(++ZNt(DDDK@тVqDADD@DDDDDDh~~ۨJJJthp8oP(~zi x Z-nLA\|@>D4(|>n7`C3~nP(ѡvI$'' ]]]HNNɓ'jjjvZX,86ǃd@ zjb$hZ\z`|ڵkF/b]0૯ ;(|v~툤TUTBV-?{~^u<CCCꫯ z뭈]Hڶt|^*]~}ą^/, <_x JlٲľX虠aXT*aX[o<:j ؿ?Ѯ`)))hjjBuu5Ks@P###PzhjjBii)N8k׮h2j"77YYYX,Zr2ҥKtp:rbsAAAٳhii@ {Fj [DB4/mN ߽{7F1|֭p8** ,B9V(tR~f<_Q(ᶶ +ʈe \.W@bpzǞWUUE l0 ~)l2֑~amHnkkZJ wvv믮p)\TT4RzeLy>[r¹GpÇa!r+ʰN \.aA©4!777O?p~ ~A^v/"~ TN ߿?Ï?D}>LV5"Ž^Z<}4-:ݓ[ܾ};+GYYrpuT*mmmp\D0G}$? C#8x!ۋO>DކdVBmm-|>`6߽{j:'Nك3gȱ8F +MMMP(8sܕKT˃ٳg޾};088'NDtE(kģ~rApĉi 񞝝#Gbg@DDDDX,۷("55֭[!"n $_AAaǎP*P(ذa|8~4ٳ(h4u(EQhjEPVz|a4555D`xx* rÇjDQB EjCCC̔ׯԄ 94B ]R&# b7J4 md2>h߯ׯz|ff&?ֵF4:nڱv%„?gu@DDDD䌿xZ;A@0( |n7 ǵܵ !'''*$;Ǿ}`2`ٰvZ!++ GtJBff&6l ̦a!33 J8;eu):س_#LHX5fk>XV`ݳnZ<9}o|> :t_~孻{ʕ+ظq"J"A|x<(//mJWr8gkѬzo тQ]]~׿FRR^y4774ӕ^"$%%%i1DDDDDDJP```'ODSjؾ}mORh`fNç~0蟱@DDDDDDLpP(h4p!fP(Áooh4w^PZPGu'EQ} """".4  EÙU~~$''l6c``E.+bFTUUFLthhh@FFƜǙ DqOhiis,.`v`0<sj΅bXv;l6v{t/S*qhmmťK_DDDXHKK!99y8x' W^Arr2vܙ4+IrS(Qmۆ6ܽ{wACC~?6oތ˗/':̧200DA @QVIkk+jjjpy>k׮ٳg4bgϞ8DMM͔Bcc##{^hmm+]vEM;x BЌ)N紎Mt:_jFqvxgF7|>ߌoܹۋL`߾}>Lr`hhɉhi@H-J%T*]WƊ+"ޜĪUrʘ-k#`ΝXjV^Ұb ϑBXVʂ{^lٲ+V:iiiz*֮]d\paFFFeZjZMi#""i셒^hrBCkkkTW~\|pFFF"nAB!y[o v"χ c prvXF Xict1t,4D4׊yfoƍV'|'pM9qtYwEQQ܌tbڵZlڵk#@]]]Xr%122zZ**S\\,mX';{,,|M49:HIIBcc#8+WDe5^oD3~ܺu _} IuVlݺQ_NvZݻw j۱;v uT*U̻7㉢T*v{ԩS0ݤۋ۷oC:N|6󑛛/bhh֭Ý;w "<p c׮]ؾ}q{Fvv6P]]W@'DDD8,IMM8JNNFqq1144R@ Ʉ/BLJTWWGܹs!Nrr2V+V}IXVL٪'Q$~=Gh/V@RahhWbAQQ>S( \|7oӧ駟bבZ`;wDgggD]ExɁ(,NS@'N ʊ 1'N@RaÆ 32Dmm- cGLE^`#@Hwbnwv:;;P(ڊ7P(qZ,yǎBBA?zzzvja۱gw6n܈7n=uj5F#***&ahh@@N q~9qDDDDϮZ>|pa=z4b~SSDQăCpL긵Vyyyx0޽7Mnr`2jr~\.&Q|*++QWW<ܿ?Fgg'^/ӣZ^WUUMMMSuuu0͸{.޽c۶mסjQVV&'z?#G:EQlh3AP[[+_;Z&]@VV-PWW'_oJ]Цs#;;;Ga4 >NGRК`p}<ʘΜ92 99YnjqhZ|HOOlƑ#Gpȑ9=UUUE4ᝎP(u B4 ;9sU@ ~s7Tw|>ߌCuuu3w"""z6j?^Nn "D ӖͥT*#H1NtJ5HDS"З.][(J[T^ J5/V VQ'hcQQQBkA@vv6Ο?\zUhp!ܺuK{:SFQNH?3[p8x J%ꐛ@EhZ( Η`ԇZ ^/~?`\I4##͐hRQw333 |mÇ֭[M-ߏAtww(7n  "///"4;;J'*=~_=Á#-:oL|1kLV7h"5fDz5ͅ9dvXv܊?0eV]]aB (شiZ-|>Ο?CKݟaXPXXt---ݜ#?? B(((~8iܵʕ+R`2pĉIcX,X,jP'*t:B~?.^pR3駟b۶mhii 5߹s`0(7%ġCڊ9Ybp FTWWcݺutx<8<AZFvv6AbnSd1`P((**BOO\l!>KJLVX:wq:re&bnI] Ž(\'dZ[[aX3g鶈hZF?BЄvG>?8ͅ9O+W݆$:. >/*9000ߏ@ h4b`` v:4055߇RkNí['rA_݃ }vlذ@**Ν;G޽{r"e챝(o߾}(,,瓛#I>|(O'Z|P*P*S&$J2DDDlu0xZ0L(++`[o%.5D-Cм_?]oRSSߏ7ذaCC"APTTjȵ h>477ڵk p:ІfݼtS1hH"cyn_jh4B5 B"L?tf իq9}Ν;v k׮%=s,vEzׯ_Ç QnF1H]ObDxXXb:;;`je4]ߟ}gJr1"466" a߾}΢" //ocW(r(JX,ttt$"( 6mJ6x<tvvN8lnn.:;;m6dff󡭭MNVaٰvZM̙ؕ3HOOǺuPPPZ-'fLI޷Ly^%߿໭"|2l6E[nj8Q W3jZ#߰aܹgd2aϞ=vZ܉T)w\T9gAnn.L&`0pyL&y^14T{oo/4 o>ڵ+x,$Ef9";wرcE((QIF\Q(0Q* f9s@!$l<%YBK4svvNDұuT%(F*N2'X"""@{{;~?JJJ&\FNwxFPFL3UVVӃP(ٌ={LY`0fK\ӉK.|\vvvȜh"ڵkNCfffTǎC(¡CZOB!@FFFׇ!yшi#={`Ϟ=?؈Rĉ'pĉe?쳈u:< d/DQr dddDMh41hxeg:͟Xѳ "" 444@NXM#W===inV5ejEAAQ 9Eb(BCCRRRQhHDQJvdffFd988&ǣ!իWQQQ߇fG @uu5n޼9hnI-[^\-Pp8Ph(`DDDnDIIɤmV+RSSq}\.NYYYrfpp8X:::p8pM\.ܻw͓r%x< [.ʱH?---OEÇp\r,:NQRTT1Q--r?ȿQYY9.͕CR!778tz,pdsFqI4ǃD=!qNcFYYɓ'QYYuA$  BQ^ ՅD=N<Weٌ/]QpP(_ȭx4 rssZZ۷ʱEYYY]Z`o6lkn kR@nkXlbZ0ZLUTLgϒI8p}}}1uuuq»[l;{c;6arj޽ضmۜ=O<1IJsΈ"POƖ-[F&*++(zHKKtCTF](W(Xp 鑇;whI|źIII0iLl6&h~MX P("y{AOf &zGa9`4q)|嗉x&򵵵p9ŋr4Q_cǎN:5k-Kf̙3 ?9I RqԱ?P*#Чi4ܼy3ilݺ5*Ip$ 8wVZH&p8p(BO5MDF0iA|ػw/ݽ{7V+!!c}|Q%"""Z,d 4\p+V˗/ʕ//wA+V@rr2;6rJZJnpAVrr2qA'iiiXr%VX!oɓHNNAZZ&oK?VVZ زe˔q[v]رcriiiXb"b裏ᅬUVaʕq=ػw/VXgbŊ(kݺup:F~MC~~>N>k׿5N>1:jE͆͛7l6#%%E.빐^Ҵgϻ]Az\~~~Dǃ|TVVW_źuV;khXrekeIQrr2{=HWW8>*yHKKիײO7m-Rr|'ӎoPMMM0L3NJ _{y 7oDii)ܹ#VEff&N'N'n;~T>X,T*v\z숈v";/7ǽ^/n7 |>\c? #J{<ץbA&<p-%`A/ىΝ;ZS@L&\.,Kz$ nݺW)p]ܹsGn]R[[+J'F/nϟ?w먫rjqu\.\~]ޖ¹s^|V/^9s2ۇ;w޽{P(k C߿Z-'HQ[[۷o#330Lzq]|QΖ# Ft#/]]]z>A5\kmmE x|^Gmm- DQ([[[iXg}uu5222us ""Zh]]]$E~~|Vj0&\c_ٟq㟞΂L'RaꚔAEh4F7;::pQT*̚5kۋJ rAP}}}᩺u ::޽{7:;; q59hZ@؎; 0<<<:;;&B(,,B/D(z*Z[[N TUUAVd2E$^||۷j*|qmGVO?Z,)jcjJ/3J5]DQą ގP(q\M&ߑ#G~z!ol|* {իWc}h4P*q})HƏD4h0ׅ `QPP gz6luB`Dr}ljtwwZVnY9~">~?v5ǎhEta7Rkl»ヒ|Sj=IR׉Xi6(ѤsF#wib`` |(_N>vS]ݮV q5ʠ-"W^_^^6-fk ׋!;3ޮhD{{| J(z=z;vu駟AxS{gRRo)dBGG !Bq[,477tʅu:<Z- äE0O}gf/ qb(p:Q& **RTvi#++ *j2_}Z->P(Wn޽{Q* {={j*8Έ%=AKAR!##ҼNBB! nnCѠdKRM9lمL6DJrGuu5F#ow-@۽{weԄA"77Gt$L&}*֮m캥ω~IPSCE?SZ5ix M[n8z*z{{;vPTʊ˗}v+.\ob֭h4ʣKS]g4 Q@=fիr/lݺ5ܡo~?!"V ^A`0kR`?u7mV '[IIIA(ڵk b4!N IrLOOh4ĉCuuܽfĉ0 :T*L)|p8P(rM˱zjzL&?~7oFqq<겲2X, /N mb~!!"Bn޼9޾};JKK#aZ-n:t:x<? 1x^xވV+"OCVcƍXzuT=b(t|rЯ֭0z2<<RCCE;wnԠWSRa```ǘf @:Νw~mx<[NT4 Z-V\ B`0:u ػw/j5~?Zmh_.\@nnl (uIŨb9tPȃS󢰰0sndd===())qtN|ijF=Ys</:vDeET'7ߌXƍxא3144PoV˚5kĺx<Bpz뭷Nuq xR N-oC`^`0*@ FB!K_CR1 btDfX,t.]jT* _mv܌*ܾ}koJ|>rrr۷cddzrt7okp_^n ol7ׯ_Rxkw܉y=ZZZPUU7oʟ鑯۷oߎQGQQ[φ$bG{qr?~5ÇΛ\.W.)) L8Zmlme>|taJ% ۷b-г>H0puuup8nnnXNzy 7o}r} IWWVTVVBEu&?`۶mCuuϙL&뛓M4և~p8܍qBѣGgA~9\|y}-vFɦW"YVhZ|ކ pE[5558s,;0>`Fvnܸ5l B裏$߼h4r=zڳbv#"Z*_;w/F%hk2x^XV[ +wX(fZ0~2ǏJB @yylh8pׯ~#FF h/,شiv;pmB]]rssA [-5`mmmhkk((**Baa!-rcw Z=zW^E?zzzԄ^ܽ{wڣ,&UUUfYc}71X,())ӧvaZyQ- NN{$Nh4_hg [3ۍ $''wEWW4ig5/ bxxX Å P('N޽{˃ 0Q'XN00>DQ(JxވZeee1߾}; znl)q߄E;s(F5<<,HLE|vFq;y2>l6Fn( yhi׋I U|>ף)))),MDxNBZZ6mڄhZvl6c߾}FGڴi\ZTbhh(((D~~>n7pw۷oGSSrrr`X :::~GBCC|.q)l6Ν;S&~͆={7@WW4 :TfnBb^9zޕ+W*'srrpEk(? kE+W .`xxX.Zhb#'SL( ɓ"33S2cǎ!++kAIvq:thηue֭[q'X.\?? n.kZqTVVbhh{쑧oٲ/^Dkk+4 ?`f}}}TŪ2Bp8p8PZZ\޽{#tujjjߏ`0Fz۷O3ɓ' CYl4 f|APDuN4755BOOJ%***oPTr6j hjjBkk#L&ɟg]]]ݑ{nNc:f99܎;bcwAݻ#Fn(0 Q#Ε|+ǯ,D4j$}w^@NNw-&ۍyIeeej]#nӊahhmmm}vVZRo=iHܹs'}J}l hkkxN@Ts鐆VTr+J%Ο?tlذS/^4]zUUU(//9DBՅ>Uƅf͕iv ٳF/kkkҢj&55ZM?Ȉh4 2%m߰a6l5}:&ZcdԩS}} L͂@ At:P(###=;FAE?czdL ݎ+WĊ+PSS#KKKv94n#99t;.*++ꫯbr1ݎRWiӃbŊعsgD.ݻxW{! 1uvvLo*'Oɓ'i&"Ԡ,_NNyY%%%qVn:Z ۶mujjjrJ$''˯P(l۱n:\iiirw5ǃ4={+WĪU*2iِUV!b^ii)z= .z=rrrm۶a۶mOu<)Rjj*ZZZpM&fN3!.GWuu5FFFd+r1p,ͮNWjBݻhjjj/nx*5l&iN>^z 6-v\:~{I<˖L|I}vv6&o.v===}6޽SNVrFkTUUa``wN>P($ސ+yyyvڤqsn޼*㑓 `PN\~ϟd嚲;Tlr!//O! B$5Ml477#77. >/_cE]]T~?p ܽ{mmmUlF tΝ;E_wŭ[p] G$CȯM6`j@?wbȉF۷O>dmy^ܹN;wPTT1PT01k5䠧'jZl8rLFAyy9޽ׯg`dd===rЀ&KJJ歙v"X,(LDsСC3BhYYYhjjBwww\mk;hhh@wwwԐsi)ͥ%ڿ?\Á+VZf(8pz3\|(ҥKhooVj0Lh4رcAuIww7/7˛Z'N ʊh4]>øJb֭z>t`0@TBR / A NE*v k׮ARڵkhmm OŪEEEP*P(())y,|>\0xzܭnr]ZO? (((t/_Fjj}jQuJZ[[~8N={###0 ؿS58ydyӉk׮ahhn& ;v?yjhooGOOA@nn(\ODDD4DF8@qq1*++h4|ؿ?^yx<yFX^ףWn#ٳgӃ˱F\|zPr-A.Ak(J qۍ1)7nC-DfB@Tbxx[DYYYYU)%%r###ضmΜ9WIVW\Fww7fBnCP@3 0z2._ub޽aQ4M$R<޽;f;zzR"c`v[lV A}}=~yٯV n(񠭭 rKbI@gϞE(a* *JCGGGDC]jAvʒoii J%P(T*ۋ6ܺukI ?{իW9.)))عs'!"^/:::"'"""gђꫯbڵrQۊ ` (**Bwww zjcժUɉ"EEEHOOǦM[n^Grr2ӱn:fTl6zzz"J֢I.J,Fk#{XvmTh4BaժUHKKH}w*vvZ[qGzz:^/󑟟׾ٳHKKO ==].w\0 (**ªU4Hkk+墙1t>Cn^W狸hS(˗Oj}vBLӡ k׮g}6jjj8pWƦM7сPMMMO<-URA̙3L̡;k0ĶmۋrݻE1ZVjܻw. 7oބB@YYJܹsr')r"jBM6F(BCCRRR&L֭[駟rp}yP-))NCQVV~<@yy9^/;6DŨXSՈkjjƒvOؚbp\}6 ń'"llI?KULL4Xj @ ( z|Gzvg<%Hx q\a~qԼp^^^~!r?Su>z(r=byq~0X\.WXф].׬ Lܿ?`wttv~ _31uݼy3l0p86LRR^^{nØwV5 lZuׯ_Ŝ.(( CYQQQ@?j]f9 `Z[aᶶid \bOMMzfffo߾cѣR k4Yxrfs܏w^@8333bZ68555 `Z3|,E1,B={Hxڟ|N>')ΖYOŰBy͝xCK ;WuVF-LqH5A&?sA kDQDuu5mۆׯ?uR9bup:G^^ܪiI]>ft׋]v>zlb1?~ A&5際#Znǽ=~DwNQ^^ɄZAZZڼ@DMVk eeeq-P`0x ߒP(˗/fCP %%;v쐏a @WWy|># %%7dsi@`ǎr,z;#FtdغukDk`io>ehozx<6%""g4d447=v`0DT,wމ7F~---ətW" 0vFɜ)i3gLy,Q#΍588f&\wonho)րS[f , DQZb~<`Z0P__[nAPb 33HMM (ׯ_NFCss󤣊bEN566JF@mmm`~FwrssAL6o"- .]v}۷UUUȐO<q >88NLPz{t]gϞE0DqqIvv Bhll9rD.\~5G}4i|()) 6X2޽{gm݋FR*S.KDDSh4rKRQرcp: B!hiiA___D^tvvul66mڄ{/BNN>sߏM6ׯ; xPZZT\rEp8n:TUU `FQff&.^:iÆ R@DD$[@d2 uuuXj6o BnnZ__!t: j}Y޽MMM()) PT8sLIJ 0 SH_  򺺻v㕕:Oc.l6CPft"77wD>mm"INNb^jhFTTTD 2! Un,fyyyhkkoSNɭkU*qFq QVVּҍ,ZDZELѢp8`QQQWZ 455[]]݄ $J !?tЄш/555rDF,wen455M(r >cAPl6?_]j|T*o߾ ( y4NI[|ᇰl8rȬ<{쑻UWWCӡmIp""J;vm:<<CCC|fffԴT72QmRtȴn}p8vc ҳ @6 }x"GDDSjllxcX0SohhJOYף |Zzit3P+&>f!322h_DD8x^|lt O'rhmmEEE<ԘYKtT*x<]6iсEw`ddX,1OdxV-&۶mCgg'q-<|ݛVEwlB&reX,hZtwwrÇrʴCۂld4a4ӃIGs!"gZƣGƌt:D1/jjj``2j1886[?-D^ؼy3=1﫯ZnG :a ~myD0}5]%yԩ`0{F,@RU,N!TUUݰX,DD 9BA022bghhuuuF[,&ՅNDh8p^+>Jee%*++';yZ(|@ -= DPw] "gtk-$Ӎ}```bپ};oߏ@ 4sl6 &)bhz1<< QY߂f48D]]VX ݎ"444D%t:a2 p8`4qԩuwww(p"EV&Wo>@HbppjfYawI-СCsN,HDDDJbyh]MHOO(p\8͆B}}}X,Z6b0:hJJ l6 f3>S t~z\p`(,,TR͛hllAjj*vĖ@sΞ= B!~p8^ž8KԂN۷oOt(DDDD昉.dFlf"RksxF F?/(1c2"ZR.֖4r(,,d3q""""2Q'NĜDMD4gB ^ ,Ltt:vZVņ - : Y'F8q`pZWhjjB@VV֒oA&N'"TDDDDDDDBzf!"ٓݤD(u3DDp{gP(1===='???}'Xz5._<28pSǾsNܹ3b덊=m}Xnv{Һkjj:v""""= 2d6{gbR""Zn7 bЬcl "㕗tn9Χnmִn󡪪 v|Ie l6*v""""] B؈P(}E;QRRR,**BKK O_Ѱ -YLM  7n՚tN©S4v׋Nj/bNgZRLt(DbFbQRRrhmmf4=c/\^~f۷ohڊ`0?uN.]~)OC.\Al޼95H@,gTd( ׳)͋ ՉhN1DDD4hZdee=n7Vk42i( a۶mFA @ww7l6.^8ԱQ[I@@6mfVCww7v;Μ9|Vx<n;w444;v`K "s}C!ss^ҥKp`(nvN%%%SlF8F83A$qR1z|P*HNNfr[()IDATh[qӉD/фN'QQQ1e徾>TWWGLzHOO0ڍ򼚚y.+Too|ff&:Ah4">Q]022)3VNs8)#%*! ׋Atvvʵ$}6mڄ2477d2! t"%%ΝgK.ahh ;#bDDDi (OgrJ5mmm!6n܈?F1j(ʘ-f3vF#hh~ss3zzz0< 2v<ѡ34!ͪ9Oؼy3>D+ф>CE꠶p8f*""" ~Ғ2DDDDHwKtD |1GܻwoԠ D т144PՉ]]]X,1]reQA&hAXnz= C!g@FFǎ-5LтRPRR?0ѡ3B@$: y-W\ItDDDK֜FDDDDDDDD-96 l4ۍJ\tIQYY'O&:d9-9׮]jŵkinV4ՊDL4D@DDDDDD4֯_ՊDQjhT*XVTDL4""""""%l6l6GLEGR-EFDDDDDDD1DDDDDDDD1DDDDDDDD1DDDDDDDD1DDDDDDDD1DDDDDDDD1DDDDDDDBkk+._P(P!b6n܈/2-*lDDDDDDDBYYt:Ѣ- ("++ wwhQaϗ&8&hh4hQb QP(Xh""""""ECPMMMhllOtHD@DDDDDD?~f%%%l D'&hQD?a4Ѣ $:""""""xATWWJt8D[Ѣz P&hp݉hQbhcB&hQBhQ(`DDDDDD(,_`6Yh""""""E("+++ѡ-:LѢ&:E58&8&8&8&8&8&8&8&8&8&8&P(ײB%"""""hӹ#gC(k`088@ Eq2DDDDDD<ͅ-M}.uIQ- |._DQիWz @TNn&\^BJJ ֬Yٳ'3BL-&|^0υ^zIoc?m}HF5ii6]pKHkTJfjXH%@ hD "4TPjLdOXU] WZk_HKB^_{sy|ι_}z-BI@pdc ;n0@p c {$Im۶Mǎva;FM֎a}+Hw ;٣[?Aϟt0ҝl#&ek@vb ~Es6nB6wd'NH^oֵf@./IznSFMj'}[zT@pc FN}''n#c ngt@pޱy:Mt ; Kl6[` d{@4l#SrI`LAIF?ޱY2Mt HB:44S.L*HNбY&60%UXXaٳ'֭[d3]wX.tddf L ŢVIRCChLP(fI҇~бY7;M4m`,X@UUUFկ~H$"wT$H$j*fH@.tdNtAR^~eB H$_~Y|TtdL͆60l69"ϧa'nKOOJJJȑ#Z.)+;siF\*U8VUU-[X,ǵrJUUU)NGftL\ȜMͦ60-l68}nCEEEڵkd\W2Ԟ={O]6M޽{ <+;qiFՒ%KtisŊ+TTT={!+>|XEEEW(י3gdɒL ˥̘hh6@iv]}}}*//W0T}}uVK$jkkSII zu1vgA.udxD dק~C* AfҲe488".3<}ZtL\ȌD d… ui|>"vءAE^W555Zh<OFuauvvرcvͦzw3rc`&Ų dө7jƍN555vB7ov{,L&588GWCCC>ժjW_jtqd\~Qn;' @VK u uvv[PHjoobQyy*** /h\2ٳgu) WXoTQQ-\ 1rc`ډ.}YFrbѼyExϞ=jppP0-..Wyy^z%\L_P0A *(HxTUUJKp]ֱ0RD%LAI*..ֻᆱx<'Nرc2Q$%+׫R=S*,,eb1:uJCCCࠢnꫯv7-;v   ϕ6l6,X >s3 8{mcP?<O}b pႂqz^x'бC.Ŏ].K~ \j#1 t!jDB@@?Ph nPx<*,,c=&-ÑK+h y   #}?/=sz㓥ڴjժL%ԱummmD".0)VU˗/ϙ@V,3G Ba _cZv\.=#r:r:e˖i׮]. ql޼Y˖-@DQ-[L˖-%͛7STZZ*߯REQB!bL+Y PGG$i.Qz{{//ϗd?xl+wޝ0 E=Lvkܹ.]JNg?n$KӶ/\P .tZ@~aո?@vI&?OIRaaΝ+:DB'NP0ENV>sqy<1D"J$J$CVNy>.>u)b1k޼yJE.+ӷ;b%tN/PHhTn[^FFx2} ÊF*,,fK naE"v9q;FE"E"\ҭ[1w7u#'M>Zz"Hvөi߯1vm߾]K,I۾n:utt[:tP~˥A/^~smm$<_ss}X,7|Sixc(V]]]:t萪DT]]@ `֦:E"թS]]]S,S]]v1{^ZZb"˥p8_S__f3Mw}}בL&5|uwwri˖-:t萚USS[? 0 IF}}}FCaX*cڵƊ+BC!عsyM I}N{v~ɴ\tmH2Ҷ758sa|!`;wn/X}~%4hjժ1_VXaH2<qҥ;;; bXִʛ~իWO?4ӷ $+VH s}iuܹsfidG(z<ʕ+ie4cwe#4g444LXﻺ 27ډW.]dvT[ZZW\1ؾ>󾍾TXv/;vy=O6wf~F\ucΜ9cTVVMMMa|攗OxTza|ؤ^3=0@sLD=L \pAXeŢ:IJrTQQ!0$IK.5_z,oVDBvSFͦ{pfyp7l_"4GiӦx̧f6qiz7NӜ0rخ]UYY~;5~_---cǵ}vI>/ZHuuuJ&N ժ>(mM$eaEgkkb1y^cay=ri͛UVŢ{n_;vЎ;-á{?$bnqq97J I;sjF+,,[L&1­# ,X@}v=f_"wIZb$NJOOѨ~A:qℒɤ '"WYY)INDn1^=B c^3^ٸqΜ93fUŐGKˉx7l;~Dmuu$رc}JKK]9Iy]5&s=N2 z:hUUUi+riΝ&566J~d!BCЉqI/̙:L&%i‡K5ƈ"pXGٳg  5<]~2=k2۹S,# L4ˉcI r:f8|S{jU"P0TqqcM[ySmmmjnntmjɓ'u]|YߴhѢ _Zg߾}J&ڿɤjjjHuu0uV\^sZҙ3g7̙3裏Ɲ_}ոL$c:wCҵkVS̑F_hvk˖-ZH{=+R#~SS)GF*))̙373oiSbk=ztc<,ZçA7nK[G/𵥥*..V4Չ'şӎK}gϞqpgs[*..N=s1K3QԩSc=S&_{=֮]EoSb|MIRKKZ[[%IVw,X ժ1[Jnג%KdXt19f2dSiՒmۧ<, 1ZK' {:::488(5fԹsn+O5k֨^۶m-)ZEWG .Ğ1fd29S|>N!wQ}}6oN;~ :H.\Zb1iʕڰa/^_~'A@d/ip8__.]*bN=G6u"5ͬQ7o6;V{L o9Ns͛/usdXT[[x<9s樭M:x̙3 bCo[bL{|R,/~ sO<G۲e$iӦMhbZf\%ժݻw#{{{' Ɓloլeee*++euСmܸQ---cO---kٻw$IjnnVggjjj̠LfCQ__on;t: Ib1nr In7::: fH2Ν;7y I$ccy$}$6ay| IFGGF Un|ivÐdx^Րdիiu}9l2a9 2nZcdOGGuOjO~,00]6nX;w^xѰ$c˖-cε}v>9s|uecΝFcchtttW\MMMFccsN{9/^O?5ƔcS^j+x***T]]|PVU~^~裏>gs^Ν6Ez{←=쳪a{eX3ϨI۶mӳ>_<^ԣ>*ۭGÇ_z2}s=a.}+WT{{۵|L_35̙B?>fm߾]:p@ھp8EQuuu]1 @$QAAV.]4/ur׶m۴vZUTTO>T]]ݛM9{JJJL&T]]-ݮ@ })MarQhT[nbQgggh@(RQQVZ%Kr… jooWggl6>3k)Zf͘[V^Z7nd;v=|:r imageIndex [label="1..*"] imageIndex -> manifest [label="1..*"] manifest -> config [label="1..1"] manifest -> layer [label="1..*"] manifest -> manifest [label="0..1"]; } image-spec-1.1.0/img/media-types.png000066400000000000000000001317301455506543700172510ustar00rootroot00000000000000PNG  IHDR8{.ibKGD IDATxyXU?I 2H] J@搊Z~Mg^IC^!,Z8\̮XVf(8\AA!FdaQ8z|<guρaJDDDDDDDH """""2Lp`;"CwI; "f\a)T\dy7YXZZرcpwww(DD5q nQ^^Qxpi}DD}"z`0!""bnn$Lp1!""" &9D }pY%rҤIqypu5"jQ\&qh2tضmoߎ'NSNZHll,}z(NQ#"… R4ݺuwXM̚5 k֬AffA!"u>|}}"=iaժU@=GťKUz,UxDDD  pww+TcEXDDDLD|2-Z'''`ݺupuu-6l +ڶm h>55%<==Qg߾}ӧڶm}"..8SSS<ꫯ=DXd lmmp'N,,,O#99Y똞yq111X|?otDDD !f+tdȐ!@n޼)""ÇVZ YpJ^^9R$88X/Ųl2 J{2tP̔K._!*Jm&EEEuV H>oooIJJl3fI|||%@fϞ-gΜ|Z-""+fffzj˓0177""RoL999(dR]]-boo/aaaR^^#FDLpQD,""B%&& ټyR3ƍ&M;;;رcA#5jTTT\xQ~U J2o< ׮]S֮]+$--MDDdĈm( 1Jh7oDDD/=|!"O5ןnZ)kӦ X)^QQ~z|W033S[XX;t@ܹsBϞ=M1}Y={&Mx1mݺ 88GntDDDw58DD~.OOO???' ϟ7oԩS8rFpfrΝX.^1YXX`ɒ%DZc4MDDTLpްF||<5Μ@=0|;w:t/N7܍dk"YGۘ_`۶mUt(=8E>L9Rʪ,p3)))ؼyT;d!22ԨSFEEƏKKK={}֬YӨw~`ddݻwc…*"L6 [n N)_ܹS,--eܹRUU%ҦM{ J|[n]W=j}ү_?Kee,ZHM62aYrƍ%??ޘƌ)"VPR ?"$jNƍrHa Caa!N 99'OƩSkDӧ+հ@^䆈.HDtrss1c $$$8{,z-L8Q!=p=^|EDFFʕ+񁝝ܰ{nC$jP~~>pNQ#"z9#GwDMvtٳ'x }"&8DDDlŋ_q!=z@߾}^x:uwDcCDDDͦUV߿?QSN)s碸ݻwǰa0rH 6 &&BDkp Dnn.\\\r;v쀷7.]ڢ} QQQC/ɓ=z40{l:uJ/1z雈 CjÆ (Toܹsq1jT4hV\x\z/ѣGѯ_?xzz"""_~6m1sLĔ˗/-"j9LpADqY*..ƺu4ڵkt,\P}5W_?Q6gi1uѱcG̙3 8vK/ۻJpp06oތhTVV6e˖W^Ϣo߾Xx1{?C"H YYYн{w[=z4 vujƌHNNiVy`` ,,,0x` :ᨨK&OOO߿qqq(--3}2̙39s&\]]ѣG8|0_vJBvv6v܉"88Xc@888 33K,-4=HMM/`ii OOO$$$(e˖!99sot .K?8qL^{M0665 nݺJTUU##~e"zh5+___wYYYHnݤO>uV)**""mJDDܼySBCCR<<5&8DDwGPP.]~ԩ̙ .4իW%%%NNN@FF9u};񁟟VKBWUUo *;v QQQEVT\''fk?!!/bOD̓ QT*0;w2_|Uj t<-yc=u>EEE5xѹsgT|ڵkKMMժ7|_n戈aH Xp![ooŁU駟Fǎ_HB)eeeeS.^7n(ӴjV-32VsmIԪ:J[GEJJ L1m?СC8x ƍW%ÕYfW^ ѣ4iã"$"FPTx^zQuMMMf1|p;Vy7Dnn.RRR }ń ^}m̧~t:tpIj߭[p1@tt4D:t]v8~8JJJp ::/ իѭ[icرݺuÿ/_nEEV\CbذaذaF;(--EFFVLV#..2"z1!"jAƍ޽{qUtSO=oV^2pssC.]`ʙcbx`ii~ G8q>V >?W_ڵk' ǎC`` xyyAAA055ŧ~҉'?'=1x`L< N:Q;wD޽b  C֭: ,3sss3w~j+#JJlܸq0=GB7x_|ƴ2Q]zj1%'O'|saǎسg˗SN-6"z;""TUUq= !izMee%8777;w]v7L4 ;wCDaCD)((@||Jw4 6 6m€ F58DD|j$$${9l۶MQݟ*,_z‘#GԾ 1i$xzz2!F"ZD4$$$믿nZjSN˫"#"C38DDDs accݻTfddSSS{VR#"=pww*jׯ_Grr2RRRd\t /_FVVrj ǎCff&JKK~׿ꫯ\!CgpQ| Ν\ 8Pc 6`Pϝ***p<ǬYb_Ĵgͽo[.Hׯ__{' Vc￯u(Y֭ѭ[7tMߡC #W_\]]5۵ktU\\ua~ 6Yo7k?%mK8Zi+88_;EFF"#"z1!zڶH?3f@rrruPZo>:tӑHLLD`` %KNNNhpvvF=RoXfR{i' m۶E߾}wOlaaapss)q|WGc6ǨxyKLL /_D<==j;66VdՒ'aaabnn.$%%cuVoҪU+ .X˓#GX-[&$::ZHDDT*ٶm֭[###IKK>L%))Ie̘1bdd$ZC1/ 2|pB0)//5fmg:Qjݶk.2oS;j޽̜9Sc_|޴i̛7Oȵkה:k׮˒3O4ڟ}Z:x~)dggO>JٓO>ZūM?HJJFiܾPZ;XXXg^^T*lll4b2d6oެ9sW7ٳgq{.G]6}VՌuJY6m@=R񵰰@AA*e:t@||<ܹsBϞ=Z=M#mٺu+z/ѣ댵6ﳆ|+bԨQe"W^yknjnnGGG 4HOOǬYcу (==>>>452|ԦNm~]VV ;;jիWܾvNNNNPVk\wڻw/^~{|M+7ܳgn }E'56YUO֦ +++̟?7oĩSp1cJKKKst:mXXX`ɒ%DZcǴ^Ys{AVcժUܹ3/^j~ΈT F)**7Z/DVnnhm4]v;wW qvv;K.Z^zQF{ذa `nnިUjnj@5EۣG̟?ΝC닩Sbƍkkk;9is'-- _|mۆW.xyy!)) ֭C㣏>猈AsQ`ʔ)SNVPZZ|XqOVV j5=m:D/"22W\ݻwArtt%^u^и+ѣSԈ"j1!ҥ#Gbȑ+W; "`CDDDDD  &8DDDDDd0Q 8yr;"Gɓ'; "z0!jfwKNNtEϑУg@"jq*zDDaXX#!""j """""2Lp`0!""""" """""2Lp`0!""""" """""2Lp`0!""""" """""2Lp`0!""""" """""2Lp`0!""""" """""2Lp`0!"""""wDDD &8DDׯcӦM}bppp@nn#&""j<}@DD-c}3m4߆)M6]v:ixbll 33jn`d?)DDkDDDuV۷+V!Con޼eҥpvx>}nܸm;<""z1!"&ԩz0\d,!!A!i"""""2Lp`0!"zD222PZZ il ZF\\\eDDD2&8DD 刈9~geT*UeDDD2"""3n8@XX#!""j8DML <=ȈѥUVGaaN+..SO=xjIUs~-!ſDstt3Seޕ+Wкuk7c $''=""2+NDT˗/cѢEprrBJJ ֭[WWWbÆ m۶X`SSS ;;;XZZ uۇ>}m۶۷/#,, nnn055?J/1'&&"00Ē%K`kk '''JKKsN 8P&** * [lANN2 0--%%%瞃ZFPPoߎׯcڴiرc* ; ҡ!Cy󦈈 >\Zj%d…+yyy2rH122`ٿ˲eDGG+yxxСC%33S.]$...ҿe{DDT*ٶm֭[###IKK>L%))Ie̘1bdd$񵎡9cccc gϖ3gH~~x{{Z ٱcj AAAҾ}{!RZZ*111Ҿ}{+W kNhhk 5sw#"JEDDD 7oVʞyٸq|ҤIbgg<;v888h!F /*ۯ^*W^sy\vM)[v4IKK*ih|%%%@~'e{HHtks4iӦ X)^QQ~z|W033S[XX;t蠬"u9deegϞuy\㸳;;:cSC377# 3gb̙5kVmѣ~HOOsPqM:u G#4ܹszY_񅅅AVcժUܹ3/^&%LptްF||<5z@=0|;w:t/N7ĬKڌ IIIXnڷo}QDOOOL8vڅ8-/NDNQ#"Cʹ* BmeG"%%7o֘vِ8DFFujaiigÚ5kZ<沲۩yrܺuKXk֬%Kobʔ)߿?N< ʍDL18}?ʕ+(//t{F޽g}...zD|DEEbbbp-899vڅ()) ݻw011QNVTx? ˗/ǭ[gggj 2/RhΘ;vLi[DPQQC "8tɓ(--p{ѣGQTTħ}]lٲ(((`РAn/ ܾnO?Ezz}A?HLLDII ._oSN9ۇ^{ ;v+^y_#++ ~~~c=9mׯcӦMn_3cOIn`Ĉ;Կq.ADÿDD-ҥKŌ3Bٳx뭷0qD}HPӧlق~ nnnAPP.^,tQ~n^~\E⋈DHH|||;_… t{Ů]0i$euP7nxD{Lp`ȑ9r z$ԩS},,,4,}޺utSԈ=Sp{i;ՔwС""cCDDD̙3{mnee%>sxzz IIIks5KDԲRرXtR \Yo~pҥfk?99/\lﳖڷ믿bȑh۶-\aHrrr+V;&+--ʕ+ꊐ$$$[?77_??ĉq1+u8sss6wDosűc@R鴿 6h|Ah~TQQ+Wo>f͚5k 33ooo̞=׮]äIg!==hׯcӦM&O,{PoR5jNmiRגXׯT*QT@y8;;+(vvvbff&o="99YF!ңG5Gbmm-qqq̛7O#pWQ#2 -ό3"})44Vj~^B ە+Wtz?۷oGUU,--u"55-ݻall\뙧 !** @N0k,L:sؿΙ3si ֬Y5k4atD\8Evvv2o̙39s&\]]ѣG8|u3;wmׯѹsghiӔ}@R!;;~>:tӕ; 233d 픔 ??ѣT*QS{i' [m.ZpB<9rIpp߿_vZEnOTTm6)**[*So$--M>3$Ζ1cƈ:YQ}_k54> RKff6jX[[]vyiۺƢpCjmٲEj3|ZCD Q46ygdƍI&GͥR);tӧk]GD$--MK]YYɖ-[:kN>S TWWKe̙MD׮]S]VHZZR/KnnFO>hڴ&#Fh# Gigu^M_JYDD^)KLLyf;vXqppЈC\QQ!Jb-"rU JhΘ9uWRR"䧟~R4*=|???^dggk$8Eןj,w?Zn-gֺ"2|F1c a EEEFFF066V +++>}Z:x~)dggO>JٓO>ZūM?HJJFi75w3//* 666127oVΜ9+jٳ8{, {cflY]jW3;iӦ hGjy:(((@yyR֡CΝ;,SNcΟ?_83fmGgnnGGG 4HOOǬY}Hm /5VF+--ŨQ4D 93ܐ4C۶mmZ&G ~~~QtqqQdjS6w.++ xիn_;s''''XXX ##f޽x)7'Oك.^@/56YUO֦ +++̟?7oĩSp1cJKKs5]ŬM Vj*t/Fuuu{9ܽ.? qqqȑ#(,,DFFvZL2 VZ)I/r1`C̊ kkk߿֋kK rssk[!ڵܹm1 IDATH$j;K.Z^zQF{ذa `nnިj+k~mo=0|;w:t/N7%3,,쁉Yn:o>>Fua]k, 7# Yf_?|deeСC1exyyUV #ѣ Q3;z(RRR0ei8w'+eee(--U_x7n1cS3Ն.۷/,--g,[ 7n͛7wyGxkYvw?O?4:v/Rc4OTUU)uj^YY (S^R0{lݻk׮ɓ٧ݯ_?aݵ/ڌ\>K}ǵ5^@c\\"##qAիXvr amm`^(((#G017t]^^[n)_YYVX333HLLDϞ=0}>{7]}u ӱqF=zYYY"HD Q3srrڵ QRR . &wqQ_(.e^ӫ(&[@OA B%\"QoJ TPoLw@pAS;B üsP఼<3sΌ9Ie_ΝLܿ| lmm!ԥLLL /yyyh PPPb 'NxcΝHII[YYY4iRSS`ȑpqqAQQΞ=+ND())$::aaa)u<==D_maaO?IIIzcMDj;%%gΜAnn\۷oGvv6AD6l};a%"Bjj* *${XLcf/`)C gQ%###Rhh(Ӽy󨴴I__6oLm۶%###𠌌 Me9RiuCloK.d``@Ç˗/7##Lo߾jIVVVC]v%KPaa! :Tuk׮%;;J$ijvtQmb޴irZd yxxP`` Y[[͛)++̫k~*K]ѣGl[lM4Ie0ەM񍍍%3V m ݻ.U]ucX/\P}ݻwؾ +VPSNrJaZu!.\8ԖEpBQm!zMDť&1^Zad2ߏ &J}~)٣vAa6ŋ>}-??݃'.]T+73ihkkcٲe:&4a1M1Vvz_ c֭[tWWWNn1H~1 +ŘIJB9sFb0Vn߾L̘1 xp|嗘Ǐɓ+uX1ƚat%xxx -- AAA4i!5*.U !C1X#81VCrWƐ!CеkW\vG!)) {ưaðh"KcFGpc߿# .V*;v޽{#44={:,cc"_~x.^???NnjL&P(`cc 6t1^ caܸq:u*M8O갚^z!66?>FTb1pc8tk׮!&&6l@VF@@Ν;O> :,c'81FNNNNNz*yj6쐘ɓ'cĉ0a?.uX1d1*>@ꐚ_ӦMCV?`СRc?r2dwDNnQF!11VVV6l̙"b1@cy&qMbِdR* ̙3cϞ=:$c 0ƚ5"Bpp0mmm$&&bΜ94P'OիWabbAaP(Rc1l=}cƌ̙3gٳx7iЭ[7 _~%Fdb1@pk"""`ii?'OĪUвeKb"ikkgϞErr2,--*uX1NpcJvv61a8;;ʕ+:,lmm)S`ʔ)0a2331ƘxX OOOaΝ;v!Zt1L:ؽ{71Ƙx1bѢE>|8lmmM4|p\~F oooKcz#8&-)) w֮] ///Cb ""077Ǟ={`cc#uH1 0ƚ$"† 0`">>f 055PZZ*uX10ƚGaʔ)8w,YhB갘7nPt]c!a5)FZZbccM3&0g!??Gppa1C0ƚ, ѿb %bcc?:,cu/Qc5z޵k5`'NԩSQ\\;wb̘1RccRNj.ְ")) ={ĻヒEꯋs֭[{ncM'8F6lb ;v ]t,^۶mCLL r+6b yflڴ C ۷k:-[>}?o [[[,];vDfffcM'8F!$$}qAK믏 y}f]qd\kkklذ:OmK^^^pssˡxzzbڴiڲ-Z@֭keYk>qqqꫯ3Jc5T0/ !!!سgѦMb͔oߎȑ#۷/Μ9#iL:z%i 1֐qkr9V^ ~:&N(uX۷/ EXX0vXIc'81)]|9V\_:u:,Tt?lق ڵkRcNpc!"c˗/WD9իWahhz WB:4cE0$373gĬYpܹWP7/^v{7pIFBJJJry^2X;p,--hZ Zz:.\N:. 6dj1lm???={>%ݫR6KcUif1^x "88زe WVPPEaӦMpqqy?'8zqxxx ''?~!1V=iӦA[[?cKcuppp@= kFD̙"bfGpcuƍpwwǭ[s̑:$MHHf͚]bϞ=:$kx1V?ڢUVHLL5;'OիWѮ]; 4fz cV=~>fΜ> gΜA=1I눎_ݓ:,k8a՚X[[#55.\UвeKbLR-Z␛ Kc5Y0^Yvv6&O 777 ..:,}ŋ񁏏\]]!uX1$Wr xzz;wĘ1c:u*r9vgggCb&Gpc/-ˆ#`gg$Nnǵk1cRcM0jpwwvZxyyIcVDDaff={Rc0DS(ذa ===\|^+aff;;;TcƘ(>Ĕ)Spy,YhѢa1d6n??? 44ߤ1ai9==/^D@@'72L9s >>߿?O'c/X0qDa„ acc#uX5i}All,f̘8;;ӧRc_S~ԩSѢE 6l!1?Ŏ;0nܸj+ hio?c* 0g5 HLL1 < 0~xL<j˦bȑ(..(ca1&P^? $$h۶a1֬a툈/~ٳ*e8~8-Z$Q10p\.ի1dtEX1pݻ7 Ei&sKӴ`ll7nLhcƚٸx"8ad¥K C* rss 17ƚ4?SNŴi~Icz#F //OTM111ظqD1Ƙt5ƚ#G?FVwyGc/ܹsxwP(,ӲeK\xׯb5+|jr$''cܹcIE@@@A0\.NJ+_[o%uXWԾ}{XXXŋxвeK B@BB޽{K-c/&͛7ٳgk1ddd ::ǎO?4j r\?G&A__ׯ_k&qČ1V8aFFhh(z)uXz@Dr =_~ϟGii)QRRhѢԡ2X&ӧ>}:=cʕhٲa1&L`1/˗/k(X=ħ~ ###`Ȑ!RjhРAϥ5aO}ZeLtcukРApssL&Sy(%Kĉ^#85PYYY5kۇO>| ['~JѾ}{ " DPDٳg!5%"dzW222*me`;~8N \]vYX0a <<\Hޓ'O`mmtǣ_~fT9//C|||Ip`…Xn>Ektu҅;6@@ϟ?cMٺܜN>M4sL@aaaDDvZ@gϦJOO~ڷoO BBBĄѻK*;k,ڱc)j߿v ba;C:::j**--:$&NpXc"6߿?m޼Y{ʔ)djj*C֦9s233&"СݼyS5':{x%8bI/SWaa!ۅm׮]mΝ;޽{BիWz)%''$8zЎ;T$"qXCVUó1&̚5 ]vEll,1jU||<ŋشi8֭[ w&LΝ;v!44W"-- +՝m={kRu%c6KKK3C Ut,--m۶a˗/cРA/c߃X=KOOLJ~OOOL:0ƚ899NNNpssUu9 ĉ}^@rr2qUxi5X_4+өhܹDž >_#Np%Vѣ˗ 6@GGcs!Dnmm cccu IDAT,^k֬Ajj*qi,XP(@Be gΜ_.[[[#,, ˖-ógϐ(XPRRoKYb2 gFdd$֯_OOO91Xc c`8r*=w%X[[#$$G6m$1^]jj*>}b$%% _ `„ h߾=,,,{Evv6"""p9ƍ8yPQзo_tn(**:u8::bܸqCtt4H5Q~cΝBU%~M.b 'N ?3gbbbP(PRRӧON:"p Tb􄞞o_:ڀfয়~"LFQ4V-[OG2E5SL>{?~f 322"KKK:x >͛7ҬׯoF~{KBvvv>MMMiƟA&SSSڷommmkTR.]N/_&"Cm`` 0@eUѣ5_:T1dT/^BL&~,Y… 10a5_ cd2߿_7Tf%) #//OP\\#GO>HHH@^$1."w}sr0֬YSN ɍRII JKK cUĉ?~<`hhCb5| c/!>>%B/W\cq000@֭ѯ_?޽!1ƚarssꪱW\QY}1`gg?S0cM0VC>>>HNNָ&\.ǝ;w_Sd1cGp}aϞ=U>ߢE ;v,1BcGpVʦ'lժ|n݊Ǐիꫯ0dBf5 홙ܹ3toooܾ}w.] >3?~uY]m]ll>}"22{FPPש{ϰFʟJ1?~4)//\+JoV?=zDhҥu\z7u>L֭,[|_I}uӓK[kzϰW_i{#86cerrrp9Rڵkq;!&}3f޽{ߏUV~:tPhk}JKK/5RgX(Gpc9qqqÇSrrru<# oߞhte""3gR.](99FMzzzdeeE'O]@| wy-Z/Bonݚ^uڰax?J#iiiUɓ;uؑ>SW?.\H:tTZt)k׎)<<\<3f Se7o$"k ܹsdooOdmmM5S]4suǦ͜9PXXx eodhhHڿ? B-Z 4{l|2eeePIIP׾}(88w%taBYfю;^TݱԺukZf =IWWWcm>H]#GR˖- -ZbccLZZZxb:r Ĉ:.dk.ͥ`ዹ%''ݻܹC4~xҢ*ۢ..rtt$→?KR<ҥKԥK:vJlb)B Q=Npc$Ir8NiSL!SSSo'''%\.l&#/*_ Ԕo.vmۖv)*^u_*GPP=h֬Y*}½e""aBJNN}Gs_<==S5Jooj1m*)u-aÇ ¶[nڶmMuqq;0p@=z4PĚl$OM<|iΜ9¶LzEKٳUF$*UMm7[U%8<cU`eeǏ͛pvvFnn!:3fŋpwwQRR"<߹sghiiE¶aÆqqq˨cjjK,--?XTb;wJe ** _nZ(?MϟC&M6*1;::b۶m¶˗/cРAߚr \#Fhmm]m[ŴML?W쳪+lr000sD|!;;[e&9333^4L&L&kHJJ&t&LΝ; XٳXĜSISͤ cIN󑒒'''x{{ nnn(aLUӿwY):_HOOxÇ|Cjjש>s">>.\ݽʘ*y&񉆒g+*Kh:/,X\t OƨQզdaiի ,Xyyyظq#°l2<{ 999Š+DūeA.]?$x܄6@iiPFPuM6jUd={6"##~zxzzV궳'l/1m*Wumŋ‰'b11/^5k 558}4,X b@(Nff&'RzY3b^*ޔÓ /??Jv]d211#Gi…$r?V#G(&&JdaaAͣ+ՕJԻwo%CCC4hmڴ>SWvTʭ]߿ǠAtd@&OLYYYNMIIIC$ˋ222޽{dooOo*O?1T*KBBBm'JMmU74pʹ{qۛڵkWQNrJaiĈ&$$ HGG};wH__,--U~e~:q}G4V4_U0V_i似Q1  333ѹsgk?޸}v=,]xAgշu5|Mk,8i222c@BBJKKQ\\@LL  r9= le\"'ODQQ,,,{Evv6"""p9ƍ8y$ۇSNA[[[fY&^ñcкuk~~~ԩLLLqƩmADXb`dd':::Xn0i$ɓ'ȑ#₢"={Vh7'OP6ADHII3g+yp @c[ٲe ڶm")) }􁣣<a̘1شiRRRPTT˗/ݺu`СسgK. =z聨(4ʕ+7? 0j̛7gϞ$" aOy%%%xp)m]ϰn:2ܺ&oZugcSlٲf͚%u3cƌ:i_QqE~).޽{X *խM+gWeUضwIzI__F2ѪU(55VZE={$֭͜9*w]1cuޝtttȈ B۶mb\6ÇcjϢּL֭(~9yzzR߾}ŨLAAUY&PWB M)Vw8i^ ɓ @'8 7oYr9Np= NTU×5C q%Ҕ۪nJlƚGׇ=/_ƬYеkW`̘1ׇ5N:% PyĂbСXxsGtuuѭ[7lܸQT^^^ i2 UԩS:t(aff2[n;vē'O_}@DDJ=BNЫW/Kwum_}ϟ?!C@OO666wZ]4suǦ|rrrhO2dhтٳ˔EdbbB%%%B]`"* ~w >|X(P(h֬YcUwll,nݚ֬YCϟ?p8m>H]#GR˖- -ZbccLZZZxb:r Ĉ:.dk.ͥ`d---JNNݻwݹsi񤥥EU)aA]8ᇴw^*--6_M.].]бcTbsN) vqkt,ܹC>>>TPP &44Ю]Ԟb?XJ9Np͛LBNNNKr\MGtu_ U)m߾](s5j۶-ܹST꾬U܏B=zЬYTB{˖-D=|\~=P>Rvt)b궲QF]m#mbbUE]9nݺ%l;|0YvBxE7 IDAT-@۶mi:...ԱcG(|.)):|}!UC)6͙3GؖIoX|}} ={؈ĝSjvssS{onŇ MDz7n>M2EئyĪVUc?1k(+!ׇVƚxeӿoڴ PsR"ذa022B\\2T,ҥKHOOǀmΝ;U˗#** >>>Сԯ_9!ЦMm6L>@٥3 hʕ+r Lum6KKK,]umSd YsD/U_===dggXxp]ziii^)$QmK׮]1aܹh׮BCCY?Ҿ}{@Ϟ=E9U4]]?#~GBVMDz|[nزe]GzcRJJ '''L[=u;wFqq2T|]ZZ ==(TuDFF⣏>}ܹDž aaapww2 8@1mbTWUt|/^ ###,X999tN>QFMɠjիWk5X`„ER5SISkcZfMDz3fѣGTYs&矦#V=Npc5 ppp11i$!Lk5*rAWWN:+(O[[o>v-ѣGG Bvv6tuu/^/ڏ=@\4T^zaz*ӧcccce7u,@bԎ Btt4z%P[(9%Vm4t,vލ~;v8ҥTGLUIpxe+K7ׯ_3 ѱcGL>fff322ЩS'_:@9sǴiT.堰PX(3?^teHr\(Sq-e˖ٳgATTVX!*^/U܏ tBdxnnnBٜ PWjj*ڴir8fϞH_젥} ˫jEL4>JuU]۪j/ |x"p ÇX~p allŋc͚5HMMEvv6N> nP6 777|'v1Th:^V$h:p5̚5 fޗUub5h^YvMCJJ 0{l<~SLݻٳzǚBiC0`޽FDDΝ;|ܸq'OPv.͝;>> )S^^ ::p "Xfff022ĉ"*^0عs'RRR*GGG֭CVV&MT>>9r$\\\PTTg JJJ>FXX/]JCbbʨ-,,駟")) 'OǏq=:t0b^7n0igu@}+W\\,똘( r=z@rDǏN<"Qw߾}8uU ~p1n(**:u8:: #mQQFo߾С,lK^^CO9DDHMMEAARSSD+gfT(qהc WWWXZZbrss1w\Se?hb5Tqց<,];xe/00 FP7ExחҒ ;2sg{Zrs>;g̜9;!KdccC(77W3ϐ%'''==<<<‚hӦMHDD3g9wޡɓ'J{h1֮]K~N{=jiiM6 /@;v OOOڽ{7UUUQll,ŋu0\;K~]btҶnJ/I LFtw.'''ȑ#ʊɓ:~k!O%hM111Ehڵ)єor%I߉9#绷E/ׯ_/NY4E푚pdi}ή "Qyӳ\,t5v[%ѕ+WhҤI&݅=Rǣ!515,͑9PҦJrIa߾}(//PXX(J|ƔQhll|b 1c jlܸĝ;w֞Çul*Nj/nmmq!00K;u"`oo;;nQƔ}㑉Пt GYZ^wKhhV>Bm ÇiРA@g=zٳdggGeeeFoO%OUUM8Ti1廹T]]MG*v-'O$JEhwu[ݏKLD#KGyFkcE#Վmt4J{t&8%_P]n*++iK_J01$___:t(ߟFI6lйwYYYdggG&L?Zケ# 51ʻќ\/^Ē%K鷷D Udz4ї=<>>DII VZ%od-ju;mi"w$|'sw᭷ҹw'OƏ?|/y<2-Fpd?s,-ԋbv$>ZiiiPعs'͛7[ړaaG._94GMTdi0^:btWjCL6 |MX{2 0 #Ksdi,-;cEÃ$ێJ"/Ka(JilloKKKY3f +'ӽ444СC>}:l"WTT;v0iUOXX]f_͛77o4Y=6뮶5%Rdӝ)]l,[ Ç7z]r(;#Ksdi, 0^%%%/P[[+T{*'r@2Q۱o>TWWD'|Gxq…v}@DBtv[ܛmqM=duZ QQQ]'#y)V̟?]Ci=;w`Ϟ=o~u9w.jY#K#K+ѝ1hȑWSF(}Qۮqqqm62d 6o߮fWSlf\0}Z1bDnȠ(ף9pq.}>tUw]=/3v(BO= ٠Fc]Ԍ2#K3Y|N:E#G6K///oKeeet5rqq)S ڲe ݾ};v,YXXз~(Q͇"ZMnڔrrrS}}=&%jY> @TSSCRˋiڴiԧO@W\ӧZYjL'"wүk@BVx:uҒ~m4}.\;K>b|}}_~^yʢJ'sss'NP]]̙3HԶ̌/,A577b:pM> ޽{4o<277I_f͚EnpV?>>|>|hmMئTҴEG:{,QYY?/|-;44,--iΝTVVFyyy4n8RTT\\L4frarMMJ4'W~:C%cXm):R}v7%jYAo5//x $___{$I~._{nK𷟟YYYQKKvi@466۷Os>>8p *#~Plܻw&MƏr*'''ذaN#332d蔯r]M{ee%t?f޽{H幹2eG|cҥ:6zzzUoǏmgi|fvH5xGGGAg/_ݻw1f̘vv]zUOJmqssâE-[IIINJl<mӋ4Twnee''' ""XjA|W="??4(Yՙ1En}:C%c1]>{g>0 㔔aaaCPPC(\\\y?ݻcv)WI=n6ΰFiiqbc׬Y|W,YD&}4AmiP⛒vVX|'׿8p ֭[dgg3g|*..Z]|٨uPWW'|^-JYkSciZIKKZΝ;1|pl޼Y4&);.o{ܙ1EPn0&r?Jۿab鰵ENN/^,Rb_QQ'xCy{8̟?_H{駑7ѨO?z cǎY{'Lx_h=-B>}}v1CŒ%Kk B?(..Fzzp@y}lllٳg+)MJ||<>c̟?>>>{.-[ŋV^7>m3 |...,[ pa;#"_|X`?.ۿVVV8pSzmIҥKQZZ5k@R᭷Bii)Ə;v`ƌ1}ڢMHH#l2e羾FTTT;%""˚V;Js{VV}]ܽ{GF~b .\s^._nnnATTFիWcŊB[/^'N+xa֬Yhhhe&;ey̙OGuءaжL .]6+[|pv}[1#niii@1 \ 4E]aŊPv44ܿ_a h Avvv4GRR캽n9affTE0 0@Z}0]?Ƹq"aooϓAaFAcc# oacp5TTT`ʕt߿:^Cpppr)k֭֬[naaF*j\t 0c $&&v8g}y&$''+XZZb„ O$7`a jZm< cLf`{ a~0 0 09iQIDATcOpaayl 0 #ICC:ӧc˖-BzEE\\\c]ڵk&+ؼy3qM(oj[S"O 1ݙƲe0|p%Y?0 0|Gxq…vhP*ףMss3n޼ѣGUV!**"+m3S1OFZwwR M٭?>QSS!jҺ{B?w={H(QjJzd!a 6C"@7o6i=4b4i=dddPTTٺu+7nݥGꮫe@@uخڡOHH=ScZfwRSSۥ.j 0A+WR6عs2d9]|Tz^KCIݭÇPT驱Q1 <!00PT:u&77pssCII Ν JOOO;wNq444 )) 3gDddo'NɓaeeÇc׮]]|9:4hp=zΝ;3gBR򍂂lذCEYY^}UG)UUU6laff&+./Ĵi`mm'*A1\;m}شiq ڵ nnn0`֯_Sp1L4  7.^ζ4xxxGƇ~hĖE5;BZBBA[]7EEE;w.쐝cGGh,Mҝ> 1c jlܸĝ;wD\Qϝ;'Zyۭf^^fΜ +++xzz… } A\VVsruKPߋdHOOiߕS ه4ezm0 Ggyyyo[*++k׮ M2YXXЖ-[7رc‚[Eyږ1@k ǡCHVڸq`OJJ 999穾?JIIhǎ@X!Ug}F TRTE4m4ӧիWSnn.UUUIVSssPVrr2߿޽K eddyZ[[)""iԩ"KKKzJvor,f~zW(++*++ߟ)22N8Auuu²3g(#QR@333JLLZڿ? dnnNt>}:ҽ{h޼ydnnN999SΖ5kZϟOE[.egg+}:)բ4-;)}xyyѣGΞ=KvvvTVVFDK)߃E %KKKڹs'Q^^7T*See%3oIijjjDP9Q*;Z[[uUD+է5séA8&))Pbbޔ^w%jM(<<\qbVcc#988о}NJ!׿8p ֭[dgg3g|*..nwue֭C]]RJJ yŶ(VMi+'-- j;wñyfJ!{Gm;3ʍDwcl,wjrJ!==]xg;'8 0 >}:lmmŋ>WTTO$cڞw^9 ৛9m#F(.+##ۥ`ԨQCuu5:itQQceɵsOݱn:\| K/ݻwlmml8q"f͚8>} ƲE tguӦMCaa!bbb`gg7|.#Eʘb}Wkz7vk3) oRxEdffԩSí[-􁧧'lmm~n:~(=78 ²e˄t%跁rz1Nok֬AAAƌ#;/|W, K:k$vt խosguY:R}B+W ""V҄kSЗ< 0I&yyyhԷo_z*9š/_Ntu:u*W)zg?.Ν;}:p@z"{G?x*..'55{*))R;w.Rkk+566lkk+=x ۷(33S}kjjHVӳ>VZr%ŋSQQ}4{l@jvI?֖|kgK?_SS͟?_PÇ,|~zjnnV͛Gwբ4g}FDD111~۷/;wN}}*EzƎNlt+oRCJu Կڻw/UUUQQQ 럗Kio˖-dffF6m2|2Shhp\||<X!vdffƷPءJJJ@&UvG}?~di#9[SSCO>$y{{SSSNh6qӄR OpazEmÆ 4p@?~<_$RT/Ç),,T*޽L^x˅28qB6l0:vNP^ll, wӹ[^^N< 988PrrzÃ,,,͍6mDDD4sLyaYEvZOuҔ{QKK mڴƆ^xڱcyzzݻbcc֖.^S!YuVZxNZPPIFIm4rH"ooo:y~S-DGGSLLlsZv}J"߮(..mFC aÆۅ y)廔Z[[)>>ƎK'wwwڳgζ?,--iܸqtz7hʔ)v(gQ#6vhK.,{{{QG;gׯ_/w)M(6)Ejc?2 0=ȢEw+,L1iii>^z%!ׯ_GHHAgGRR캽n͘5u 5TC.0 J>|^y <^{5w-N ͱflݺfGSu 0cFUUj5.]1c;aų>Lܼy~~~ppp׿NNNf ,--1a|'9rd0 8g0 L 0 norNTϢw#EIDATxʂA 2Ŧw0%ͦoHY޻mO(Waysx31*?9y%,ٟ U^^D@ h YIE))EIKQM)EJ/rXr1^j#ܜYΟ1vp _ll׾:g]=\8o~z3:_qIKJ{0/HF9oS筝a\{W8?^ /mkDez#kXQrߞ$;4lU}vAiBY`B/-ݘ;F-_3,OY C|H0Op6 ?x%$?=3 sXD~(dsux p 0 U4 X?1w p?e p{<|߁` p-OX+. p}ZX+-'KFGXba~4_X[^9ݢ49wo\ (r8 }SYn~Ƿ Wa*@#%HVL F>+NBj0<<*!K^r_~}kD7ّ ';Dk~l~x/ nt8$FphET\aowv#tӅ2OUU+Sk$leTN ±j64^"H&fe0Nſ{,U'z-Zڝ`Bl 1`Cl 1`Cl?]H.<_HVzǻbk=PZtZ ` l|-XN,3=P\ӵZt-# [ `-.J42Ri\Oϟ!`x"{ɸ9:lXTH|/Ru XQȇݱL!T)Awi:iq; O%m|κ-QtWUoZd5;4ԗ͎õZ!] U<(~CKMRBBg#]p-CX#TUDdV=|N8"G^S:IgC D: $s q !X_y yt6 Ox- `c`%0xŮ+-\yXA,e'7$ēoWYliL?bk=h?xhin·{+NwK+9T"'&k14'2yI4˖)|vȾeo?c/o*9 O&#Ly#~.dM + Ό'2.5dvvr_0<p3NFĥy Mk0!V;:i'4 @w5Y]erY]}S(F*VE >{m@w&Qcłgm$'7wӝ`tgwҵ-9'+r!e[6^b>IЅT4N B\aow66r6~y9v]@ Gn)O|_:2 {o p5U`r#_Q) z\;}Dck0<}Y5ɢh265?*~ctje?Ŋ{]U_DcC;MN.l1 A 35!e;^g`OA} 7阸. Jҩ_ny>1IV^QHڝIoB&G&NzUu`x"|#\t[x.k'g~L\2&S+;'d&V9H c9FCΝ%g?Nvʎũ= -*CKQ&R`x"tH~宬X(I^"\]j#XUthfu`p4RD3ڌ/Ħg]>'&nuauAS\OR:$9|b? OLoǡSډ`x"}a~56s^t޽p(%mmvHb5v$9Z3)\AٝzX_T'S>r~px Æ>7/?PAѝ$ɴL'OgZ)+͂"`=?WRBT0<(ʐݕʅr})/oe{ݷvѝ}4dWN 7 pBx*I`?t4k&WٞAm1wg}#/E9 q|t_}D,K?}b[[FeWir|"U}U`U7|}b[[ZH? 8T#dѿ>8 >}i=`nE֝\˗+4|W.bu8 ؝|o}-N.?BXAXm;K+]}5I>,o߄Z结/;C}\7< p?*pc'*3O)^b{3w獓5?ߎԵN8'xJƷ~!/l17/W~(ruVeXH- `c0!6vc+IVW{UոzM/l5(֊t'ұG)h5g5ϨhߕaG 0%;^޺tΫۇ")[>.O 09>7e&[kKHa&kL O_]V윪 p\tΫ- s[n6ӓ-pFǫ_ sLozL]OҐTJ靰h8,ےJLb&^_Pwޥ=1U CjQ_U6gU~=,ߡ_JU5y2Uzg }pH_TRiid\Ϙe~7SHq̊lkKU/'*'d5p,}"<8 Rd#fdiM \Бlj"k&&HUk bzuRYwLV_D7zkM?s{c_N=Bhjx17|ɭs->Oʿzl2ܣi7̞Z `ON p(bCJtY(B1ɂkނ I(ˏ9r+ J J9O%Ğ=={je9k |zc\HO.8ܕsrU.S{c )I!14#;Ƿo%"gY`^n_Q>-w([[X2+_ߪ}AʒR$ީ<컠HHcq.;R_|1^AkVo:@YջGH4O;2ۘ'}i2/0 bPn%+lpҚ13b1C1_|ӻF1R6v)d҄+UY]\g50?$ 0QZn>it1xĦGcop4ϲx7M@YtV0VZG^\KѭVx өSLuդy@X%h7Pyw"ʼnǣJ=iXObcѽ(v/z| @пh39@Y_im̒c#Sl#75C+gz Vߓˏ9=tpi9/hC6\B;?mOtl*15Yn Z=0<}jzLOXj0{cC2Mj^SW- nE:Т,ZZY^N [GCFVh Fl^p'U_'ejb1bU>[3H(N7mG{zIӳmp޵⥠>o'2[Iݘ|isZ60X~ Jxkg;q}zm@׳VKIyZ:®Ebq;[?hNo}c5cK6Wx,4j޻|J~Nsw>+(3Y+oXK{}q3̃"$M;l9=;F]o!:쬕1lUofm35=zsIluU1f*ނcWX[/rj{(KԛL4oHY%!Se+ڴ'{ݵ&J_9Jc3V}DNURzq1x4^^OdU#po |C;91 p `!%1鉲i/h@C~#1d_2Ns8~Es{+> X}|+/lh|P=}28yQhgY}ٍA3ӿ8Ja$I L)6ݖy]6?D?ɴ )-T^#~Js;!E yLӳ* {cV@(_5e8_&ܘGnef"w~lwvkwyw k^ |LZofi/obҿnTOٹcRԏIǽ->|=a-W|3w^5r:o++Գh6[݁7Y\dញ۹E 0\'8NyD*40y_u_zAcm;`%vn f/ XTOꈝ ٧?'Z7i(GVV:_^#8E/?QGi~;wL2AUrbVF{,9.ٜʞFH9.FH8 pg4 ﺆsQCN 1J=гZƜ"0 1 linkcount may be outside the directory that the changeset is being produced from, in which case the `linkname` is not recorded in the changeset. - Hardlinks are stored in a tar archive with type of a `1` char, per the [GNU Basic Tar Format][gnu-tar-standard] and [libarchive tar(5)][libarchive-tar]. - While approaches to deriving new or changed hardlinks may vary, a possible approach is: ```text SET LinkMap to map[< Major:Minor String >]map[< inode integer >]< path string > SET LinkNames to map[< src path string >]< dest path string > FOR each path in root path IF path type is directory CONTINUE ENDIF SET filestat to stat(path) IF filestat num of links == 1 CONTINUE ENDIF IF LinkMap[filestat device][filestat inode] is not empty SET LinkNames[path] to LinkMap[filestat device][filestat inode] ELSE SET LinkMap[filestat device][filestat inode] to path ENDIF END FOR ``` With this approach, the link map and links names of a directory could be compared against that of another directory to derive additions and changes to hardlinks. #### Platform-specific attributes Implementations on Windows MUST support these additional attributes, encoded in [PAX vendor extensions](https://github.com/libarchive/libarchive/wiki/ManPageTar5#pax-interchange-format) as follows: - [Windows file attributes](https://msdn.microsoft.com/en-us/library/windows/desktop/gg258117(v=vs.85).aspx) (`MSWINDOWS.fileattr`) - [Security descriptor](https://msdn.microsoft.com/en-us/library/cc230366.aspx) (`MSWINDOWS.rawsd`): base64-encoded self-relative binary security descriptor - Mount points (`MSWINDOWS.mountpoint`): if present on a directory symbolic link, then the link should be created as a [directory junction](https://en.wikipedia.org/wiki/NTFS_junction_point) - Creation time (`LIBARCHIVE.creationtime`) ## Creating ### Initial Root Filesystem The initial root filesystem is the base or parent layer. For this example, an image root filesystem has an initial state as an empty directory. The name of the directory is not relevant to the layer itself, only for the purpose of producing comparisons. Here is an initial empty directory structure for a changeset, with a unique directory name `rootfs-c9d-v1`. ```text rootfs-c9d-v1/ ``` ### Populate Initial Filesystem Files and directories are then created: ```text rootfs-c9d-v1/ etc/ my-app-config bin/ my-app-binary my-app-tools ``` The `rootfs-c9d-v1` directory is then created as a plain [tar archive][tar-archive] with relative path to `rootfs-c9d-v1`. Entries for the following files: ```text ./ ./etc/ ./etc/my-app-config ./bin/ ./bin/my-app-binary ./bin/my-app-tools ``` ### Populate a Comparison Filesystem Create a new directory and initialize it with a copy or snapshot of the prior root filesystem. Example commands that can preserve [file attributes](#file-attributes) to make this copy are: - [cp(1)](https://linux.die.net/man/1/cp): `cp -a rootfs-c9d-v1/ rootfs-c9d-v1.s1/` - [rsync(1)](https://linux.die.net/man/1/rsync): `rsync -aHAX rootfs-c9d-v1/ rootfs-c9d-v1.s1/` - [tar(1)](https://linux.die.net/man/1/tar): `mkdir rootfs-c9d-v1.s1 && tar --acls --xattrs -C rootfs-c9d-v1/ -c . | tar -C rootfs-c9d-v1.s1/ --acls --xattrs -x` (including `--selinux` where supported) Any [changes](#change-types) to the snapshot MUST NOT change or affect the directory it was copied from. For example `rootfs-c9d-v1.s1` is an identical snapshot of `rootfs-c9d-v1`. In this way `rootfs-c9d-v1.s1` is prepared for updates and alterations. **Implementor's Note**: *a copy-on-write or union filesystem can efficiently make directory snapshots* Initial layout of the snapshot: ```text rootfs-c9d-v1.s1/ etc/ my-app-config bin/ my-app-binary my-app-tools ``` See [Change Types](#change-types) for more details on changes. For example, add a directory at `/etc/my-app.d` containing a default config file, removing the existing config file. Also a change (in attribute or file content) to `./bin/my-app-tools` binary to handle the config layout change. Following these changes, the representation of the `rootfs-c9d-v1.s1` directory: ```text rootfs-c9d-v1.s1/ etc/ my-app.d/ default.cfg bin/ my-app-binary my-app-tools ``` ### Determining Changes When two directories are compared, the relative root is the top-level directory. The directories are compared, looking for files that have been [added, modified, or removed](#change-types). For this example, `rootfs-c9d-v1/` and `rootfs-c9d-v1.s1/` are recursively compared, each as relative root path. The following changeset is found: ```text Added: /etc/my-app.d/ Added: /etc/my-app.d/default.cfg Modified: /bin/my-app-tools Deleted: /etc/my-app-config ``` This reflects the removal of `/etc/my-app-config` and creation of a file and directory at `/etc/my-app.d/default.cfg`. `/bin/my-app-tools` has also been replaced with an updated version. ### Representing Changes A [tar archive][tar-archive] is then created which contains _only_ this changeset: - Added and modified files and directories in their entirety - Deleted files or directories marked with a [whiteout file](#whiteouts) The resulting tar archive for `rootfs-c9d-v1.s1` has the following entries: ```text ./etc/my-app.d/ ./etc/my-app.d/default.cfg ./bin/my-app-tools ./etc/.wh.my-app-config ``` To signify that the resource `./etc/my-app-config` MUST be removed when the changeset is applied, the basename of the entry is prefixed with `.wh.`. ## Applying Changesets - Layer Changesets of [media type](media-types.md) `application/vnd.oci.image.layer.v1.tar` are _applied_, rather than simply extracted as tar archives. - Applying a layer changeset requires special consideration for the [whiteout](#whiteouts) files. - In the absence of any [whiteout](#whiteouts) files in a layer changeset, the archive is extracted like a regular tar archive. ### Changeset over existing files This section specifies applying an entry from a layer changeset if the target path already exists. If the entry and the existing path are both directories, then the existing path's attributes MUST be replaced by those of the entry in the changeset. In all other cases, the implementation MUST do the semantic equivalent of the following: - removing the file path (e.g. [`unlink(2)`](https://linux.die.net/man/2/unlink) on Linux systems) - recreating the file path, based on the contents and attributes of the changeset entry ## Whiteouts - A whiteout file is an empty file with a special filename that signifies a path should be deleted. - A whiteout filename consists of the prefix `.wh.` plus the basename of the path to be deleted. - As files prefixed with `.wh.` are special whiteout markers, it is not possible to create a filesystem which has a file or directory with a name beginning with `.wh.`. - Once a whiteout is applied, the whiteout itself MUST also be hidden. - Whiteout files MUST only apply to resources in lower/parent layers. - Files that are present in the same layer as a whiteout file can only be hidden by whiteout files in subsequent layers. The following is a base layer with several resources: ```text a/ a/b/ a/b/c/ a/b/c/bar ``` When the next layer is created, the original `a/b` directory is deleted and recreated with `a/b/c/foo`: ```text a/ a/.wh..wh..opq a/b/ a/b/c/ a/b/c/foo ``` When processing the second layer, `a/.wh..wh..opq` is applied first, before creating the new version of `a/b`, regardless of the ordering in which the whiteout file was encountered. For example, the following layer is equivalent to the layer above: ```text a/ a/b/ a/b/c/ a/b/c/foo a/.wh..wh..opq ``` Implementations SHOULD generate layers such that the whiteout files appear before sibling directory entries. ### Opaque Whiteout - In addition to expressing that a single entry should be removed from a lower layer, layers may remove all of the children using an opaque whiteout entry. - An opaque whiteout entry is a file with the name `.wh..wh..opq` indicating that all siblings are hidden in the lower layer. Let's take the following base layer as an example: ```text etc/ my-app-config bin/ my-app-binary my-app-tools tools/ my-app-tool-one ``` If all children of `bin/` are removed, the next layer would have the following: ```text bin/ .wh..wh..opq ``` This is called _opaque whiteout_ format. An _opaque whiteout_ file hides _all_ children of the `bin/` including sub-directories and all descendants. Using _explicit whiteout_ files, this would be equivalent to the following: ```text bin/ .wh.my-app-binary .wh.my-app-tools .wh.tools ``` In this case, a unique whiteout file is generated for each entry. If there were more children of `bin/` in the base layer, there would be an entry for each. Note that this opaque file will apply to _all_ children, including sub-directories, other resources and all descendants. Implementations SHOULD generate layers using _explicit whiteout_ files, but MUST accept both. Any given image is likely to be composed of several of these Image Filesystem Changeset tar archives. ## Non-Distributable Layers > **NOTE**: Non-distributable layers are deprecated, and not recommended for future use. > Implementations SHOULD NOT produce new non-distributable layers. Due to legal requirements, certain layers may not be regularly distributable. Such "non-distributable" layers are typically downloaded directly from a distributor but never uploaded. Non-distributable layers SHOULD be tagged with an alternative mediatype of `application/vnd.oci.image.layer.nondistributable.v1.tar`. Implementations SHOULD NOT upload layers tagged with this media type; however, such a media type SHOULD NOT affect whether an implementation downloads the layer. [Descriptors](descriptor.md) referencing non-distributable layers MAY include `urls` for downloading these layers directly; however, the presence of the `urls` field SHOULD NOT be used to determine whether or not a layer is non-distributable. [libarchive-tar]: https://github.com/libarchive/libarchive/wiki/ManPageTar5#POSIX_ustar_Archives [gnu-tar-standard]: https://www.gnu.org/software/tar/manual/html_node/Standard.html [rfc1952_2]: https://tools.ietf.org/html/rfc1952 [tar-archive]: https://en.wikipedia.org/wiki/Tar_(computing) [rfc8478]: https://tools.ietf.org/html/rfc8478 image-spec-1.1.0/manifest.md000066400000000000000000000322031455506543700156710ustar00rootroot00000000000000# OCI Image Manifest Specification There are three main goals of the Image Manifest Specification. The first goal is content-addressable images, by supporting an image model where the image's configuration can be hashed to generate a unique ID for the image and its components. The second goal is to allow multi-architecture images, through a "fat manifest" which references image manifests for platform-specific versions of an image. In OCI, this is codified in an [image index](image-index.md). The third goal is to be [translatable](conversion.md) to the [OCI Runtime Specification](https://github.com/opencontainers/runtime-spec). This section defines the `application/vnd.oci.image.manifest.v1+json` [media type](media-types.md). For the media type(s) that this is compatible with see the [matrix](media-types.md#compatibility-matrix). ## Image Manifest Unlike the [image index](image-index.md), which contains information about a set of images that can span a variety of architectures and operating systems, an image manifest provides a configuration and set of layers for a single container image for a specific architecture and operating system. ## _Image Manifest_ Property Descriptions - **`schemaVersion`** *int* This REQUIRED property specifies the image manifest schema version. For this version of the specification, this MUST be `2` to ensure backward compatibility with older versions of Docker. The value of this field will not change. This field MAY be removed in a future version of the specification. - **`mediaType`** *string* This property SHOULD be used and [remain compatible](media-types.md#compatibility-matrix) with earlier versions of this specification and with other similar external formats. When used, this field MUST contain the media type `application/vnd.oci.image.manifest.v1+json`. This field usage differs from the [descriptor](descriptor.md#properties) use of `mediaType`. - **`artifactType`** *string* This OPTIONAL property contains the type of an artifact when the manifest is used for an artifact. This MUST be set when `config.mediaType` is set to the [empty value](#guidance-for-an-empty-descriptor). If defined, the value MUST comply with [RFC 6838][rfc6838], including the [naming requirements in its section 4.2][rfc6838-s4.2], and MAY be registered with [IANA][iana]. Implementations storing or copying image manifests MUST NOT error on encountering an `artifactType` that is unknown to the implementation. - **`config`** *[descriptor](descriptor.md)* This REQUIRED property references a configuration object for a container, by digest. Beyond the [descriptor requirements](descriptor.md#properties), the value has the following additional restrictions: - **`mediaType`** *string* This [descriptor property](descriptor.md#properties) has additional restrictions for `config`. Implementations MUST NOT attempt to parse the referenced content if this media type is unknown and instead consider the referenced content as arbitrary binary data (e.g.: as `application/octet-stream`). Implementations storing or copying image manifests MUST NOT error on encountering a value that is unknown to the implementation. Implementations MUST support at least the following media types: - [`application/vnd.oci.image.config.v1+json`](config.md) Manifests for container images concerned with portability SHOULD use one of the above media types. Manifests for artifacts concerned with portability SHOULD use `config.mediaType` as described in [Guidelines for Artifact Usage](#guidelines-for-artifact-usage). If the manifest uses a different media type than the above, it MUST comply with [RFC 6838][rfc6838], including the [naming requirements in its section 4.2][rfc6838-s4.2], and MAY be registered with [IANA][iana]. To set an effectively null or empty config and maintain portability see the [guidance for an empty descriptor](#guidance-for-an-empty-descriptor) below, and `DescriptorEmptyJSON` of the reference code. - **`layers`** *array of objects* Each item in the array MUST be a [descriptor](descriptor.md). For portability, `layers` SHOULD have at least one entry. See the [guidance for an empty descriptor](#guidance-for-an-empty-descriptor) below, and `DescriptorEmptyJSON` of the reference code. When the `config.mediaType` is set to `application/vnd.oci.image.config.v1+json`, the following additional restrictions apply: - The array MUST have the base layer at index 0. - Subsequent layers MUST then follow in stack order (i.e. from `layers[0]` to `layers[len(layers)-1]`). - The final filesystem layout MUST match the result of [applying](layer.md#applying-changesets) the layers to an empty directory. - The [ownership, mode, and other attributes](layer.md#file-attributes) of the initial empty directory are unspecified. Beyond the [descriptor requirements](descriptor.md#properties), the value has the following additional restrictions: - **`mediaType`** *string* This [descriptor property](descriptor.md#properties) has additional restrictions for `layers[]`. Implementations MUST support at least the following media types: - [`application/vnd.oci.image.layer.v1.tar`](layer.md) - [`application/vnd.oci.image.layer.v1.tar+gzip`](layer.md#gzip-media-types) - [`application/vnd.oci.image.layer.nondistributable.v1.tar`](layer.md#non-distributable-layers) - [`application/vnd.oci.image.layer.nondistributable.v1.tar+gzip`](layer.md#gzip-media-types) Manifests concerned with portability SHOULD use one of the above media types. Implementations storing or copying image manifests MUST NOT error on encountering a `mediaType` that is unknown to the implementation. Entries in this field will frequently use the `+gzip` types. If the manifest uses a different media type than the above, it MUST comply with [RFC 6838][rfc6838], including the [naming requirements in its section 4.2][rfc6838-s4.2], and MAY be registered with [IANA][iana]. See [Guidelines for Artifact Usage](#guidelines-for-artifact-usage) for other uses of the `layers`. - **`subject`** *[descriptor](descriptor.md)* This OPTIONAL property specifies a [descriptor](descriptor.md) of another manifest. This value defines a weak association to a separate [Merkle Directed Acyclic Graph (DAG)][dag] structure, and is used by the [`referrers` API][referrers-api] to include this manifest in the list of responses for the subject digest. - **`annotations`** *string-string map* This OPTIONAL property contains arbitrary metadata for the image manifest. This OPTIONAL property MUST use the [annotation rules](annotations.md#rules). See [Pre-Defined Annotation Keys](annotations.md#pre-defined-annotation-keys). ## Example Image Manifest *Example showing an image manifest:* ```json,title=Manifest&mediatype=application/vnd.oci.image.manifest.v1%2Bjson { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7", "size": 7023 }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0", "size": 32654 }, { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b", "size": 16724 }, { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736", "size": 73109 } ], "subject": { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", "size": 7682 }, "annotations": { "com.example.key1": "value1", "com.example.key2": "value2" } } ``` ## Guidance for an Empty Descriptor *Implementers note*: The following is considered GUIDANCE for portability. Parts of the spec necessitate including a descriptor to a blob where some implementations of artifacts do not have associated content. While an empty blob (`size` of 0) may be preferable, practice has shown that not to be ubiquitously supported. The media type `application/vnd.oci.empty.v1+json` (`MediaTypeEmptyJSON`) has been specified for a descriptor that has no content for the implementation. The blob payload is the most minimal content that is still a valid JSON object: `{}` (`size` of 2). The blob digest of `{}` is `sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a`. The data field is optional, and if included is the base64 encoding of `{}`: `e30=`. The resulting descriptor shown here is also defined in reference code as `DescriptorEmptyJSON`: ```json,title=empty%20config&mediatype=application/vnd.oci.descriptor.v1%2Bjson { "mediaType": "application/vnd.oci.empty.v1+json", "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", "size": 2, "data": "e30=" } ``` ## Guidelines for Artifact Usage Content other than OCI container images MAY be packaged using the image manifest. When this is done, the `config.mediaType` value MUST be set to a value specific to the artifact type or the [empty value](#guidance-for-an-empty-descriptor). If the `config.mediaType` is set to the empty value, the `artifactType` MUST be defined. If the artifact does not need layers, a single layer SHOULD be included with a non-zero size. The suggested content for an unused `layers` array is the [empty descriptor](#guidance-for-an-empty-descriptor). The design of the artifact depends on what content is being packaged with the artifact. The decision tree below and the associated examples MAY be used to design new artifacts: 1. Does the artifact consist of at least one file or blob? If yes, continue to 2. If no, specify the `artifactType`, and set the `config` and a single `layers` element to the empty descriptor value. Here is an example of this with annotations included: ```json,title=Minimal%20artifact&mediatype=application/vnd.oci.image.manifest.v1%2Bjson { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", "artifactType": "application/vnd.example+type", "config": { "mediaType": "application/vnd.oci.empty.v1+json", "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", "size": 2 }, "layers": [ { "mediaType": "application/vnd.oci.empty.v1+json", "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", "size": 2 } ], "annotations": { "oci.opencontainers.image.created": "2023-01-02T03:04:05Z", "com.example.data": "payload" } } ``` 2. Does the artifact have additional JSON formatted metadata as configuration? If yes, continue to 3. If no, specify the `artifactType`, include the artifact in the `layers`, and set `config` to the empty descriptor value. Here is an example of this with a single layer: ```json,title=Artifact%20without%20config&mediatype=application/vnd.oci.image.manifest.v1%2Bjson { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", "artifactType": "application/vnd.example+type", "config": { "mediaType": "application/vnd.oci.empty.v1+json", "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", "size": 2 }, "layers": [ { "mediaType": "application/vnd.example+type", "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317", "size": 1234 } ] } ``` 3. For artifacts with a config blob, specify the `artifactType` to a common value for your artifact tooling, specify the `config` with the metadata for this artifact, and include the artifact in the `layers`. Here is an example of this: ```json,title=Artifact%20with%20config&mediatype=application/vnd.oci.image.manifest.v1%2Bjson { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", "artifactType": "application/vnd.example+type", "config": { "mediaType": "application/vnd.example.config.v1+json", "digest": "sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03", "size": 123 }, "layers": [ { "mediaType": "application/vnd.example.data.v1.tar+gzip", "digest": "sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317", "size": 1234 } ] } ``` _Implementers note:_ artifacts have historically been created without an `artifactType` field, and tooling to work with artifacts should fallback to the `config.mediaType` value. [dag]: https://en.wikipedia.org/wiki/Merkle_tree [iana]: https://www.iana.org/assignments/media-types/media-types.xhtml [referrers-api]: https://github.com/opencontainers/distribution-spec/blob/main/spec.md#listing-referrers [rfc6838]: https://tools.ietf.org/html/rfc6838 [rfc6838-s4.2]: https://tools.ietf.org/html/rfc6838#section-4.2 image-spec-1.1.0/media-types.md000066400000000000000000000125161455506543700163110ustar00rootroot00000000000000# OCI Image Media Types The following media types identify the formats described here and their referenced resources: - `application/vnd.oci.descriptor.v1+json`: [Content Descriptor](descriptor.md) - `application/vnd.oci.layout.header.v1+json`: [OCI Layout](image-layout.md#oci-layout-file) - `application/vnd.oci.image.index.v1+json`: [Image Index](image-index.md) - `application/vnd.oci.image.manifest.v1+json`: [Image manifest](manifest.md#image-manifest) - `application/vnd.oci.image.config.v1+json`: [Image config](config.md) - `application/vnd.oci.image.layer.v1.tar`: ["Layer", as a tar archive](layer.md) - `application/vnd.oci.image.layer.v1.tar+gzip`: ["Layer", as a tar archive](layer.md#gzip-media-types) compressed with [gzip][rfc1952] - `application/vnd.oci.image.layer.v1.tar+zstd`: ["Layer", as a tar archive](layer.md#zstd-media-types) compressed with [zstd][rfc8478] - `application/vnd.oci.empty.v1+json`: [Empty for unused descriptors](manifest.md#guidance-for-an-empty-descriptor) The following media types identify a ["Layer" with distribution restrictions](layer.md#non-distributable-layers), but are **deprecated** and not recommended for future use: - `application/vnd.oci.image.layer.nondistributable.v1.tar`: "Layer", as a tar archive - `application/vnd.oci.image.layer.nondistributable.v1.tar+gzip`: ["Layer", as a tar archive with distribution restrictions](layer.md#gzip-media-types) compressed with [gzip][rfc1952] - `application/vnd.oci.image.layer.nondistributable.v1.tar+zstd`: ["Layer", as a tar archive with distribution restrictions](layer.md#zstd-media-types) compressed with [zstd][rfc8478] ## Media Type Conflicts [Blob](image-layout.md) retrieval methods MAY return media type metadata. For example, a HTTP response might return a manifest with the Content-Type header set to `application/vnd.oci.image.manifest.v1+json`. Implementations MAY also have expectations for the blob's media type and digest (e.g. from a [descriptor](descriptor.md) referencing the blob). - Implementations that do not have an expected media type for the blob SHOULD respect the returned media type. - Implementations that have an expected media type which matches the returned media type SHOULD respect the matched media type. - Implementations that have an expected media type which does not match the returned media type SHOULD: - Respect the expected media type if the blob matches the expected digest. Implementations MAY warn about the media type mismatch. - Return an error if the blob does not match the expected digest (as [recommended for descriptors](descriptor.md#properties)). - Return an error if they do not have an expected digest. ## Compatibility Matrix The OCI Image Specification strives to be backwards and forwards compatible when possible. Breaking compatibility with existing systems creates a burden on users whether they are build systems, distribution systems, container engines, etc. This section shows where the OCI Image Specification is compatible with formats external to the OCI Image and different versions of this specification. ### application/vnd.oci.image.index.v1+json Similar/related schema: - [application/vnd.docker.distribution.manifest.list.v2+json](https://github.com/distribution/distribution/blob/master/docs/spec/manifest-v2-2.md#manifest-list) - `.annotations`: only present in OCI - `.[]manifests.annotations`: only present in OCI - `.[]manifests.urls`: only present in OCI ### application/vnd.oci.image.manifest.v1+json Similar/related schema: - [application/vnd.docker.distribution.manifest.v2+json](https://github.com/distribution/distribution/blob/master/docs/spec/manifest-v2-2.md#image-manifest-field-descriptions) - `.annotations`: only present in OCI - `.config.annotations`: only present in OCI - `.config.urls`: only present in OCI - `.[]layers.annotations`: only present in OCI ### application/vnd.oci.image.layer.v1.tar+gzip Interchangeable and fully compatible mime-types: - [application/vnd.docker.image.rootfs.diff.tar.gzip](https://github.com/moby/moby/blob/v20.10.8/image/spec/v1.2.md#creating-an-image-filesystem-changeset) ### application/vnd.oci.image.config.v1+json Similar/related schema: - [application/vnd.docker.container.image.v1+json](https://github.com/moby/moby/blob/v20.10.8/image/spec/v1.2.md#image-json-description) (Docker Image Spec v1.2) - `.config.Memory`: only present in Docker, and reserved in OCI - `.config.MemorySwap`: only present in Docker, and reserved in OCI - `.config.CpuShares`: only present in Docker, and reserved in OCI - `.config.Healthcheck`: only present in Docker, and reserved in OCI - [Moby/Docker](https://github.com/moby/moby) - `.config.ArgsEscaped`: Windows-specific Moby/Docker extension, deprecated in OCI, available for compatibility with older images. `.config.StopSignal` and `.config.Labels` are accidentally undocumented in Docker Image Spec v1.2, but these fields are not OCI-specific concepts. ## Relations The following figure shows how the above media types reference each other: ![media types](img/media-types.png) [Descriptors](descriptor.md) are used for all references. The image-index being a "fat manifest" references a list of image manifests per target platform. An image manifest references exactly one target configuration and possibly many layers. [rfc1952]: https://tools.ietf.org/html/rfc1952 [rfc8478]: https://tools.ietf.org/html/rfc8478 image-spec-1.1.0/project.md000066400000000000000000000003661455506543700155360ustar00rootroot00000000000000# 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.1.0/schema/000077500000000000000000000000001455506543700150015ustar00rootroot00000000000000image-spec-1.1.0/schema/backwards_compatibility_test.go000066400000000000000000000305001455506543700232570ustar00rootroot00000000000000// Copyright 2016 The Linux Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package schema_test import ( _ "crypto/sha256" "strings" "testing" "github.com/opencontainers/go-digest" "github.com/opencontainers/image-spec/schema" v1 "github.com/opencontainers/image-spec/specs-go/v1" ) var compatMap = map[string]string{ "application/vnd.docker.distribution.manifest.list.v2+json": v1.MediaTypeImageIndex, "application/vnd.docker.distribution.manifest.v2+json": v1.MediaTypeImageManifest, "application/vnd.docker.image.rootfs.diff.tar.gzip": v1.MediaTypeImageLayerGzip, "application/vnd.docker.container.image.v1+json": v1.MediaTypeImageConfig, } // convertFormats converts Docker v2.2 image format JSON documents to OCI // format by simply replacing instances of the strings found in the compatMap // found in the input string. func convertFormats(input string) string { out := input for k, v := range compatMap { out = strings.Replace(out, k, v, -1) } return out } func TestBackwardsCompatibilityImageIndex(t *testing.T) { for i, tt := range []struct { imageIndex string digest digest.Digest fail bool }{ { digest: "sha256:4ffd0883f25635999f04ea543240a27c9a4341979ff7d46a9774f71512eebb1f", imageIndex: `{ "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", "manifests": [ { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 2094, "digest": "sha256:7820f9a86d4ad15a2c4f0c0e5479298df2aa7c2f6871288e2ef8546f3e7b6783", "platform": { "architecture": "ppc64le", "os": "linux" } }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 1922, "digest": "sha256:ae1b0e06e8ade3a11267564a26e750585ba2259c0ecab59ab165ad1af41d1bdd", "platform": { "architecture": "amd64", "os": "linux" } }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 2084, "digest": "sha256:e4c0df75810b953d6717b8f8f28298d73870e8aa2a0d5e77b8391f16fdfbbbe2", "platform": { "architecture": "s390x", "os": "linux" } }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 2084, "digest": "sha256:07ebe243465ef4a667b78154ae6c3ea46fdb1582936aac3ac899ea311a701b40", "platform": { "architecture": "arm", "os": "linux", "variant": "v7" } }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 2090, "digest": "sha256:fb2fc0707b86dafa9959fe3d29e66af8787aee4d9a23581714be65db4265ad8a", "platform": { "architecture": "arm64", "os": "linux", "variant": "v8" } } ] }`, fail: false, }, } { got := digest.FromString(tt.imageIndex) if tt.digest != got { t.Errorf("test %d: expected digest %s but got %s", i, tt.digest, got) } imageIndex := convertFormats(tt.imageIndex) r := strings.NewReader(imageIndex) err := schema.ValidatorMediaTypeImageIndex.Validate(r) if got := err != nil; tt.fail != got { t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) } } } func TestBackwardsCompatibilityManifest(t *testing.T) { for i, tt := range []struct { manifest string digest digest.Digest fail bool }{ // manifest pulled from docker hub using hash value // // curl -L -H "Authorization: Bearer ..." -H \ // "Accept: application/vnd.docker.distribution.manifest.v2+json" \ // https://registry-1.docker.io/v2/library/docker/manifests/sha256:888206c77cd2811ec47e752ba291e5b7734e3ef137dfd222daadaca39a9f17bc { digest: "sha256:888206c77cd2811ec47e752ba291e5b7734e3ef137dfd222daadaca39a9f17bc", manifest: `{ "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": { "mediaType": "application/octet-stream", "size": 3210, "digest": "sha256:5359a4f250650c20227055957e353e8f8a74152f35fe36f00b6b1f9fc19c8861" }, "layers": [ { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": 2310272, "digest": "sha256:fae91920dcd4542f97c9350b3157139a5d901362c2abec284de5ebd1b45b4957" }, { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": 913022, "digest": "sha256:f384f6ab36adad485192f09379c0b58dc612a3cde82c551e082a7c29a87c95da" }, { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": 9861668, "digest": "sha256:ed0d2dd5e1a0e5e650a330a864c8a122e9aa91fa6ba9ac6f0bd1882e59df55e7" }, { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": 465, "digest": "sha256:ec4d00b58417c45f7ddcfde7bcad8c9d62a7d6d5d17cdc1f7d79bcb2e22c1491" } ] }`, fail: false, }, } { got := digest.FromString(tt.manifest) if tt.digest != got { t.Errorf("test %d: expected digest %s but got %s", i, tt.digest, got) } manifest := convertFormats(tt.manifest) r := strings.NewReader(manifest) err := schema.ValidatorMediaTypeManifest.Validate(r) if got := err != nil; tt.fail != got { t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) } } } func TestBackwardsCompatibilityConfig(t *testing.T) { for i, tt := range []struct { config string digest digest.Digest fail bool }{ // config pulled from docker hub blob store // // $ TOKEN=$(curl https://auth.docker.io/token\?service\=registry.docker.io\&scope\=repository:library/docker:pull | jq -r .token) // $ CONFIG_DIGEST=$(curl -H "Authorization: Bearer ${TOKEN}" -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' https://index.docker.io/v2/library/docker/manifests/1.12.1 | jq -r .config.digest) // $ curl -LH "Authorization: Bearer ${TOKEN}" https://index.docker.io/v2/library/docker/blobs/${CONFIG_DIGEST} { digest: "sha256:a059ea7356d5b5a9e0f6352bfa463e7bd4721c2ade3ef168603826e0de6fe54b", config: `{"architecture":"amd64","config":{"Hostname":"09713501c176","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","DOCKER_BUCKET=get.docker.com","DOCKER_VERSION=1.12.1","DOCKER_SHA256=05ceec7fd937e1416e5dce12b0b6e1c655907d349d52574319a1e875077ccb79"],"Cmd":["sh"],"Image":"sha256:32e2e3ccf2a4fbaa75b078bf539cd5ea2e374a4242665a5ec3f3c01e7a3eefb8","Volumes":null,"WorkingDir":"","Entrypoint":["docker-entrypoint.sh"],"OnBuild":[],"Labels":{}},"container":"15a30be053fb3069a7879b4ea537e84689d8e8e8ba94dc4dd499271506803ba1","container_config":{"Hostname":"09713501c176","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","DOCKER_BUCKET=get.docker.com","DOCKER_VERSION=1.12.1","DOCKER_SHA256=05ceec7fd937e1416e5dce12b0b6e1c655907d349d52574319a1e875077ccb79"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"sh\"]"],"Image":"sha256:32e2e3ccf2a4fbaa75b078bf539cd5ea2e374a4242665a5ec3f3c01e7a3eefb8","Volumes":null,"WorkingDir":"","Entrypoint":["docker-entrypoint.sh"],"OnBuild":[],"Labels":{}},"created":"2016-10-10T23:04:00.821781828Z","docker_version":"1.12.1","history":[{"created":"2016-09-23T16:29:57.276868291Z","created_by":"/bin/sh -c #(nop) ADD file:d6ee3ba7a4d59b161917082cc7242c660c61bb3f3cc1549c7e2dfff2b0de7104 in / "},{"created":"2016-09-23T16:36:54.024611637Z","created_by":"/bin/sh -c apk add --no-cache \t\tca-certificates \t\tcurl \t\topenssl"},{"created":"2016-09-23T16:36:54.365914519Z","created_by":"/bin/sh -c #(nop) ENV DOCKER_BUCKET=get.docker.com","empty_layer":true},{"created":"2016-09-23T16:36:54.662005049Z","created_by":"/bin/sh -c #(nop) ENV DOCKER_VERSION=1.12.1","empty_layer":true},{"created":"2016-09-23T16:36:54.946033025Z","created_by":"/bin/sh -c #(nop) ENV DOCKER_SHA256=05ceec7fd937e1416e5dce12b0b6e1c655907d349d52574319a1e875077ccb79","empty_layer":true},{"created":"2016-09-23T16:36:58.535084011Z","created_by":"/bin/sh -c set -x \t\u0026\u0026 curl -fSL \"https://${DOCKER_BUCKET}/builds/Linux/x86_64/docker-${DOCKER_VERSION}.tgz\" -o docker.tgz \t\u0026\u0026 echo \"${DOCKER_SHA256} *docker.tgz\" | sha256sum -c - \t\u0026\u0026 tar -xzvf docker.tgz \t\u0026\u0026 mv docker/* /usr/local/bin/ \t\u0026\u0026 rmdir docker \t\u0026\u0026 rm docker.tgz \t\u0026\u0026 docker -v"},{"created":"2016-10-10T23:04:00.334158993Z","created_by":"/bin/sh -c #(nop) COPY file:399605dc1850a60a586b5494ab538bad495fd6f94eabca0c5f8a26468ce6030f in /usr/local/bin/ "},{"created":"2016-10-10T23:04:00.577900192Z","created_by":"/bin/sh -c #(nop) ENTRYPOINT [\"docker-entrypoint.sh\"]","empty_layer":true},{"created":"2016-10-10T23:04:00.821781828Z","created_by":"/bin/sh -c #(nop) CMD [\"sh\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:9007f5987db353ec398a223bc5a135c5a9601798ba20a1abba537ea2f8ac765f","sha256:1b06990ff0df8dad281fad7e6e4c5e91f32f8f8c095d6c74cf1e90a6f4407e28","sha256:9d12251ce74aac7619a83641ab72431a8d82e58bcd8a262c2bb0cdb280f1f3b5","sha256:17a7f292c2427adfc75c3a789bab8efec925dc38c5437bf83d2f528013ab80e2"]}}`, fail: false, }, { // fedora:23 from docker hub // both Entrypoint and Cmd can be nullable digest: "sha256:a20665eb1fe2912accb3d5dadaed360430df0d1aa46874875886947d61d3d4ee", config: `{"architecture":"amd64","author":"Patrick Uiterwijk \u003cpatrick@puiterwijk.org\u003e","config":{"Hostname":"8dfe43d80430","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":null,"Image":"sha256:6986ae504bbf843512d680cc959484452034965db15f75ee8bdd1b107f61500b","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"container":"6249cd2c4b1d6b1bf05903364cbcb95781508994d6407c1564d494e748ea1b41","container_config":{"Hostname":"8dfe43d80430","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ADD file:293a6e463aa402bb8f80eb5cfc937f375cedc6843abaeb9eccfe3923bb3fc80b in /"],"Image":"sha256:6986ae504bbf843512d680cc959484452034965db15f75ee8bdd1b107f61500b","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2016-06-10T18:44:31.784795904Z","docker_version":"1.10.3","history":[{"created":"2016-06-10T18:44:03.360264073Z","author":"Patrick Uiterwijk \u003cpatrick@puiterwijk.org\u003e","created_by":"/bin/sh -c #(nop) MAINTAINER Patrick Uiterwijk \u003cpatrick@puiterwijk.org\u003e","empty_layer":true},{"created":"2016-06-10T18:44:31.784795904Z","author":"Patrick Uiterwijk \u003cpatrick@puiterwijk.org\u003e","created_by":"/bin/sh -c #(nop) ADD file:293a6e463aa402bb8f80eb5cfc937f375cedc6843abaeb9eccfe3923bb3fc80b in /"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:d43f38155a799dc53d8fbb9f3bc11f51805f4027cd5c3d10b9823201cd5b9400"]}}`, fail: false, }, } { got := digest.FromString(tt.config) if tt.digest != got { t.Errorf("test %d: expected digest %s but got %s", i, tt.digest, got) } config := convertFormats(tt.config) r := strings.NewReader(config) err := schema.ValidatorMediaTypeImageConfig.Validate(r) if got := err != nil; tt.fail != got { t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) } } } image-spec-1.1.0/schema/config-schema.json000066400000000000000000000057311455506543700204050ustar00rootroot00000000000000{ "description": "OpenContainer Config Specification", "$schema": "http://json-schema.org/draft-04/schema#", "id": "https://opencontainers.org/schema/image/config", "type": "object", "properties": { "created": { "type": "string", "format": "date-time" }, "author": { "type": "string" }, "architecture": { "type": "string" }, "variant": { "type": "string" }, "os": { "type": "string" }, "os.version": { "type": "string" }, "os.features": { "type": "array", "items": { "type": "string" } }, "config": { "type": "object", "properties": { "User": { "type": "string" }, "ExposedPorts": { "$ref": "defs.json#/definitions/mapStringObject" }, "Env": { "type": "array", "items": { "type": "string" } }, "Entrypoint": { "oneOf": [ { "type": "array", "items": { "type": "string" } }, { "type": "null" } ] }, "Cmd": { "oneOf": [ { "type": "array", "items": { "type": "string" } }, { "type": "null" } ] }, "Volumes": { "oneOf": [ { "$ref": "defs.json#/definitions/mapStringObject" }, { "type": "null" } ] }, "WorkingDir": { "type": "string" }, "Labels": { "oneOf": [ { "$ref": "defs.json#/definitions/mapStringString" }, { "type": "null" } ] }, "StopSignal": { "type": "string" }, "ArgsEscaped": { "type": "boolean" } } }, "rootfs": { "type": "object", "properties": { "diff_ids": { "type": "array", "items": { "type": "string" } }, "type": { "type": "string", "enum": [ "layers" ] } }, "required": [ "diff_ids", "type" ] }, "history": { "type": "array", "items": { "type": "object", "properties": { "created": { "type": "string", "format": "date-time" }, "author": { "type": "string" }, "created_by": { "type": "string" }, "comment": { "type": "string" }, "empty_layer": { "type": "boolean" } } } } }, "required": [ "architecture", "os", "rootfs" ] } image-spec-1.1.0/schema/config_test.go000066400000000000000000000132561455506543700176430ustar00rootroot00000000000000// Copyright 2016 The Linux Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package schema_test import ( "strings" "testing" "github.com/opencontainers/image-spec/schema" ) func TestConfig(t *testing.T) { for i, tt := range []struct { config string fail bool }{ // expected failure: field "os" has numeric value, must be string { config: ` { "architecture": "amd64", "os": 123, "rootfs": { "diff_ids": [ "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" ], "type": "layers" } } `, fail: true, }, // expected failure: field "variant" has numeric value, must be string { config: ` { "architecture": "arm64", "variant": 123, "os": "linux", "rootfs": { "diff_ids": [ "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" ], "type": "layers" } } `, fail: true, }, // expected failure: field "config.User" has numeric value, must be string { config: ` { "created": "2015-10-31T22:22:56.015925234Z", "author": "Alyssa P. Hacker ", "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": "arm64", "variant": "v8", "os": "linux", "config": { "User": "1:1", "ExposedPorts": { "8080/tcp": {} }, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "FOO=docker_is_a_really", "BAR=great_tool_you_know" ], "Entrypoint": [ "/bin/sh" ], "Cmd": [ "--foreground", "--config", "/etc/my-app.d/default.cfg" ], "Volumes": { "/var/job-result-data": {}, "/var/log/my-app-logs": {} }, "StopSignal": "SIGKILL", "WorkingDir": "/home/alice", "Labels": { "com.example.project.git.url": "https://example.com/project.git", "com.example.project.git.commit": "45a939b2999782a3f005621a8d0f29aa387e1d6b" } }, "rootfs": { "diff_ids": [ "sha256:9d3dd9504c685a304985025df4ed0283e47ac9ffa9bd0326fddf4d59513f0827", "sha256:2b689805fbd00b2db1df73fae47562faac1a626d5f61744bfe29946ecff5d73d" ], "type": "layers" }, "history": [ { "created": "2015-10-31T22:22:54.690851953Z", "created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /" }, { "created": "2015-10-31T22:22:55.613815829Z", "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]", "empty_layer": true } ] } `, fail: false, }, // valid config with only required fields { config: ` { "architecture": "amd64", "os": "linux", "rootfs": { "diff_ids": [ "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" ], "type": "layers" } } `, fail: false, }, // expected failure: Env is invalid { config: ` { "architecture": "amd64", "os": "linux", "config": { "Env": [ "foo" ] }, "rootfs": { "diff_ids": [ "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" ], "type": "layers" } } `, fail: true, }, } { r := strings.NewReader(tt.config) err := schema.ValidatorMediaTypeImageConfig.Validate(r) if got := err != nil; tt.fail != got { t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) } } } image-spec-1.1.0/schema/content-descriptor.json000066400000000000000000000025361455506543700215300ustar00rootroot00000000000000{ "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" }, "data": { "description": "an embedding of the targeted content (base64 encoded)", "$ref": "defs.json#/definitions/base64" }, "artifactType": { "description": "the IANA media type of this artifact", "$ref": "defs-descriptor.json#/definitions/mediaType" }, "annotations": { "id": "https://opencontainers.org/schema/descriptor/annotations", "$ref": "defs-descriptor.json#/definitions/annotations" } }, "required": [ "mediaType", "size", "digest" ] } image-spec-1.1.0/schema/defs-descriptor.json000066400000000000000000000015141455506543700207720ustar00rootroot00000000000000{ "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": { "$ref": "defs.json#/definitions/mapStringString" } } } image-spec-1.1.0/schema/defs.json000066400000000000000000000033611455506543700166200ustar00rootroot00000000000000{ "description": "Definitions used throughout the OpenContainer Specification", "definitions": { "int8": { "type": "integer", "minimum": -128, "maximum": 127 }, "int16": { "type": "integer", "minimum": -32768, "maximum": 32767 }, "int32": { "type": "integer", "minimum": -2147483648, "maximum": 2147483647 }, "int64": { "type": "integer", "minimum": -9223372036854776000, "maximum": 9223372036854776000 }, "uint8": { "type": "integer", "minimum": 0, "maximum": 255 }, "uint16": { "type": "integer", "minimum": 0, "maximum": 65535 }, "uint32": { "type": "integer", "minimum": 0, "maximum": 4294967295 }, "uint64": { "type": "integer", "minimum": 0, "maximum": 18446744073709552000 }, "uint16Pointer": { "oneOf": [ { "$ref": "#/definitions/uint16" }, { "type": "null" } ] }, "uint64Pointer": { "oneOf": [ { "$ref": "#/definitions/uint64" }, { "type": "null" } ] }, "base64": { "type": "string", "media": { "binaryEncoding": "base64" } }, "stringPointer": { "oneOf": [ { "type": "string" }, { "type": "null" } ] }, "mapStringString": { "type": "object", "patternProperties": { ".{1,}": { "type": "string" } } }, "mapStringObject": { "type": "object", "patternProperties": { ".{1,}": { "type": "object" } } } } } image-spec-1.1.0/schema/descriptor_test.go000066400000000000000000000213571455506543700205550ustar00rootroot00000000000000// Copyright 2016 The Linux Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package schema_test import ( "strings" "testing" "github.com/opencontainers/image-spec/schema" ) func TestDescriptor(t *testing.T) { for i, tt := range []struct { descriptor string fail bool }{ // valid descriptor { descriptor: ` { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" } `, fail: false, }, // expected failure: mediaType missing { descriptor: ` { "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" } `, fail: true, }, // expected failure: mediaType does not match pattern (no subtype) { descriptor: ` { "mediaType": "application", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" } `, fail: true, }, // expected failure: mediaType does not match pattern (invalid first type character) { descriptor: ` { "mediaType": ".foo/bar", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" } `, fail: true, }, // expected failure: mediaType does not match pattern (invalid first subtype character) { descriptor: ` { "mediaType": "foo/.bar", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" } `, fail: true, }, // expected success: mediaType has type and subtype as long as possible { descriptor: ` { "mediaType": "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567/1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" } `, fail: false, }, // expected failure: mediaType does not match pattern (type too long) { descriptor: ` { "mediaType": "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678/bar", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" } `, fail: true, }, // expected failure: mediaType does not match pattern (subtype too long) { descriptor: ` { "mediaType": "foo/12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" } `, fail: true, }, // expected failure: size missing { descriptor: ` { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" } `, fail: true, }, // expected failure: size is a string, expected integer { descriptor: ` { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": "7682", "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" } `, fail: true, }, // expected failure: digest missing { descriptor: ` { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682 } `, fail: true, }, // expected failure: digest does not match pattern (no algorithm) { descriptor: ` { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "digest": ":5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" } `, fail: true, }, // expected failure: digest does not match pattern (no hash) { descriptor: ` { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "digest": "sha256" } `, fail: true, }, // expected failure: digest does not match pattern (invalid aglorithm characters) { descriptor: ` { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "digest": "SHA256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" } `, fail: true, }, // expected failure: digest does not match pattern (characters needs to be lower for sha256) { descriptor: ` { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "digest": "sha256:5B0BCABD1ED22E9FB1310CF6C2DEC7CDEF19F0AD69EFA1F392E94A4333501270" } `, fail: true, }, // expected success: valid URL entry { descriptor: ` { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", "urls": [ "https://example.com/foo" ] } `, fail: false, }, // expected failure: urls does not match format (invalide url characters) { descriptor: ` { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", "urls": [ "value" ] } `, fail: true, }, // expected success: artifactType is present and an IANA compliant value { descriptor: ` { "mediaType": "application/vnd.oci.image.manifest.v1+json", "artifactType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" } `, fail: false, }, // expected failure: artifactType does not match pattern (invalid first subtype character) { descriptor: ` { "mediaType": "application/vnd.oci.image.manifest.v1+json", "artifactType": "foo/.bar", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" } `, fail: true, }, // expected success: data field is present and has base64 content { descriptor: ` { "mediaType": "text/plain", "size": 34, "data": "aHR0cHM6Ly9naXRodWIuY29tL29wZW5jb250YWluZXJzCg==", "digest": "sha256:2690af59371e9eca9453dc29882643f46e5ca47ec2862bd517b5e17351325153" } `, fail: false, }, { descriptor: `{ "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256+b64:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }`, }, { descriptor: `{ "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256+b64:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }`, }, { descriptor: `{ "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256+foo-bar:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }`, }, { descriptor: ` { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256.foo-bar:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }`, }, { descriptor: `{ "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "multihash+base58:QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8" }`, }, { // fail: repeated separators in algorithm descriptor: `{ "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256+foo+-b:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }`, fail: true, }, { descriptor: `{ "digest": "sha256+b64u:LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564", "size": 1000000, "mediaType": "application/vnd.oci.image.config.v1+json" }`, }, { // test for those who cannot use modulo arithmetic to recover padding. descriptor: `{ "digest": "sha256+b64u.unknownlength:LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564=", "size": 1000000, "mediaType": "application/vnd.oci.image.config.v1+json" }`, }, { descriptor: ` { "mediaType": "text/plain", "size": 34, "data": "aHR0cHM6Ly9naXRodWIuY29tL29wZW5jb250YWluZXJzCg", "digest": "sha256:2690af59371e9eca9453dc29882643f46e5ca47ec2862bd517b5e17351325153" } `, fail: true, }, } { r := strings.NewReader(tt.descriptor) err := schema.ValidatorMediaTypeDescriptor.Validate(r) if got := err != nil; tt.fail != got { t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) } } } image-spec-1.1.0/schema/doc.go000066400000000000000000000013071455506543700160760ustar00rootroot00000000000000// 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.1.0/schema/error.go000066400000000000000000000030561455506543700164650ustar00rootroot00000000000000// Copyright 2016 The Linux Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package schema import ( "bufio" "encoding/json" "errors" "io" ) // A SyntaxError is a description of a JSON syntax error // including line, column and offset in the JSON file. type SyntaxError struct { msg string Line, Col int Offset int64 } func (e *SyntaxError) Error() string { return e.msg } // WrapSyntaxError checks whether the given error is a *json.SyntaxError // and converts it into a *schema.SyntaxError containing line/col information using the given reader. // If the given error is not a *json.SyntaxError it is returned unchanged. func WrapSyntaxError(r io.Reader, err error) error { var serr *json.SyntaxError if errors.As(err, &serr) { buf := bufio.NewReader(r) line := 0 col := 0 for i := int64(0); i < serr.Offset; i++ { b, berr := buf.ReadByte() if berr != nil { break } if b == '\n' { line++ col = 1 } else { col++ } } return &SyntaxError{serr.Error(), line, col, serr.Offset} } return err } image-spec-1.1.0/schema/image-index-schema.json000066400000000000000000000064471455506543700213340ustar00rootroot00000000000000{ "description": "OpenContainer Image Index Specification", "$schema": "http://json-schema.org/draft-04/schema#", "id": "https://opencontainers.org/schema/image/index", "type": "object", "properties": { "schemaVersion": { "description": "This field specifies the image index schema version as an integer", "id": "https://opencontainers.org/schema/image/index/schemaVersion", "type": "integer", "minimum": 2, "maximum": 2 }, "mediaType": { "description": "the mediatype of the referenced object", "$ref": "defs-descriptor.json#/definitions/mediaType" }, "artifactType": { "description": "the artifact mediatype of the referenced object", "$ref": "defs-descriptor.json#/definitions/mediaType" }, "subject": { "$ref": "content-descriptor.json" }, "manifests": { "type": "array", "items": { "id": "https://opencontainers.org/schema/image/manifestDescriptor", "type": "object", "required": [ "mediaType", "size", "digest" ], "properties": { "mediaType": { "description": "the mediatype of the referenced object", "$ref": "defs-descriptor.json#/definitions/mediaType" }, "size": { "description": "the size in bytes of the referenced object", "$ref": "defs.json#/definitions/int64" }, "digest": { "description": "the cryptographic checksum digest of the object, in the pattern ':'", "$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.1.0/schema/image-layout-schema.json000066400000000000000000000006671455506543700215400ustar00rootroot00000000000000{ "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.1.0/schema/image-manifest-schema.json000066400000000000000000000024171455506543700220240ustar00rootroot00000000000000{ "description": "OpenContainer Image Manifest Specification", "$schema": "http://json-schema.org/draft-04/schema#", "id": "https://opencontainers.org/schema/image/manifest", "type": "object", "properties": { "schemaVersion": { "description": "This field specifies the image manifest schema version as an integer", "id": "https://opencontainers.org/schema/image/manifest/schemaVersion", "type": "integer", "minimum": 2, "maximum": 2 }, "mediaType": { "description": "the mediatype of the referenced object", "$ref": "defs-descriptor.json#/definitions/mediaType" }, "artifactType": { "description": "the artifact mediatype of the referenced object", "$ref": "defs-descriptor.json#/definitions/mediaType" }, "config": { "$ref": "content-descriptor.json" }, "subject": { "$ref": "content-descriptor.json" }, "layers": { "type": "array", "minItems": 1, "items": { "$ref": "content-descriptor.json" } }, "annotations": { "id": "https://opencontainers.org/schema/image/manifest/annotations", "$ref": "defs-descriptor.json#/definitions/annotations" } }, "required": [ "schemaVersion", "config", "layers" ] } image-spec-1.1.0/schema/imageindex_test.go000066400000000000000000000162201455506543700205020ustar00rootroot00000000000000// Copyright 2016 The Linux Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package schema_test import ( "strings" "testing" "github.com/opencontainers/image-spec/schema" ) func TestImageIndex(t *testing.T) { for i, tt := range []struct { imageIndex string fail bool }{ // expected failure: mediaType does not match pattern { imageIndex: ` { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "invalid", "size": 7143, "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", "platform": { "architecture": "ppc64le", "os": "linux" } } ] } `, fail: true, }, // expected failure: manifest.size is string, expected integer { imageIndex: ` { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": "7682", "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", "platform": { "architecture": "amd64", "os": "linux" } } ] } `, fail: true, }, // expected failure: manifest.digest is missing, expected required { imageIndex: ` { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "platform": { "architecture": "amd64", "os": "linux" } } ] } `, fail: true, }, // expected failure: in the optional field platform platform.architecture is missing, expected required { imageIndex: ` { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", "platform": { "os": "linux" } } ] } `, fail: true, }, // expected failure: invalid referenced manifest media type { imageIndex: ` { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "invalid", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", "platform": { "architecture": "amd64", "os": "linux" } } ] } `, fail: true, }, // expected failure: empty referenced manifest media type { imageIndex: ` { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", "platform": { "architecture": "amd64", "os": "linux" } } ] } `, fail: true, }, // valid image index, with optional fields { imageIndex: ` { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7143, "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", "platform": { "architecture": "ppc64le", "os": "linux" } }, { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", "platform": { "architecture": "amd64", "os": "linux" } } ], "annotations": { "com.example.key1": "value1", "com.example.key2": "value2" } } `, fail: false, }, // valid image index, with required fields only { imageIndex: ` { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7143, "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f" } ] } `, fail: false, }, // valid image index, with customized media type of referenced manifest { imageIndex: ` { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/customized.manifest+json", "size": 7143, "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", "platform": { "architecture": "ppc64le", "os": "linux" } } ] } `, fail: false, }, // valid image index with artifactType and manifests { imageIndex: ` { "schemaVersion": 2, "mediaType" : "application/vnd.oci.image.index.v1+json", "artifactType": "application/vnd.example+type", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7143, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" }, { "mediaType": "application/vnd.oci.image.manifest.v1+json", "artifactType": "application/vnd.example1+type", "size": 506, "digest": "sha256:99953afc4b90c7d78079d189ae10da0a1002e6be5e9e8dedaf9f7f29def42111" } ] } `, fail: false, }, // valid image index with a subject field { imageIndex: ` { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", "platform": { "architecture": "amd64", "os": "linux" } } ], "subject" : { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 1234, "digest": "sha256:220a60ecd4a3c32c282622a625a54db9ba0ff55b5ba9c29c7064a2bc358b6a3e" } } `, fail: false, }, // expected failure, invalid subject field { imageIndex: ` { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", "platform": { "architecture": "amd64", "os": "linux" } } ], "subject" : "nope" } `, fail: true, }, } { r := strings.NewReader(tt.imageIndex) err := schema.ValidatorMediaTypeImageIndex.Validate(r) if got := err != nil; tt.fail != got { t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) } } } image-spec-1.1.0/schema/imagelayout_test.go000066400000000000000000000024421455506543700207110ustar00rootroot00000000000000// 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.1.0/schema/loader.go000066400000000000000000000070041455506543700165770ustar00rootroot00000000000000// Copyright 2018 The Linux Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package schema import ( "bytes" "encoding/json" "fmt" "io" "net/http" "strings" "github.com/xeipuuv/gojsonreference" "github.com/xeipuuv/gojsonschema" ) // fsLoaderFactory implements gojsonschema.JSONLoaderFactory by reading files under the specified namespaces from the root of fs. type fsLoaderFactory struct { namespaces []string fs http.FileSystem } // newFSLoaderFactory returns a fsLoaderFactory reading files under the specified namespaces from the root of fs. func newFSLoaderFactory(namespaces []string, fs http.FileSystem) *fsLoaderFactory { return &fsLoaderFactory{ namespaces: namespaces, fs: fs, } } func (factory *fsLoaderFactory) New(source string) gojsonschema.JSONLoader { return &fsLoader{ factory: factory, source: source, } } // refContents returns the contents of ref, if available in fsLoaderFactory. func (factory *fsLoaderFactory) refContents(ref gojsonreference.JsonReference) ([]byte, error) { refStr := ref.String() path := "" for _, ns := range factory.namespaces { if strings.HasPrefix(refStr, ns) { path = "/" + strings.TrimPrefix(refStr, ns) break } } if path == "" { return nil, fmt.Errorf("schema reference %#v unexpectedly not available in fsLoaderFactory with namespaces %#v", path, factory.namespaces) } f, err := factory.fs.Open(path) if err != nil { return nil, err } defer f.Close() return io.ReadAll(f) } // fsLoader implements gojsonschema.JSONLoader by reading the document named by source from a fsLoaderFactory. type fsLoader struct { factory *fsLoaderFactory source string } // JsonSource implements gojsonschema.JSONLoader.JsonSource. The "Json" capitalization needs to be maintained to conform to the interface. func (l *fsLoader) JsonSource() interface{} { // revive:disable-line:var-naming return l.source } func (l *fsLoader) LoadJSON() (interface{}, error) { // Based on gojsonschema.jsonReferenceLoader.LoadJSON. reference, err := gojsonreference.NewJsonReference(l.source) if err != nil { return nil, err } refToURL := reference refToURL.GetUrl().Fragment = "" body, err := l.factory.refContents(refToURL) if err != nil { return nil, err } return decodeJSONUsingNumber(bytes.NewReader(body)) } // decodeJSONUsingNumber returns JSON parsed from an io.Reader func decodeJSONUsingNumber(r io.Reader) (interface{}, error) { // Copied from gojsonschema. var document interface{} decoder := json.NewDecoder(r) decoder.UseNumber() err := decoder.Decode(&document) if err != nil { return nil, err } return document, nil } // JsonReference implements gojsonschema.JSONLoader.JsonReference. The "Json" capitalization needs to be maintained to conform to the interface. func (l *fsLoader) JsonReference() (gojsonreference.JsonReference, error) { // revive:disable-line:var-naming return gojsonreference.NewJsonReference(l.JsonSource().(string)) } func (l *fsLoader) LoaderFactory() gojsonschema.JSONLoaderFactory { return l.factory } image-spec-1.1.0/schema/manifest_test.go000066400000000000000000000221361455506543700202010ustar00rootroot00000000000000// Copyright 2016 The Linux Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package schema_test import ( "strings" "testing" "github.com/opencontainers/image-spec/schema" ) func TestManifest(t *testing.T) { for i, tt := range []struct { manifest string fail bool }{ // expected failure: mediaType does not match pattern { manifest: ` { "schemaVersion": 2, "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "invalid", "size": 1470, "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 148, "digest": "sha256:c57089565e894899735d458f0fd4bb17a0f1e0df8d72da392b85c9b35ee777cd" } ] } `, fail: true, }, // expected failure: config.size is a string, expected integer { manifest: ` { "schemaVersion": 2, "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": "1470", "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 148, "digest": "sha256:c57089565e894899735d458f0fd4bb17a0f1e0df8d72da392b85c9b35ee777cd" } ] } `, fail: true, }, // expected failure: layers.size is string, expected integer { manifest: ` { "schemaVersion": 2, "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": "675598", "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" } ] } `, fail: true, }, // valid manifest with optional fields { manifest: ` { "schemaVersion": 2, "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 675598, "digest": "sha256:9d3dd9504c685a304985025df4ed0283e47ac9ffa9bd0326fddf4d59513f0827" }, { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 156, "digest": "sha256:2b689805fbd00b2db1df73fae47562faac1a626d5f61744bfe29946ecff5d73d" }, { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 148, "digest": "sha256:c57089565e894899735d458f0fd4bb17a0f1e0df8d72da392b85c9b35ee777cd" } ], "annotations": { "key1": "value1", "key2": "value2" } } `, fail: false, }, // valid manifest with only required fields { manifest: ` { "schemaVersion": 2, "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 675598, "digest": "sha256:9d3dd9504c685a304985025df4ed0283e47ac9ffa9bd0326fddf4d59513f0827" }, { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 156, "digest": "sha256:2b689805fbd00b2db1df73fae47562faac1a626d5f61744bfe29946ecff5d73d" }, { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 148, "digest": "sha256:c57089565e894899735d458f0fd4bb17a0f1e0df8d72da392b85c9b35ee777cd" } ] } `, fail: false, }, // expected failure: empty layer, expected at least one { manifest: ` { "schemaVersion": 2, "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, "layers": [] } `, fail: true, }, // expected pass: test bounds of algorithm field in digest. { manifest: ` { "schemaVersion": 2, "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256+b64:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 1470, "digest": "sha256+foo-bar:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 1470, "digest": "sha256.foo-bar:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 1470, "digest": "multihash+base58:QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8" } ] } `, }, // expected success: subject field with a valid descriptor { manifest: ` { "schemaVersion": 2, "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 1470, "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" } ], "subject" : { "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 1234, "digest": "sha256:220a60ecd4a3c32c282622a625a54db9ba0ff55b5ba9c29c7064a2bc358b6a3e" } } `, fail: false, }, // expected failure: subject field with invalid value (something that is not a descriptor) { manifest: ` { "schemaVersion": 2, "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 1470, "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" } ], "subject" : ".nope" } `, fail: true, }, // expected failure: push bounds of algorithm field in digest too far. { manifest: ` { "schemaVersion": 2, "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, "digest": "sha256+b64:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 1470, "digest": "sha256+foo+-b:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" } ] } `, fail: true, }, // valid manifest for an artifact with a dedicated config { manifest: ` { "schemaVersion": 2, "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.example.config+json", "size": 1470, "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, "layers": [ { "mediaType": "application/vnd.example.data+type", "size": 675598, "digest": "sha256:9d3dd9504c685a304985025df4ed0283e47ac9ffa9bd0326fddf4d59513f0827" } ] } `, fail: false, }, // valid manifest for an artifact using the empty config and artifactType { manifest: ` { "schemaVersion": 2, "mediaType" : "application/vnd.oci.image.manifest.v1+json", "artifactType": "application/vnd.example+type", "config": { "mediaType": "application/vnd.oci.empty.v1+json", "size": 2, "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" }, "layers": [ { "mediaType": "application/vnd.example+type", "size": 675598, "digest": "sha256:9d3dd9504c685a304985025df4ed0283e47ac9ffa9bd0326fddf4d59513f0827" } ] } `, fail: false, }, } { r := strings.NewReader(tt.manifest) err := schema.ValidatorMediaTypeManifest.Validate(r) if got := err != nil; tt.fail != got { t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) } } } image-spec-1.1.0/schema/schema.go000066400000000000000000000066411455506543700165770ustar00rootroot00000000000000// Copyright 2016 The Linux Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package schema import ( "embed" "net/http" v1 "github.com/opencontainers/image-spec/specs-go/v1" ) // Media types for the OCI image formats const ( ValidatorMediaTypeDescriptor Validator = v1.MediaTypeDescriptor ValidatorMediaTypeLayoutHeader Validator = v1.MediaTypeLayoutHeader ValidatorMediaTypeManifest Validator = v1.MediaTypeImageManifest ValidatorMediaTypeImageIndex Validator = v1.MediaTypeImageIndex ValidatorMediaTypeImageConfig Validator = v1.MediaTypeImageConfig ValidatorMediaTypeImageLayer unimplemented = v1.MediaTypeImageLayer ) var ( // fs stores the embedded http.FileSystem // having the OCI JSON schema files in root "/". //go:embed *.json fs embed.FS // schemaNamespaces is a set of URI prefixes which are treated as containing the schema files of fs. // This is necessary because *.json schema files in this directory use "id" and "$ref" attributes which evaluate to such URIs, e.g. // ./image-manifest-schema.json URI contains // "id": "https://opencontainers.org/schema/image/manifest", // and // "$ref": "content-descriptor.json" // which evaluates as a link to https://opencontainers.org/schema/image/content-descriptor.json . // // To support such links without accessing the network (and trying to load content which is not hosted at these URIs), // fsLoaderFactory accepts any URI starting with one of the schemaNamespaces below, // and uses _escFS to load them from the root of its in-memory filesystem tree. // // (Note that this must contain subdirectories before its parent directories for fsLoaderFactory.refContents to work.) schemaNamespaces = []string{ "https://opencontainers.org/schema/image/descriptor/", "https://opencontainers.org/schema/image/index/", "https://opencontainers.org/schema/image/manifest/", "https://opencontainers.org/schema/image/", "https://opencontainers.org/schema/descriptor/", "https://opencontainers.org/schema/", } // specs maps OCI schema media types to schema URIs. // These URIs are expected to be used only by fsLoaderFactory (which trims schemaNamespaces defined above) // and should never cause a network access. specs = map[Validator]string{ ValidatorMediaTypeDescriptor: "https://opencontainers.org/schema/content-descriptor.json", ValidatorMediaTypeLayoutHeader: "https://opencontainers.org/schema/image/image-layout-schema.json", ValidatorMediaTypeManifest: "https://opencontainers.org/schema/image/image-manifest-schema.json", ValidatorMediaTypeImageIndex: "https://opencontainers.org/schema/image/image-index-schema.json", ValidatorMediaTypeImageConfig: "https://opencontainers.org/schema/image/config-schema.json", } ) // FileSystem returns an in-memory filesystem including the schema files. // The schema files are located at the root directory. func FileSystem() http.FileSystem { return http.FS(fs) } image-spec-1.1.0/schema/spec_test.go000066400000000000000000000120661455506543700173260ustar00rootroot00000000000000// 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" "errors" "fmt" "io" "net/url" "os" "path/filepath" "reflect" "strings" "testing" "github.com/opencontainers/image-spec/schema" "github.com/russross/blackfriday" ) var ( errFormatInvalid = errors.New("format: invalid") ) func TestValidateDescriptor(t *testing.T) { validate(t, "../descriptor.md") } func TestValidateManifest(t *testing.T) { validate(t, "../manifest.md") } func TestValidateImageIndex(t *testing.T) { validate(t, "../image-index.md") } func TestValidateImageLayout(t *testing.T) { validate(t, "../image-layout.md") } func TestValidateConfig(t *testing.T) { validate(t, "../config.md") } func TestSchemaFS(t *testing.T) { expectedSchemaFileNames, err := filepath.Glob("*.json") if err != nil { t.Error(err) } dir, err := schema.FileSystem().Open("/") if err != nil { t.Fatal(err) } files, err := dir.Readdir(-1) if err != nil { t.Fatal(err) } var schemaFileNames []string for _, f := range files { schemaFileNames = append(schemaFileNames, f.Name()) } if !reflect.DeepEqual(schemaFileNames, expectedSchemaFileNames) { t.Fatalf("got %v, expected %v", schemaFileNames, expectedSchemaFileNames) } } // TODO(sur): include examples from all specification files func validate(t *testing.T, name string) { m, err := os.Open(name) if err != nil { t.Fatal(err) } defer m.Close() examples, err := extractExamples(m) if err != nil { t.Fatal(err) } for _, example := range examples { if errors.Is(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 var verr schema.ValidationError if errors.As(err, &verr) { errs = verr.Errs } else { printFields(t, "error", example.Mediatype, example.Title, err) t.Error(err) t.Log(example.Body, "---") continue } for _, err := range errs { printFields(t, "invalid", example.Mediatype, example.Title) t.Error(err) fmt.Println(example.Body, "---") continue } } } // renderer allows one to incercept fenced blocks in markdown documents. type renderer struct { blackfriday.Renderer fn func(text []byte, lang string) } func (r *renderer) BlockCode(out *bytes.Buffer, text []byte, lang string) { r.fn(text, lang) r.Renderer.BlockCode(out, text, lang) } type example struct { Lang string // gets raw "lang" field Title string Mediatype string Body string Err error // TODO(stevvooe): Figure out how to keep track of revision, file, line so // that we can trace back verification output. } // parseExample treats the field as a syntax,attribute tuple separated by a comma. // Attributes are encoded as a url values. // // An example of this is `json,title=Foo%20Bar&mediatype=application/json. We // get that the "lang" is json, the title is "Foo Bar" and the mediatype is // "application/json". // // This preserves syntax highlighting and lets us tag examples with further // metadata. func parseExample(lang, body string) (e example) { e.Lang = lang e.Body = body parts := strings.SplitN(lang, ",", 2) if len(parts) < 2 { e.Err = errFormatInvalid return } m, err := url.ParseQuery(parts[1]) if err != nil { e.Err = err return } e.Mediatype = m.Get("mediatype") e.Title = m.Get("title") return } func extractExamples(rd io.Reader) ([]example, error) { p, err := io.ReadAll(rd) if err != nil { return nil, err } var examples []example renderer := &renderer{ Renderer: blackfriday.HtmlRenderer(0, "test test", ""), fn: func(text []byte, lang string) { examples = append(examples, parseExample(lang, string(text))) }, } // just pass over the markdown and ignore the rendered result. We just want // the side-effect of calling back for each code block. // TODO(stevvooe): Consider just parsing these with a scanner. It will be // faster and we can retain file, line no. blackfriday.MarkdownOptions(p, renderer, blackfriday.Options{ Extensions: blackfriday.EXTENSION_FENCED_CODE, }) return examples, nil } // printFields prints each value tab separated. func printFields(t *testing.T, vs ...interface{}) { var ss []string for _, f := range vs { ss = append(ss, fmt.Sprint(f)) } t.Log(strings.Join(ss, "\t")) } image-spec-1.1.0/schema/validator.go000066400000000000000000000154611455506543700173240ustar00rootroot00000000000000// 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" "errors" "fmt" "io" "regexp" digest "github.com/opencontainers/go-digest" v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/xeipuuv/gojsonschema" ) // Validator wraps a media type string identifier // and implements validation against a JSON schema. type Validator string type validateFunc func(r io.Reader) error var mapValidate = map[Validator]validateFunc{ ValidatorMediaTypeImageConfig: validateConfig, ValidatorMediaTypeDescriptor: validateDescriptor, ValidatorMediaTypeImageIndex: validateIndex, ValidatorMediaTypeManifest: validateManifest, } // ValidationError contains all the errors that happened during validation. type ValidationError struct { Errs []error } func (e ValidationError) Error() string { return fmt.Sprintf("%v", e.Errs) } // Validate validates the given reader against the schema of the wrapped media type. func (v Validator) Validate(src io.Reader) error { buf, err := io.ReadAll(src) if err != nil { return fmt.Errorf("unable to read the document file: %w", err) } if f, ok := mapValidate[v]; ok { if f == nil { return fmt.Errorf("internal error: mapValidate[%q] is nil", v) } err = f(bytes.NewReader(buf)) if err != nil { return err } } sl := newFSLoaderFactory(schemaNamespaces, FileSystem()).New(specs[v]) ml := gojsonschema.NewStringLoader(string(buf)) result, err := gojsonschema.Validate(sl, ml) if err != nil { return fmt.Errorf("schema %s: unable to validate: %w", v, WrapSyntaxError(bytes.NewReader(buf), err)) } if result.Valid() { return nil } errs := make([]error, 0, len(result.Errors())) for _, desc := range result.Errors() { errs = append(errs, fmt.Errorf("%s", desc)) } return ValidationError{ Errs: errs, } } type unimplemented string func (v unimplemented) Validate(_ io.Reader) error { return fmt.Errorf("%s: unimplemented", v) } func validateManifest(r io.Reader) error { header := v1.Manifest{} buf, err := io.ReadAll(r) if err != nil { return fmt.Errorf("error reading the io stream: %w", err) } err = json.Unmarshal(buf, &header) if err != nil { return fmt.Errorf("manifest format mismatch: %w", err) } if header.Config.MediaType != string(v1.MediaTypeImageConfig) { fmt.Printf("warning: config %s has an unknown media type: %s\n", header.Config.Digest, header.Config.MediaType) } for _, layer := range header.Layers { if layer.MediaType != string(v1.MediaTypeImageLayer) && layer.MediaType != string(v1.MediaTypeImageLayerGzip) && layer.MediaType != string(v1.MediaTypeImageLayerZstd) && layer.MediaType != string(v1.MediaTypeImageLayerNonDistributable) && //nolint:staticcheck layer.MediaType != string(v1.MediaTypeImageLayerNonDistributableGzip) && //nolint:staticcheck layer.MediaType != string(v1.MediaTypeImageLayerNonDistributableZstd) { //nolint:staticcheck fmt.Printf("warning: layer %s has an unknown media type: %s\n", layer.Digest, layer.MediaType) } } return nil } func validateDescriptor(r io.Reader) error { header := v1.Descriptor{} buf, err := io.ReadAll(r) if err != nil { return fmt.Errorf("error reading the io stream: %w", err) } err = json.Unmarshal(buf, &header) if err != nil { return fmt.Errorf("descriptor format mismatch: %w", err) } err = header.Digest.Validate() if errors.Is(err, digest.ErrDigestUnsupported) { // we ignore unsupported algorithms fmt.Printf("warning: unsupported digest: %q: %v\n", header.Digest, err) return nil } return err } func validateIndex(r io.Reader) error { header := v1.Index{} buf, err := io.ReadAll(r) if err != nil { return fmt.Errorf("error reading the io stream: %w", err) } err = json.Unmarshal(buf, &header) if err != nil { return fmt.Errorf("index format mismatch: %w", err) } for _, manifest := range header.Manifests { if manifest.MediaType != string(v1.MediaTypeImageManifest) { fmt.Printf("warning: manifest %s has an unknown media type: %s\n", manifest.Digest, manifest.MediaType) } if manifest.Platform != nil { checkPlatform(manifest.Platform.OS, manifest.Platform.Architecture) checkArchitecture(manifest.Platform.Architecture, manifest.Platform.Variant) } } return nil } func validateConfig(r io.Reader) error { header := v1.Image{} buf, err := io.ReadAll(r) if err != nil { return fmt.Errorf("error reading the io stream: %w", err) } err = json.Unmarshal(buf, &header) if err != nil { return fmt.Errorf("config format mismatch: %w", err) } checkPlatform(header.OS, header.Architecture) checkArchitecture(header.Architecture, header.Variant) envRegexp := regexp.MustCompile(`^[^=]+=.*$`) for _, e := range header.Config.Env { if !envRegexp.MatchString(e) { return fmt.Errorf("unexpected env: %q", e) } } return nil } func checkArchitecture(Architecture string, Variant string) { validCombins := map[string][]string{ "arm": {"", "v6", "v7", "v8"}, "arm64": {"", "v8"}, "386": {""}, "amd64": {""}, "ppc64": {""}, "ppc64le": {""}, "mips64": {""}, "mips64le": {""}, "s390x": {""}, "riscv64": {""}, } for arch, variants := range validCombins { if arch == Architecture { for _, variant := range variants { if variant == Variant { return } } fmt.Printf("warning: combination of architecture %q and variant %q is not valid.\n", Architecture, Variant) } } fmt.Printf("warning: architecture %q is not supported yet.\n", Architecture) } func checkPlatform(OS string, Architecture string) { validCombins := map[string][]string{ "android": {"arm"}, "darwin": {"386", "amd64", "arm", "arm64"}, "dragonfly": {"amd64"}, "freebsd": {"386", "amd64", "arm"}, "linux": {"386", "amd64", "arm", "arm64", "ppc64", "ppc64le", "mips64", "mips64le", "s390x", "riscv64"}, "netbsd": {"386", "amd64", "arm"}, "openbsd": {"386", "amd64", "arm"}, "plan9": {"386", "amd64"}, "solaris": {"amd64"}, "windows": {"386", "amd64"}} for os, archs := range validCombins { if os == OS { for _, arch := range archs { if arch == Architecture { return } } fmt.Printf("warning: combination of os %q and architecture %q is invalid.\n", OS, Architecture) } } fmt.Printf("warning: operating system %q of the bundle is not supported yet.\n", OS) } image-spec-1.1.0/spec.md000066400000000000000000000102311455506543700150120ustar00rootroot00000000000000# Open Container Initiative ## Image Format Specification This specification defines an OCI Image, consisting of an [image manifest](manifest.md), an [image index](image-index.md) (optional), a set of [filesystem layers](layer.md), and a [configuration](config.md). The goal of this specification is to enable the creation of interoperable tools for building, transporting, and preparing a container image to run. ### Table of Contents - [Notational Conventions](#notational-conventions) - [Overview](#overview) - [Understanding the Specification](#understanding-the-specification) - [Media Types](media-types.md) - [Content Descriptors](descriptor.md) - [Image Layout](image-layout.md) - [Image Manifest](manifest.md) - [Image Index](image-index.md) - [Filesystem Layers](layer.md) - [Image Configuration](config.md) - [Annotations](annotations.md) - [Conversion](conversion.md) - [Considerations](considerations.md) - [Extensibility](considerations.md#extensibility) - [Canonicalization](considerations.md#canonicalization) ## Notational Conventions The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" are to be interpreted as described in [RFC 2119](https://tools.ietf.org/html/rfc2119) (Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997). The key words "unspecified", "undefined", and "implementation-defined" are to be interpreted as described in the [rationale for the C99 standard][c99-unspecified]. An implementation is not compliant if it fails to satisfy one or more of the MUST, MUST NOT, REQUIRED, SHALL, or SHALL NOT requirements for the protocols it implements. An implementation is compliant if it satisfies all the MUST, MUST NOT, REQUIRED, SHALL, and SHALL NOT requirements for the protocols it implements. ## Overview At a high level the image manifest contains metadata about the contents and dependencies of the image including the content-addressable identity of one or more [filesystem layer changeset](layer.md) archives that will be unpacked to make up the final runnable filesystem. The image configuration includes information such as application arguments, environments, etc. The image index is a higher-level manifest which points to a list of manifests and descriptors. Typically, these manifests may provide different implementations of the image, possibly varying by platform or other attributes. ![build diagram](img/build-diagram.png) Once built the OCI Image can then be discovered by name, downloaded, verified by hash, trusted through a signature, and unpacked into an [OCI Runtime Bundle](https://github.com/opencontainers/runtime-spec/blob/master/bundle.md). ![runtime diagram](img/run-diagram.png) ### Understanding the Specification The [OCI Image Media Types](media-types.md) document is a starting point to understanding the overall structure of the specification. The high-level components of the spec include: - [Image Manifest](manifest.md) - a document describing the components that make up a container image - [Image Index](image-index.md) - an annotated list of manifests - [Image Layout](image-layout.md) - a filesystem layout representing the contents of an image - [Filesystem Layer](layer.md) - a changeset that describes a container's filesystem - [Image Configuration](config.md) - a document determining layer ordering and configuration of the image suitable for translation into a [runtime bundle][runtime-spec] - [Conversion](conversion.md) - a document describing how this translation should occur - [Artifacts Guidance](artifacts-guidance.md) - a document describing how to use the spec for packaging content other than OCI images - [Descriptor](descriptor.md) - a reference that describes the type, metadata and content address of referenced content Future versions of this specification may include the following OPTIONAL features: - Signatures that are based on signing image content address - Naming that is federated based on DNS and can be delegated [c99-unspecified]: https://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf#page=18 [runtime-spec]: https://github.com/opencontainers/runtime-spec image-spec-1.1.0/specs-go/000077500000000000000000000000001455506543700152615ustar00rootroot00000000000000image-spec-1.1.0/specs-go/v1/000077500000000000000000000000001455506543700156075ustar00rootroot00000000000000image-spec-1.1.0/specs-go/v1/annotations.go000066400000000000000000000061511455506543700204760ustar00rootroot00000000000000// Copyright 2016 The Linux Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package v1 const ( // AnnotationCreated is the annotation key for the date and time on which the image was built (date-time string as defined by RFC 3339). AnnotationCreated = "org.opencontainers.image.created" // AnnotationAuthors is the annotation key for the contact details of the people or organization responsible for the image (freeform string). AnnotationAuthors = "org.opencontainers.image.authors" // AnnotationURL is the annotation key for the URL to find more information on the image. AnnotationURL = "org.opencontainers.image.url" // AnnotationDocumentation is the annotation key for the URL to get documentation on the image. AnnotationDocumentation = "org.opencontainers.image.documentation" // AnnotationSource is the annotation key for the URL to get source code for building the image. AnnotationSource = "org.opencontainers.image.source" // AnnotationVersion is the annotation key for the version of the packaged software. // The version MAY match a label or tag in the source code repository. // The version MAY be Semantic versioning-compatible. AnnotationVersion = "org.opencontainers.image.version" // AnnotationRevision is the annotation key for the source control revision identifier for the packaged software. AnnotationRevision = "org.opencontainers.image.revision" // AnnotationVendor is the annotation key for the name of the distributing entity, organization or individual. AnnotationVendor = "org.opencontainers.image.vendor" // AnnotationLicenses is the annotation key for the license(s) under which contained software is distributed as an SPDX License Expression. AnnotationLicenses = "org.opencontainers.image.licenses" // AnnotationRefName is the annotation key for the name of the reference for a target. // SHOULD only be considered valid when on descriptors on `index.json` within image layout. AnnotationRefName = "org.opencontainers.image.ref.name" // AnnotationTitle is the annotation key for the human-readable title of the image. AnnotationTitle = "org.opencontainers.image.title" // AnnotationDescription is the annotation key for the human-readable description of the software packaged in the image. AnnotationDescription = "org.opencontainers.image.description" // AnnotationBaseImageDigest is the annotation key for the digest of the image's base image. AnnotationBaseImageDigest = "org.opencontainers.image.base.digest" // AnnotationBaseImageName is the annotation key for the image reference of the image's base image. AnnotationBaseImageName = "org.opencontainers.image.base.name" ) image-spec-1.1.0/specs-go/v1/config.go000066400000000000000000000107041455506543700174050ustar00rootroot00000000000000// Copyright 2016 The Linux Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package v1 import ( "time" digest "github.com/opencontainers/go-digest" ) // ImageConfig defines the execution parameters which should be used as a base when running a container using an image. type ImageConfig struct { // User defines the username or UID which the process in the container should run as. User string `json:"User,omitempty"` // ExposedPorts a set of ports to expose from a container running this image. ExposedPorts map[string]struct{} `json:"ExposedPorts,omitempty"` // Env is a list of environment variables to be used in a container. Env []string `json:"Env,omitempty"` // Entrypoint defines a list of arguments to use as the command to execute when the container starts. Entrypoint []string `json:"Entrypoint,omitempty"` // Cmd defines the default arguments to the entrypoint of the container. Cmd []string `json:"Cmd,omitempty"` // Volumes is a set of directories describing where the process is likely write data specific to a container instance. Volumes map[string]struct{} `json:"Volumes,omitempty"` // WorkingDir sets the current working directory of the entrypoint process in the container. WorkingDir string `json:"WorkingDir,omitempty"` // Labels contains arbitrary metadata for the container. Labels map[string]string `json:"Labels,omitempty"` // StopSignal contains the system call signal that will be sent to the container to exit. StopSignal string `json:"StopSignal,omitempty"` // ArgsEscaped // // Deprecated: This field is present only for legacy compatibility with // Docker and should not be used by new image builders. It is used by Docker // for Windows images to indicate that the `Entrypoint` or `Cmd` or both, // contains only a single element array, that is a pre-escaped, and combined // into a single string `CommandLine`. If `true` the value in `Entrypoint` or // `Cmd` should be used as-is to avoid double escaping. // https://github.com/opencontainers/image-spec/pull/892 ArgsEscaped bool `json:"ArgsEscaped,omitempty"` } // RootFS describes a layer content addresses type RootFS struct { // Type is the type of the rootfs. Type string `json:"type"` // DiffIDs is an array of layer content hashes (DiffIDs), in order from bottom-most to top-most. DiffIDs []digest.Digest `json:"diff_ids"` } // History describes the history of a layer. type History struct { // Created is the combined date and time at which the layer was created, formatted as defined by RFC 3339, section 5.6. Created *time.Time `json:"created,omitempty"` // CreatedBy is the command which created the layer. CreatedBy string `json:"created_by,omitempty"` // Author is the author of the build point. Author string `json:"author,omitempty"` // Comment is a custom message set when creating the layer. Comment string `json:"comment,omitempty"` // EmptyLayer is used to mark if the history item created a filesystem diff. EmptyLayer bool `json:"empty_layer,omitempty"` } // Image is the JSON structure which describes some basic information about the image. // This provides the `application/vnd.oci.image.config.v1+json` mediatype when marshalled to JSON. type Image struct { // Created is the combined date and time at which the image was created, formatted as defined by RFC 3339, section 5.6. Created *time.Time `json:"created,omitempty"` // Author defines the name and/or email address of the person or entity which created and is responsible for maintaining the image. Author string `json:"author,omitempty"` // Platform describes the platform which the image in the manifest runs on. Platform // Config defines the execution parameters which should be used as a base when running a container using the image. Config ImageConfig `json:"config,omitempty"` // RootFS references the layer content addresses used by the image. RootFS RootFS `json:"rootfs"` // History describes the history of each layer. History []History `json:"history,omitempty"` } image-spec-1.1.0/specs-go/v1/descriptor.go000066400000000000000000000060461455506543700203220ustar00rootroot00000000000000// Copyright 2016-2022 The Linux Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package v1 import digest "github.com/opencontainers/go-digest" // Descriptor describes the disposition of targeted content. // This structure provides `application/vnd.oci.descriptor.v1+json` mediatype // when marshalled to JSON. type Descriptor struct { // MediaType is the media type of the object this schema refers to. MediaType string `json:"mediaType"` // Digest is the digest of the targeted content. Digest digest.Digest `json:"digest"` // Size specifies the size in bytes of the blob. Size int64 `json:"size"` // URLs specifies a list of URLs from which this object MAY be downloaded URLs []string `json:"urls,omitempty"` // Annotations contains arbitrary metadata relating to the targeted content. Annotations map[string]string `json:"annotations,omitempty"` // Data is an embedding of the targeted content. This is encoded as a base64 // string when marshalled to JSON (automatically, by encoding/json). If // present, Data can be used directly to avoid fetching the targeted content. Data []byte `json:"data,omitempty"` // Platform describes the platform which the image in the manifest runs on. // // This should only be used when referring to a manifest. Platform *Platform `json:"platform,omitempty"` // ArtifactType is the IANA media type of this artifact. ArtifactType string `json:"artifactType,omitempty"` } // Platform describes the platform which the image in the manifest runs on. type Platform struct { // Architecture field specifies the CPU architecture, for example // `amd64` or `ppc64le`. Architecture string `json:"architecture"` // OS specifies the operating system, for example `linux` or `windows`. OS string `json:"os"` // OSVersion is an optional field specifying the operating system // version, for example on Windows `10.0.14393.1066`. OSVersion string `json:"os.version,omitempty"` // OSFeatures is an optional field specifying an array of strings, // each listing a required OS feature (for example on Windows `win32k`). OSFeatures []string `json:"os.features,omitempty"` // Variant is an optional field specifying a variant of the CPU, for // example `v7` to specify ARMv7 when architecture is `arm`. Variant string `json:"variant,omitempty"` } // DescriptorEmptyJSON is the descriptor of a blob with content of `{}`. var DescriptorEmptyJSON = Descriptor{ MediaType: MediaTypeEmptyJSON, Digest: `sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a`, Size: 2, Data: []byte(`{}`), } image-spec-1.1.0/specs-go/v1/index.go000066400000000000000000000031031455506543700172420ustar00rootroot00000000000000// Copyright 2016 The Linux Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package v1 import "github.com/opencontainers/image-spec/specs-go" // Index references manifests for various platforms. // This structure provides `application/vnd.oci.image.index.v1+json` mediatype when marshalled to JSON. type Index struct { specs.Versioned // MediaType specifies the type of this document data structure e.g. `application/vnd.oci.image.index.v1+json` MediaType string `json:"mediaType,omitempty"` // ArtifactType specifies the IANA media type of artifact when the manifest is used for an artifact. ArtifactType string `json:"artifactType,omitempty"` // Manifests references platform specific manifests. Manifests []Descriptor `json:"manifests"` // Subject is an optional link from the image manifest to another manifest forming an association between the image manifest and the other manifest. Subject *Descriptor `json:"subject,omitempty"` // Annotations contains arbitrary metadata for the image index. Annotations map[string]string `json:"annotations,omitempty"` } image-spec-1.1.0/specs-go/v1/layout.go000066400000000000000000000023651455506543700174610ustar00rootroot00000000000000// Copyright 2016 The Linux Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package v1 const ( // ImageLayoutFile is the file name containing ImageLayout in an OCI Image Layout ImageLayoutFile = "oci-layout" // ImageLayoutVersion is the version of ImageLayout ImageLayoutVersion = "1.0.0" // ImageIndexFile is the file name of the entry point for references and descriptors in an OCI Image Layout ImageIndexFile = "index.json" // ImageBlobsDir is the directory name containing content addressable blobs in an OCI Image Layout ImageBlobsDir = "blobs" ) // ImageLayout is the structure in the "oci-layout" file, found in the root // of an OCI Image-layout directory. type ImageLayout struct { Version string `json:"imageLayoutVersion"` } image-spec-1.1.0/specs-go/v1/manifest.go000066400000000000000000000033761455506543700177550ustar00rootroot00000000000000// Copyright 2016-2022 The Linux Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package v1 import "github.com/opencontainers/image-spec/specs-go" // Manifest provides `application/vnd.oci.image.manifest.v1+json` mediatype structure when marshalled to JSON. type Manifest struct { specs.Versioned // MediaType specifies the type of this document data structure e.g. `application/vnd.oci.image.manifest.v1+json` MediaType string `json:"mediaType,omitempty"` // ArtifactType specifies the IANA media type of artifact when the manifest is used for an artifact. ArtifactType string `json:"artifactType,omitempty"` // Config references a configuration object for a container, by digest. // The referenced configuration object is a JSON blob that the runtime uses to set up the container. Config Descriptor `json:"config"` // Layers is an indexed list of layers referenced by the manifest. Layers []Descriptor `json:"layers"` // Subject is an optional link from the image manifest to another manifest forming an association between the image manifest and the other manifest. Subject *Descriptor `json:"subject,omitempty"` // Annotations contains arbitrary metadata for the image manifest. Annotations map[string]string `json:"annotations,omitempty"` } image-spec-1.1.0/specs-go/v1/mediatype.go000066400000000000000000000072101455506543700201170ustar00rootroot00000000000000// 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" // MediaTypeImageIndex specifies the media type for an image index. MediaTypeImageIndex = "application/vnd.oci.image.index.v1+json" // MediaTypeImageManifest specifies the media type for an image manifest. MediaTypeImageManifest = "application/vnd.oci.image.manifest.v1+json" // MediaTypeImageConfig specifies the media type for the image configuration. MediaTypeImageConfig = "application/vnd.oci.image.config.v1+json" // MediaTypeEmptyJSON specifies the media type for an unused blob containing the value "{}". MediaTypeEmptyJSON = "application/vnd.oci.empty.v1+json" ) const ( // MediaTypeImageLayer is the media type used for layers referenced by the manifest. MediaTypeImageLayer = "application/vnd.oci.image.layer.v1.tar" // MediaTypeImageLayerGzip is the media type used for gzipped layers // referenced by the manifest. MediaTypeImageLayerGzip = "application/vnd.oci.image.layer.v1.tar+gzip" // MediaTypeImageLayerZstd is the media type used for zstd compressed // layers referenced by the manifest. MediaTypeImageLayerZstd = "application/vnd.oci.image.layer.v1.tar+zstd" ) // Non-distributable layer media-types. // // Deprecated: Non-distributable layers are deprecated, and not recommended // for future use. Implementations SHOULD NOT produce new non-distributable // layers. // https://github.com/opencontainers/image-spec/pull/965 const ( // MediaTypeImageLayerNonDistributable is the media type for layers referenced by // the manifest but with distribution restrictions. // // Deprecated: Non-distributable layers are deprecated, and not recommended // for future use. Implementations SHOULD NOT produce new non-distributable // layers. // https://github.com/opencontainers/image-spec/pull/965 MediaTypeImageLayerNonDistributable = "application/vnd.oci.image.layer.nondistributable.v1.tar" // MediaTypeImageLayerNonDistributableGzip is the media type for // gzipped layers referenced by the manifest but with distribution // restrictions. // // Deprecated: Non-distributable layers are deprecated, and not recommended // for future use. Implementations SHOULD NOT produce new non-distributable // layers. // https://github.com/opencontainers/image-spec/pull/965 MediaTypeImageLayerNonDistributableGzip = "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip" // MediaTypeImageLayerNonDistributableZstd is the media type for zstd // compressed layers referenced by the manifest but with distribution // restrictions. // // Deprecated: Non-distributable layers are deprecated, and not recommended // for future use. Implementations SHOULD NOT produce new non-distributable // layers. // https://github.com/opencontainers/image-spec/pull/965 MediaTypeImageLayerNonDistributableZstd = "application/vnd.oci.image.layer.nondistributable.v1.tar+zstd" ) image-spec-1.1.0/specs-go/version.go000066400000000000000000000021521455506543700172750ustar00rootroot00000000000000// Copyright 2016 The Linux Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package specs import "fmt" const ( // VersionMajor is for an API incompatible changes VersionMajor = 1 // VersionMinor is for functionality in a backwards-compatible manner VersionMinor = 1 // VersionPatch is for backwards-compatible bug fixes VersionPatch = 0 // VersionDev indicates development branch. Releases will be empty string. VersionDev = "" ) // 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.1.0/specs-go/versioned.go000066400000000000000000000016521455506543700176120ustar00rootroot00000000000000// 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"` }