diff --git a/.github/PULL_REQUEST_TEMPLATE/maintainer_nomination.md b/.github/PULL_REQUEST_TEMPLATE/maintainer_nomination.md new file mode 100644 index 000000000..6523d7a82 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/maintainer_nomination.md @@ -0,0 +1,21 @@ +# Nomination for a New Maintainer + +## Nominating Maintainer + +Name of the existing OCI maintainer with GitHub username + +## New Maintainer + +Name of the new maintainer with GitHub username + +## Justification + +Highlight any work contributed by the new maintainer. Examples of contributions may be: +- Community involvement in mailing lists and meetings +- Involvement in any OCI working groups +- Contributions to any of the OCI git repositories + +Other considerations may be: +- Diversity of organizations +- Time involved in the community +- Personal experience working with the new maintainer diff --git a/.github/workflows/docs-and-linting.yml b/.github/workflows/docs-and-linting.yml new file mode 100644 index 000000000..7e6dff468 --- /dev/null +++ b/.github/workflows/docs-and-linting.yml @@ -0,0 +1,45 @@ +name: Render and Lint Documentation + +on: + pull_request: + branches_ignore: [] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + go: ['1.17', '1.18', '1.19'] + + name: Documentation and Linting + steps: + + - uses: actions/checkout@v3 + with: + path: go/src/github.com/opencontainers/image-spec + + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go }} + + - name: Render and Lint + env: + GOPATH: /home/runner/work/image-spec/image-spec/go + run: | + export PATH=$GOPATH/bin:$PATH + cd go/src/github.com/opencontainers/image-spec + make install.tools + go get -t -d ./... + ls ../ + make + make .gitvalidation + make lint + make check-license + make test + make docs + + - name: documentation artifacts + uses: actions/upload-artifact@v3 + with: + name: oci-docs + path: go/src/github.com/opencontainers/image-spec/output diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 000000000..35b52f7ca --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,26 @@ +run: + timeout: 10m + +linters: + disable-all: true + enable: + - deadcode + - dupl + - gofmt + - goimports + - gosimple + - govet + - ineffassign + - misspell + - nakedret + - revive + - structcheck + - unused + - varcheck + - staticcheck + +linters-settings: + gofmt: + simplify: true + dupl: + threshold: 400 diff --git a/.pullapprove.yml b/.pullapprove.yml deleted file mode 100644 index 0f56c1e0c..000000000 --- a/.pullapprove.yml +++ /dev/null @@ -1,27 +0,0 @@ -version: 2 - -requirements: - signed_off_by: - required: true - -group_defaults: - required: 2 - approve_by_comment: - enabled: true - approve_regex: '^(Approved|lgtm|LGTM|:shipit:|:star:|:\+1:|:ship:)' - reject_regex: ^Rejected - reset_on_push: - enabled: true - author_approval: - ignored: true - always_pending: - title_regex: ^WIP - explanation: 'Work in progress...' - conditions: - branches: - - master - -groups: - image-spec: - teams: - - image-spec-maintainers diff --git a/.tool/lint b/.tool/lint deleted file mode 100755 index aa4f179f2..000000000 --- a/.tool/lint +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o nounset -set -o pipefail - -if [ ! $(command -v gometalinter) ]; then - go get -u github.com/alecthomas/gometalinter - gometalinter --install -fi - -for d in $(find . -type d -not -iwholename '*.git*' -a -not -iname '.tool' -a -not -iwholename '*vendor*'); do - gometalinter \ - --exclude='error return value not checked.*(Close|Log|Print).*\(errcheck\)$' \ - --exclude='.*_test\.go:.*error return value not checked.*\(errcheck\)$' \ - --exclude='duplicate of.*_test.go.*\(dupl\)$' \ - --exclude='schema/fs.go' \ - --disable=aligncheck \ - --disable=gotype \ - --disable=gas \ - --cyclo-over=35 \ - --tests \ - --deadline=60s "${d}" -done diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 17f4840ce..000000000 --- a/.travis.yml +++ /dev/null @@ -1,28 +0,0 @@ -language: go -go: - - 1.7 - -sudo: required - -services: - - docker - -before_script: - - export PATH=$HOME/gopath/bin:$PATH - -before_install: - - docker pull vbatts/pandoc - - make install.tools - - go get -u github.com/alecthomas/gometalinter - - gometalinter --install - - go get -t -d ./... - -install: true - -script: - - env | grep TRAVIS_ - - make .gitvalidation - - make lint - - make check-license - - make test - - make docs diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..6eb592ba8 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @jonjohnsonjr @jonboulle @sajayantony @stevvooe @sudo-bmitch @vbatts @cyphar diff --git a/EMERITUS.md b/EMERITUS.md new file mode 100644 index 000000000..eef0b44fd --- /dev/null +++ b/EMERITUS.md @@ -0,0 +1,9 @@ +We would like to acknowledge previous OCI image spec maintainers and their huge contributions to our collective success: + +* Brandon Philips (@philips) +* Brendan Burns (@brendandburns) +* Jason Bouzane (@jbouzane) +* John Starks (@jstarks) +* Keyang Xie (@xiekeyang) + +We thank these members for their service to the OCI community. diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 92c860949..c780b12ef 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -67,4 +67,4 @@ For example: > [runtime-spec adopted]: Tag 0647920 as 1.0.0-rc (+6 -0 #3) -[charter]: https://www.opencontainers.org/about/governance +[charter]: https://github.com/opencontainers/tob/blob/main/CHARTER.md diff --git a/HACKING.md b/HACKING.md index fd38866da..52dfb42b2 100644 --- a/HACKING.md +++ b/HACKING.md @@ -96,7 +96,7 @@ $ make clean This target generates PNG image files from DOT source files in the `img` directory. Prerequisites: -* [graphviz](http://www.graphviz.org/) +* [graphviz](https://www.graphviz.org/) Invocation: ``` diff --git a/MAINTAINERS b/MAINTAINERS index 63de32938..a28746615 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,9 +1,7 @@ -Brandon Philips (@philips) -Brendan Burns (@brendandburns) -Jason Bouzane (@jbouzane) -John Starks (@jstarks) -Jonathan Boulle (@jonboulle) -Stephen Day (@stevvooe) -Vincent Batts (@vbatts) +Brandon Mitchell (@sudo-bmitch) +Jon Johnson (@jonjohnsonjr) +Jonathan Boulle (@jonboulle) +Sajay Antony (@sajayantony) +Stephen Day (@stevvooe) +Vincent Batts (@vbatts) Aleksa Sarai (@cyphar) -Keyang Xie (@xiekeyang) diff --git a/Makefile b/Makefile index a8d5fd39b..0c763b199 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,23 @@ -GO15VENDOREXPERIMENT=1 -export GO15VENDOREXPERIMENT +EPOCH_TEST_COMMIT ?= v0.2.0 DOCKER ?= $(shell command -v docker 2>/dev/null) PANDOC ?= $(shell command -v pandoc 2>/dev/null) +GOPATH:=$(shell go env GOPATH) + +OUTPUT_DIRNAME ?= output +DOC_FILENAME ?= oci-image-spec + +PANDOC_CONTAINER ?= ghcr.io/opencontainers/pandoc:2.9.2.1-8.fc33.x86_64@sha256:5d81ff930a043295a557be8b003ece2a33d14e91b28c50d368413b83372f8d28 ifeq "$(strip $(PANDOC))" '' ifneq "$(strip $(DOCKER))" '' PANDOC = $(DOCKER) run \ - -it \ --rm \ -v $(shell pwd)/:/input/:ro \ -v $(shell pwd)/$(OUTPUT_DIRNAME)/:/$(OUTPUT_DIRNAME)/ \ -u $(shell id -u) \ --workdir /input \ - docker.io/vbatts/pandoc:1.17.0.3-2.fc25.x86_64 + $(PANDOC_CONTAINER) PANDOC_SRC := /input/ PANDOC_DST := / endif @@ -37,12 +41,7 @@ DOC_FILES := \ FIGURE_FILES := \ img/media-types.png -OUTPUT_DIRNAME ?= output/ -DOC_FILENAME ?= oci-image-spec - -EPOCH_TEST_COMMIT ?= v0.2.0 - -TOOLS := esc gitvalidation glide glide-vc +TOOLS := esc gitvalidation default: check-license lint test @@ -52,7 +51,6 @@ help: @echo " * 'docs' - produce document in the $(OUTPUT_DIRNAME) directory" @echo " * 'fmt' - format the json with indentation" @echo " * 'validate-examples' - validate the examples in the specification markdown files" - @echo " * 'schema-fs' - regenerate the virtual schema http/FileSystem" @echo " * 'check-license' - check license headers in source files" @echo " * 'lint' - Execute the source code linter" @echo " * 'test' - Execute the unit tests" @@ -69,35 +67,29 @@ $(OUTPUT_DIRNAME)/$(DOC_FILENAME).pdf: $(DOC_FILES) $(FIGURE_FILES) else $(OUTPUT_DIRNAME)/$(DOC_FILENAME).pdf: $(DOC_FILES) $(FIGURE_FILES) @mkdir -p $(OUTPUT_DIRNAME)/ && \ - $(PANDOC) -f markdown_github -t latex --latex-engine=xelatex -o $(PANDOC_DST)$@ $(patsubst %,$(PANDOC_SRC)%,$(DOC_FILES)) + $(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 markdown_github -t html5 -H $(PANDOC_SRC)header.html --standalone -o $(PANDOC_DST)$@ $(patsubst %,$(PANDOC_SRC)%,$(DOC_FILES)) + $(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 > $@ -validate-examples: schema/fs.go +validate-examples: schema/schema.go go test -run TestValidate ./schema -schema/fs.go: $(wildcard schema/*.json) schema/gen.go - cd schema && printf "%s\n\n%s\n" "$$(cat ../.header)" "$$(go generate)" > fs.go - -schema-fs: schema/fs.go - @echo "generating schema fs" - check-license: @echo "checking license headers" @./.tool/check-license -lint: +lint: .install.lint @echo "checking lint" - @./.tool/lint + @GO111MODULE=on $(GOPATH)/bin/golangci-lint run test: schema/fs.go go test -race -cover $(shell go list ./... | grep -v /vendor/) @@ -106,28 +98,26 @@ img/%.png: img/%.dot dot -Tpng $^ > $@ -# When this is running in travis, it will only check the travis commit range +# When this is running in GitHub, it will only check the commit range .gitvalidation: @which git-validation > /dev/null 2>/dev/null || (echo "ERROR: git-validation not found. Consider 'make install.tools' target" && false) -ifdef TRAVIS_COMMIT_RANGE - git-validation -q -run DCO,short-subject,dangling-whitespace +ifdef GITHUB_SHA + $(GOPATH)/bin/git-validation -q -run DCO,short-subject,dangling-whitespace -range $(GITHUB_SHA)..HEAD else - git-validation -v -run DCO,short-subject,dangling-whitespace -range $(EPOCH_TEST_COMMIT)..HEAD + $(GOPATH)/bin/git-validation -v -run DCO,short-subject,dangling-whitespace -range $(EPOCH_TEST_COMMIT)..HEAD endif install.tools: $(TOOLS:%=.install.%) -.install.esc: - go get -u github.com/mjibson/esc +.install.lint: + case "$$(go env GOVERSION)" in \ + go1.17.*) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.47.3;; \ + go1.18.*) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.47.3;; \ + *) go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest;; \ + esac .install.gitvalidation: - go get -u github.com/vbatts/git-validation - -.install.glide: - go get -u github.com/Masterminds/glide - -.install.glide-vc: - go get -u github.com/sgotti/glide-vc + go install github.com/vbatts/git-validation@latest clean: rm -rf *~ $(OUTPUT_DIRNAME) header.html diff --git a/README.md b/README.md index 31e827865..ce8308cdd 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,11 @@ The Go types and validation should be compatible with the current Go release; ea Additional documentation about how this group operates: -- [Code of Conduct](https://github.com/opencontainers/tob/blob/d2f9d68c1332870e40693fe077d311e0742bc73d/code-of-conduct.md) +- [Code of Conduct][code-of-conduct] - [Roadmap](#roadmap) - [Releases](RELEASES.md) - [Project Documentation](project.md) -The _optional_ and _base_ layers of all OCI projects are tracked in the [OCI Scope Table](https://www.opencontainers.org/about/oci-scope-table). - ## Running an OCI Image The OCI Image Format partner project is the [OCI Runtime Spec project](https://github.com/opencontainers/runtime-spec). @@ -39,7 +37,7 @@ To support this UX the OCI Image Format contains sufficient information to launc **Q: Why doesn't this project mention distribution?** -A: Distribution, for example using HTTP as both Docker v2.2 and AppC do today, is currently out of scope on the [OCI Scope Table](https://www.opencontainers.org/about/oci-scope-table). +A: Distribution, for example using HTTP as both Docker v2.2 and AppC do today, is currently out of scope. There has been [some discussion on the TOB mailing list](https://groups.google.com/a/opencontainers.org/d/msg/tob/A3JnmI-D-6Y/tLuptPDHAgAJ) to make distribution an optional layer, but this topic is a work in progress. **Q: What happens to AppC or Docker Image Formats?** @@ -71,12 +69,10 @@ It also guarantees that the design is sound before code is written; a GitHub pul Typos and grammatical errors can go straight to a pull-request. When in doubt, start on the [mailing-list](#mailing-list). -## Weekly Call +## Meetings -The contributors and maintainers of all OCI projects have a weekly meeting Wednesdays at 2:00 PM (USA Pacific). -Everyone is welcome to participate via [UberConference web][UberConference] or audio-only: +1-415-968-0849 (no PIN needed). -An initial agenda will be posted to the [mailing list](#mailing-list) earlier in the week, and everyone is welcome to propose additional topics or suggest other agenda alterations there. -Minutes are posted to the [mailing list](#mailing-list) and minutes from past calls are archived [here][minutes]. +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 @@ -97,7 +93,7 @@ For example, this paragraph will span three lines in the Markdown source. ### Sign your work The sign-off is a simple line at the end of the explanation for the patch, which certifies that you wrote it or otherwise have the right to pass it on as an open-source patch. -The rules are pretty simple: if you can certify the below (from [developercertificate.org](http://developercertificate.org/)): +The rules are pretty simple: if you can certify the below (from [developercertificate.org](https://developercertificate.org/)): ``` Developer Certificate of Origin @@ -149,7 +145,7 @@ You can add the sign off when creating the git commit via `git commit -s`. ### Commit Style Simple house-keeping for clean git history. -Read more on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/) or the Discussion section of [`git-commit(1)`](http://git-scm.com/docs/git-commit). +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 @@ -162,6 +158,5 @@ Read more on [How to Write a Git Commit Message](http://chris.beams.io/posts/git 8. When possible, one keyword to scope the change in the subject (i.e. "README: ...", "runtime: ...") -[UberConference]: https://www.uberconference.com/opencontainers +[code-of-conduct]: https://github.com/opencontainers/org/blob/master/CODE_OF_CONDUCT.md [irc-logs]: http://ircbot.wl.linuxfoundation.org/eavesdrop/%23opencontainers/ -[minutes]: http://ircbot.wl.linuxfoundation.org/meetings/opencontainers/ diff --git a/RELEASES.md b/RELEASES.md index e220042c8..22690cd1e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -48,4 +48,4 @@ Specifications have a variety of different timelines in their lifecycle. For example if a breaking change is introduced in v1.0.0-rc2 then the series would end with v1.0.0-rc4 and v1.0.0. - Minor and patch releases SHOULD be made on an as-needed basis. -[charter]: https://www.opencontainers.org/about/governance +[charter]: https://github.com/opencontainers/tob/blob/main/CHARTER.md diff --git a/annotations.md b/annotations.md index f3ba371be..8bf2d7a4d 100644 --- a/annotations.md +++ b/annotations.md @@ -24,7 +24,7 @@ This specification defines the following annotation keys, intended for but not l * **org.opencontainers.image.source** URL to get source code for building the image (string) * **org.opencontainers.image.version** version of the packaged software * The version MAY match a label or tag in the source code repository - * version MAY be [Semantic versioning-compatible](http://semver.org/) + * 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]. @@ -40,6 +40,15 @@ This specification defines the following annotation keys, intended for but not l ``` * **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](https://github.com/distribution/distribution/blob/d0deff9cd6c2b8c82c6f3d1c713af51df099d07b/reference/reference.go). + * 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 @@ -47,7 +56,7 @@ This specification defines the following annotation keys, intended for but not l 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 | +| `org.opencontainers.image` prefix | `org.label-schema` prefix | Compatibility notes | |---------------------------|-------------------------|---------------------| | `created` | `build-date` | Compatible | | `url` | `url` | Compatible | diff --git a/config.md b/config.md index 398296ae4..4306c82e9 100644 --- a/config.md +++ b/config.md @@ -110,6 +110,24 @@ Note: Any OPTIONAL field MAY also be set to null, which is equivalent to being a 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. @@ -148,7 +166,7 @@ Note: Any OPTIONAL field MAY also be set to null, which is equivalent to being a - **Volumes** *object*, OPTIONAL - A set of directories describing where the process is likely write data specific to a container instance. + 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 @@ -165,6 +183,22 @@ Note: Any OPTIONAL field MAY also be set to null, which is equivalent to being a 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`. + - **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. @@ -264,6 +298,10 @@ Here is an example image configuration JSON document: "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" } ] } diff --git a/considerations.md b/considerations.md index 7b53c3438..d176c511f 100644 --- a/considerations.md +++ b/considerations.md @@ -20,10 +20,10 @@ Implementations: * [Go][]: [github.com/docker/go][], which claims to implement [canonical JSON][canonical-json] except for Unicode normalization. -[canonical-json]: http://wiki.laptop.org/go/Canonical_JSON +[canonical-json]: https://wiki.laptop.org/go/Canonical_JSON [github.com/docker/go]: https://github.com/docker/go/ [Go]: https://golang.org/ -[JSON]: http://json.org/ +[JSON]: https://json.org/ # EBNF diff --git a/conversion.md b/conversion.md index 285c2037e..d406baedc 100644 --- a/conversion.md +++ b/conversion.md @@ -44,15 +44,25 @@ These fields all affect the `annotations` of the runtime configuration, and are | Image Field | Runtime Field | Notes | | ------------------- | --------------- | ----- | -| `author` | `annotations` | 1,2 | -| `created` | `annotations` | 1,3 | +| `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,4 | +| `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.author` in `annotations`. -3. The value of this field MUST be set as the value of `org.opencontainers.image.created` in `annotations`. -4. The value of this field MUST be set as the value of `org.opencontainers.image.stopSignal` in `annotations`. +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 diff --git a/descriptor.md b/descriptor.md index 570985c52..e8ca10326 100644 --- a/descriptor.md +++ b/descriptor.md @@ -41,25 +41,25 @@ The following fields contain the primary properties that constitute a Descriptor - **`annotations`** *string-string map* - This OPTIONAL property contains arbitrary metadata for this descriptor. - This OPTIONAL property MUST use the [annotation rules](annotations.md#rules). + This OPTIONAL property contains arbitrary metadata for this descriptor. + This OPTIONAL property MUST use the [annotation rules](annotations.md#rules). -Descriptors pointing to [`application/vnd.oci.image.manifest.v1+json`](manifest.md) SHOULD include the extended field `platform`, see [Image Index Property Descriptions](image-index.md#image-index-property-descriptions) for details. - -### Reserved +- **`data`** *string* -The following field keys are reserved and MUST NOT be used by other specifications. + 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. -- **`data`** *string* +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. - This key is RESERVED for future versions of the specification. +### Reserved -All other fields may be included in other OCI specifications. Extended _Descriptor_ field additions proposed in other OCI specifications SHOULD first be considered for addition into this specification. ## Digests -The _digest_ property of a Descriptor acts as a content identifier, enabling [content addressability](http://en.wikipedia.org/wiki/Content-addressable_storage). +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. @@ -99,7 +99,7 @@ As an example, we can parameterize the encoding and algorithm as `multihash+base Before consuming content targeted by a descriptor from untrusted sources, the byte content SHOULD be verified against the digest string. Before calculating the digest, the size of the content SHOULD be verified to reduce hash collision space. Heavy processing before calculating a hash SHOULD be avoided. -Implementations MAY employ [canonicalization](canonicalization.md#canonicalization) of the underlying content to ensure stable content identifiers. +Implementations MAY employ [canonicalization](considerations.md#canonicalization) of the underlying content to ensure stable content identifiers. ### Digest calculations @@ -145,12 +145,26 @@ Note that `[A-F]` MUST NOT be used here. #### SHA-512 -[SHA-512][rfc4634-s4.2] is a collision-resistant hash function which [may be more perfomant][sha256-vs-sha512] than [SHA-256](#sha-256) on some CPUs. +[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: @@ -179,6 +193,7 @@ In the following example, the descriptor indicates that the referenced manifest [rfc3986]: https://tools.ietf.org/html/rfc3986 [rfc4634-s4.1]: https://tools.ietf.org/html/rfc4634#section-4.1 [rfc4634-s4.2]: https://tools.ietf.org/html/rfc4634#section-4.2 +[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 diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..d2c12ecfd --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module github.com/opencontainers/image-spec + +go 1.17 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/opencontainers/go-digest v1.0.0 + github.com/pkg/errors v0.9.1 + github.com/russross/blackfriday v1.6.0 + github.com/stretchr/testify v1.7.0 // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 + github.com/xeipuuv/gojsonschema v1.2.0 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) + +require github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..766d1071d --- /dev/null +++ b/go.sum @@ -0,0 +1,25 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/image-index.md b/image-index.md index 2db34fc28..ab3dc4e9c 100644 --- a/image-index.md +++ b/image-index.md @@ -4,6 +4,7 @@ The image index is a higher-level manifest which points to specific [image manif 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 @@ -35,9 +36,13 @@ For the media type(s) that this document is compatible with, see the [matrix][ma - [`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 SHOULD be safely ignored. + An encountered `mediaType` that is unknown to the implementation MUST be ignored. - **`platform`** *object* @@ -72,20 +77,14 @@ For the media type(s) that this document is compatible with, see the [matrix][ma - **`variant`** *string* This OPTIONAL property specifies the variant of the CPU. - Image indexes SHOULD use, and implementations SHOULD understand, values listed in the following table. - When the variant of the CPU is not listed in the table, values are implementation-defined and SHOULD be submitted to this specification for standardization. - - | ISA/ABI | `architecture` | `variant` | - |-----------------|----------------|-------------| - | ARM 32-bit, v6 | `arm` | `v6` | - | ARM 32-bit, v7 | `arm` | `v7` | - | ARM 32-bit, v8 | `arm` | `v8` | - | ARM 64-bit, v8 | `arm64` | `v8` | + 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. + - **`annotations`** *string-string map* This OPTIONAL property contains arbitrary metadata for the image index. @@ -93,12 +92,24 @@ For the media type(s) that this document is compatible with, see the [matrix][ma 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", diff --git a/image-layout.md b/image-layout.md index 2915c8948..19f9a7dc3 100644 --- a/image-layout.md +++ b/image-layout.md @@ -1,6 +1,6 @@ ## OCI Image Layout Specification -* The OCI Image Layout is directory structure for OCI content-addressable blobs and [location-addressable](https://en.wikipedia.org/wiki/Content-addressable_storage#Content-addressed_vs._location-addressed) references (refs). +* 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: @@ -162,6 +162,7 @@ Those tags will often be represented in an image-layout repository with matching ```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", @@ -199,7 +200,11 @@ Those tags will often be represented in an image-layout repository with matching } ``` -This illustrates an index that provides two named manifest references and an auxiliary mediatype for this image layout. +This illustrates an index that provides two named references and an auxiliary mediatype for this image layout. +The first named reference (`stable-release`) points to another index that might contain multiple references with distinct platforms and annotations. +Note that the [`org.opencontainers.image.ref.name` annotation](annotations.md) SHOULD only be considered valid when on descriptors on `index.json`. + +The second named reference (`v1.0`) points to a manifest that is specific to the linux/ppc64le platform. [descriptors]: ./descriptor.md diff --git a/img/build-diagram.png b/img/build-diagram.png index d34a04786..9ad9b84eb 100644 Binary files a/img/build-diagram.png and b/img/build-diagram.png differ diff --git a/img/media-types.dot b/img/media-types.dot index 12ff55ff5..e3b1e3f3d 100644 --- a/img/media-types.dot +++ b/img/media-types.dot @@ -6,6 +6,7 @@ digraph G { layer [shape=note, label="Layer tar archive\napplication/vnd.oci.image.layer.v1.tar\napplication/vnd.oci.image.layer.v1.tar+gzip\napplication/vnd.oci.image.layer.nondistributable.v1.tar\napplication/vnd.oci.image.layer.nondistributable.v1.tar+gzip"] } + imageIndex -> imageIndex [label="1..*"] imageIndex -> manifest [label="1..*"] manifest -> config [label="1..1"] manifest -> layer [label="1..*"] diff --git a/img/media-types.png b/img/media-types.png index 996aae8e6..729c02d54 100644 Binary files a/img/media-types.png and b/img/media-types.png differ diff --git a/img/run-diagram.png b/img/run-diagram.png index df6145bf0..c1009f4de 100644 Binary files a/img/run-diagram.png and b/img/run-diagram.png differ diff --git a/implementations.md b/implementations.md index 31d8279e1..18dbe18e6 100644 --- a/implementations.md +++ b/implementations.md @@ -4,18 +4,23 @@ Projects or Companies currently adopting the OCI Image Specification * [projectatomic/skopeo](https://github.com/projectatomic/skopeo) * [Amazon Elastic Container Registry (ECR)](https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-manifest-formats.html) ([announcement](https://aws.amazon.com/about-aws/whats-new/2017/01/amazon-ecr-supports-docker-image-manifest-v2-schema-2/)) +* [Azure Container Registry (ACR)](https://docs.microsoft.com/azure/container-registry/container-registry-image-formats#oci-images) * [openSUSE/umoci](https://github.com/openSUSE/umoci) * [cloudfoundry/grootfs](https://github.com/cloudfoundry/grootfs) ([source](https://github.com/cloudfoundry/grootfs/blob/c3da26e1e463b51be1add289032f3dca6698b335/fetcher/remote/docker_src.go)) * [Mesos plans](https://issues.apache.org/jira/browse/MESOS-5011) ([design doc](https://docs.google.com/document/d/1Pus7D-inIBoLSIPyu3rl_apxvUhtp3rp0_b0Ttr2Xww/edit#heading=h.hrvk2wboog4p)) * [Docker](https://github.com/docker) - [docker/docker (`docker save/load` WIP)](https://github.com/docker/docker/pull/26369) - - [docker/distribution (registry PR)](https://github.com/docker/distribution/pull/2076) + - [distribution/distribution (registry PR)](https://github.com/distribution/distribution/pull/2076) * [containerd/containerd](https://github.com/containerd/containerd) * [Containers](https://github.com/containers/) - [containers/build](https://github.com/containers/build) - [containers/image](https://github.com/containers/image) + - [containers/oci-spec-rs](https://github.com/containers/oci-spec-rs) + - [containers/libocispec](https://github.com/containers/libocispec) +* [krustlet/oci-distribution](https://github.com/krustlet/oci-distribution) * [coreos/rkt](https://github.com/coreos/rkt) * [box-builder/box](https://github.com/box-builder/box) * [coolljt0725/docker2oci](https://github.com/coolljt0725/docker2oci) +* [regclient/regclient](https://github.com/regclient/regclient) _(to add your project please open a [pull-request](https://github.com/opencontainers/image-spec/pulls))_ diff --git a/layer.md b/layer.md index 947037ce5..533a427cb 100644 --- a/layer.md +++ b/layer.md @@ -4,13 +4,18 @@ This document describes how to serialize a filesystem and filesystem changes lik One or more layers are applied on top of each other to create a complete filesystem. This document will use a concrete example to illustrate how to create and consume these filesystem layers. -This section defines the `application/vnd.oci.image.layer.v1.tar`, `application/vnd.oci.image.layer.v1.tar+gzip`, `application/vnd.oci.image.layer.nondistributable.v1.tar`, and `application/vnd.oci.image.layer.nondistributable.v1.tar+gzip` [media types](media-types.md). +This section defines the `application/vnd.oci.image.layer.v1.tar`, `application/vnd.oci.image.layer.v1.tar+gzip`, `application/vnd.oci.image.layer.v1.tar+zstd`, `application/vnd.oci.image.layer.nondistributable.v1.tar`, `application/vnd.oci.image.layer.nondistributable.v1.tar+gzip`, and `application/vnd.oci.image.layer.nondistributable.v1.tar+zstd` [media types](media-types.md). ## `+gzip` Media Types * The media type `application/vnd.oci.image.layer.v1.tar+gzip` represents an `application/vnd.oci.image.layer.v1.tar` payload which has been compressed with [gzip][rfc1952_2]. * The media type `application/vnd.oci.image.layer.nondistributable.v1.tar+gzip` represents an `application/vnd.oci.image.layer.nondistributable.v1.tar` payload which has been compressed with [gzip][rfc1952_2]. +## `+zstd` Media Types + +* The media type `application/vnd.oci.image.layer.v1.tar+zstd` represents an `application/vnd.oci.image.layer.v1.tar` payload which has been compressed with [zstd][rfc8478]. +* The media type `application/vnd.oci.image.layer.nondistributable.v1.tar+zstd` represents an `application/vnd.oci.image.layer.nondistributable.v1.tar` payload which has been compressed with [zstd][rfc8478]. + ## Distributable Format * Layer Changesets for the [media type](media-types.md) `application/vnd.oci.image.layer.v1.tar` MUST be packaged in [tar archive][tar-archive]. @@ -58,7 +63,7 @@ Where supported, MUST include file attributes for Additions and Modifications in #### Hardlinks -* Hardlinks are a [POSIX concept](http://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html) for having one or more directory entries for the same file on the same device. +* Hardlinks are a [POSIX concept](https://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html) for having one or more directory entries for the same file on the same device. * Not all filesystems support hardlinks (e.g. [FAT](https://en.wikipedia.org/wiki/File_Allocation_Table)). * Hardlinks are possible with all [file types](#file-types) except `directories`. * Non-directory files are considered "hardlinked" when their link count is greater than 1. @@ -142,9 +147,9 @@ Entries for the following files: Create a new directory and initialize it with a copy or snapshot of the prior root filesystem. Example commands that can preserve [file attributes](#file-attributes) to make this copy are: -* [cp(1)](http://linux.die.net/man/1/cp): `cp -a rootfs-c9d-v1/ rootfs-c9d-v1.s1/` -* [rsync(1)](http://linux.die.net/man/1/rsync): `rsync -aHAX rootfs-c9d-v1/ rootfs-c9d-v1.s1/` -* [tar(1)](http://linux.die.net/man/1/tar): `mkdir rootfs-c9d-v1.s1 && tar --acls --xattrs -C rootfs-c9d-v1/ -c . | tar -C rootfs-c9d-v1.s1/ --acls --xattrs -x` (including `--selinux` where supported) +* [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. @@ -230,7 +235,7 @@ This section specifies applying an entry from a layer changeset if the target pa If the entry and the existing path are both directories, then the existing path's attributes MUST be replaced by those of the entry in the changeset. In all other cases, the implementation MUST do the semantic equivalent of the following: -- removing the file path (e.g. [`unlink(2)`](http://linux.die.net/man/2/unlink) on Linux systems) +- 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 @@ -328,6 +333,7 @@ Implementations SHOULD NOT upload layers tagged with this media type; however, s [Descriptors](descriptor.md) referencing non-distributable layers MAY include `urls` for downloading these layers directly; however, the presence of the `urls` field SHOULD NOT be used to determine whether or not a layer is non-distributable. [libarchive-tar]: https://github.com/libarchive/libarchive/wiki/ManPageTar5#POSIX_ustar_Archives -[gnu-tar-standard]: http://www.gnu.org/software/tar/manual/html_node/Standard.html +[gnu-tar-standard]: https://www.gnu.org/software/tar/manual/html_node/Standard.html [rfc1952_2]: https://tools.ietf.org/html/rfc1952 [tar-archive]: https://en.wikipedia.org/wiki/Tar_(computing) +[rfc8478]: https://tools.ietf.org/html/rfc8478 diff --git a/manifest.md b/manifest.md index 8676045cb..c7e8ffe54 100644 --- a/manifest.md +++ b/manifest.md @@ -61,6 +61,8 @@ Unlike the [image index](image-index.md), which contains information about a set - [`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. + An encountered `mediaType` that is unknown to the implementation MUST be ignored. + Entries in this field will frequently use the `+gzip` types. @@ -77,6 +79,7 @@ Unlike the [image index](image-index.md), which contains information about a set ```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", "size": 7023, diff --git a/media-types.md b/media-types.md index 1f01fd6b2..aa0b464b3 100644 --- a/media-types.md +++ b/media-types.md @@ -9,8 +9,10 @@ The following media types identify the formats described here and their referenc - `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.image.layer.nondistributable.v1.tar`: ["Layer", as a tar archive with distribution restrictions](layer.md#non-distributable-layers) - `application/vnd.oci.image.layer.nondistributable.v1.tar+gzip`: ["Layer", as a tar archive with distribution restrictions](layer.md#gzip-media-types) compressed with [gzip][rfc1952] +- `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 @@ -36,25 +38,38 @@ This section shows where the OCI Image Specification is compatible with formats **Similar/related schema** -- [application/vnd.docker.distribution.manifest.list.v2+json](https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-2.md#manifest-list) - mediaType is different +- [application/vnd.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/docker/distribution/blob/master/docs/spec/manifest-v2-2.md#image-manifest-field-descriptions) +- [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/docker/docker/blob/master/image/spec/v1.md#creating-an-image-filesystem-changeset) +- [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/docker/docker/blob/master/image/spec/v1.md#image-json-description) +- [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 + +`.config.StopSignal` and `.config.Labels` are accidentally undocumented in Docker Image Spec v1.2, but these fields are not OCI-specific concepts. ## Relations @@ -66,3 +81,4 @@ The following figure shows how the above media types reference each other: The image-index being a "fat manifest" references a list of image manifests per target platform. An image manifest references exactly one target configuration and possibly many layers. [rfc1952]: https://tools.ietf.org/html/rfc1952 +[rfc8478]: https://tools.ietf.org/html/rfc8478 diff --git a/schema/backwards_compatibility_test.go b/schema/backwards_compatibility_test.go index 00fb6afc9..ca17bbc04 100644 --- a/schema/backwards_compatibility_test.go +++ b/schema/backwards_compatibility_test.go @@ -21,7 +21,7 @@ import ( "github.com/opencontainers/go-digest" "github.com/opencontainers/image-spec/schema" - "github.com/opencontainers/image-spec/specs-go/v1" + v1 "github.com/opencontainers/image-spec/specs-go/v1" ) var compatMap = map[string]string{ diff --git a/schema/config-schema.json b/schema/config-schema.json index 15bccd04e..f85f8cd4b 100644 --- a/schema/config-schema.json +++ b/schema/config-schema.json @@ -1,6 +1,6 @@ { "description": "OpenContainer Config Specification", - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "https://json-schema.org/draft-04/schema#", "id": "https://opencontainers.org/schema/image/config", "type": "object", "properties": { @@ -14,9 +14,21 @@ "architecture": { "type": "string" }, + "variant": { + "type": "string" + }, "os": { "type": "string" }, + "os.version": { + "type": "string" + }, + "os.features": { + "type": "array", + "items": { + "type": "string" + } + }, "config": { "type": "object", "properties": { diff --git a/schema/config_test.go b/schema/config_test.go index 28b872870..9daa41e5c 100644 --- a/schema/config_test.go +++ b/schema/config_test.go @@ -39,6 +39,23 @@ func TestConfig(t *testing.T) { "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, }, @@ -140,7 +157,8 @@ func TestConfig(t *testing.T) { { "created": "2015-10-31T22:22:56.015925234Z", "author": "Alyssa P. Hacker ", - "architecture": "amd64", + "architecture": "arm64", + "variant": "v8", "os": "linux", "config": { "User": "1:1", diff --git a/schema/content-descriptor.json b/schema/content-descriptor.json index 69fcea92e..b64ca1313 100644 --- a/schema/content-descriptor.json +++ b/schema/content-descriptor.json @@ -1,6 +1,6 @@ { "description": "OpenContainer Content Descriptor Specification", - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "https://json-schema.org/draft-04/schema#", "id": "https://opencontainers.org/schema/descriptor", "type": "object", "properties": { @@ -21,7 +21,7 @@ "$ref": "defs-descriptor.json#/definitions/urls" }, "annotations": { - "id": "https://opencontainers.org/schema/image/descriptor/annotations", + "id": "https://opencontainers.org/schema/descriptor/annotations", "$ref": "defs-descriptor.json#/definitions/annotations" } }, diff --git a/schema/defs-descriptor.json b/schema/defs-descriptor.json index feaea001b..dad2b0a3f 100644 --- a/schema/defs-descriptor.json +++ b/schema/defs-descriptor.json @@ -20,7 +20,6 @@ } }, "annotations": { - "id": "https://opencontainers.org/schema/image/descriptor/annotations", "$ref": "defs.json#/definitions/mapStringString" } } diff --git a/schema/descriptor_test.go b/schema/descriptor_test.go index ecdbe52b1..c6b610779 100644 --- a/schema/descriptor_test.go +++ b/schema/descriptor_test.go @@ -97,7 +97,7 @@ func TestDescriptor(t *testing.T) { fail: false, }, - // expected success: mediaType does not match pattern (type too long) + // expected failure: mediaType does not match pattern (type too long) { descriptor: ` { @@ -109,7 +109,7 @@ func TestDescriptor(t *testing.T) { fail: true, }, - // expected success: mediaType does not match pattern (subtype too long) + // expected failure: mediaType does not match pattern (subtype too long) { descriptor: ` { diff --git a/schema/error.go b/schema/error.go index 8b0bfc2af..baf875195 100644 --- a/schema/error.go +++ b/schema/error.go @@ -15,10 +15,9 @@ package schema import ( + "bufio" "encoding/json" "io" - - "go4.org/errorutil" ) // A SyntaxError is a description of a JSON syntax error @@ -36,7 +35,21 @@ func (e *SyntaxError) Error() string { return e.msg } // If the given error is not a *json.SyntaxError it is returned unchanged. func WrapSyntaxError(r io.Reader, err error) error { if serr, ok := err.(*json.SyntaxError); ok { - line, col, _ := errorutil.HighlightBytePosition(r, serr.Offset) + 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} } diff --git a/schema/fs.go b/schema/fs.go deleted file mode 100644 index f83391b78..000000000 --- a/schema/fs.go +++ /dev/null @@ -1,321 +0,0 @@ -// Copyright 2016 The Linux Foundation -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package schema - -import ( - "bytes" - "compress/gzip" - "encoding/base64" - "io/ioutil" - "net/http" - "os" - "path" - "sync" - "time" -) - -type _escLocalFS struct{} - -var _escLocal _escLocalFS - -type _escStaticFS struct{} - -var _escStatic _escStaticFS - -type _escDirectory struct { - fs http.FileSystem - name string -} - -type _escFile struct { - compressed string - size int64 - modtime int64 - local string - isDir bool - - once sync.Once - data []byte - name string -} - -func (_escLocalFS) Open(name string) (http.File, error) { - f, present := _escData[path.Clean(name)] - if !present { - return nil, os.ErrNotExist - } - return os.Open(f.local) -} - -func (_escStaticFS) prepare(name string) (*_escFile, error) { - f, present := _escData[path.Clean(name)] - if !present { - return nil, os.ErrNotExist - } - var err error - f.once.Do(func() { - f.name = path.Base(name) - if f.size == 0 { - return - } - var gr *gzip.Reader - b64 := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(f.compressed)) - gr, err = gzip.NewReader(b64) - if err != nil { - return - } - f.data, err = ioutil.ReadAll(gr) - }) - if err != nil { - return nil, err - } - return f, nil -} - -func (fs _escStaticFS) Open(name string) (http.File, error) { - f, err := fs.prepare(name) - if err != nil { - return nil, err - } - return f.File() -} - -func (dir _escDirectory) Open(name string) (http.File, error) { - return dir.fs.Open(dir.name + name) -} - -func (f *_escFile) File() (http.File, error) { - type httpFile struct { - *bytes.Reader - *_escFile - } - return &httpFile{ - Reader: bytes.NewReader(f.data), - _escFile: f, - }, nil -} - -func (f *_escFile) Close() error { - return nil -} - -func (f *_escFile) Readdir(count int) ([]os.FileInfo, error) { - return nil, nil -} - -func (f *_escFile) Stat() (os.FileInfo, error) { - return f, nil -} - -func (f *_escFile) Name() string { - return f.name -} - -func (f *_escFile) Size() int64 { - return f.size -} - -func (f *_escFile) Mode() os.FileMode { - return 0 -} - -func (f *_escFile) ModTime() time.Time { - return time.Unix(f.modtime, 0) -} - -func (f *_escFile) IsDir() bool { - return f.isDir -} - -func (f *_escFile) Sys() interface{} { - return f -} - -// _escFS returns a http.Filesystem for the embedded assets. If useLocal is true, -// the filesystem's contents are instead used. -func _escFS(useLocal bool) http.FileSystem { - if useLocal { - return _escLocal - } - return _escStatic -} - -// _escDir returns a http.Filesystem for the embedded assets on a given prefix dir. -// If useLocal is true, the filesystem's contents are instead used. -func _escDir(useLocal bool, name string) http.FileSystem { - if useLocal { - return _escDirectory{fs: _escLocal, name: name} - } - return _escDirectory{fs: _escStatic, name: name} -} - -// _escFSByte returns the named file from the embedded assets. If useLocal is -// true, the filesystem's contents are instead used. -func _escFSByte(useLocal bool, name string) ([]byte, error) { - if useLocal { - f, err := _escLocal.Open(name) - if err != nil { - return nil, err - } - b, err := ioutil.ReadAll(f) - f.Close() - return b, err - } - f, err := _escStatic.prepare(name) - if err != nil { - return nil, err - } - return f.data, nil -} - -// _escFSMustByte is the same as _escFSByte, but panics if name is not present. -func _escFSMustByte(useLocal bool, name string) []byte { - b, err := _escFSByte(useLocal, name) - if err != nil { - panic(err) - } - return b -} - -// _escFSString is the string version of _escFSByte. -func _escFSString(useLocal bool, name string) (string, error) { - b, err := _escFSByte(useLocal, name) - return string(b), err -} - -// _escFSMustString is the string version of _escFSMustByte. -func _escFSMustString(useLocal bool, name string) string { - return string(_escFSMustByte(useLocal, name)) -} - -var _escData = map[string]*_escFile{ - - "/config-schema.json": { - local: "config-schema.json", - size: 2771, - modtime: 1498025574, - compressed: ` -H4sIAAAJbogA/+RWQW/bPAy9+1cYbo9t/R2+U67dbgMyINh2KIZAsemEnSVqFD3MGPLfB8vJZtmym3XI -aScDFB/f4xMl60eSplkJrmC0gmSyVZqtLZhHMqLQAKePZCrcpxsLBVZYKJ9118FuXXEArTrIQcSu8vzZ -kbnvow/E+7xkVcn9f//nfeymx2F5hrhVnpMFU5zZnIf12TlqtYe88Pw9UloLHZZ2z1BIH7NMFlgQXLZK -u3bSNCsYlED5KzCAOmE0fTkfr4i1km6lVAL3ghoyv3bsUzLVyIF4oVSYzcUBBQppGC7FkLs08+RFJHvg -iI9HXPHxDw44iMwwDlh9ztvvlhyU74nFjfG3DJU3ECr30I3ATV5ChQa7UXG5VnbjK697jfH65tucLMWs -2uxuuIQCeixjoZE0Pc6QCreW0MiYmwysu56eAoKQblHigswXpIZyR5IXVZimrsNKwzqfoxY86vKf7f0j -1Y2GyThf2P9rp/7aXX0i/oJm/wZfdc7fqR3U17ZkE9n4a1qyEbIb3BtVX2xJMvyer18mkip6WV96/ZZY -VVssJwZf/6475S91H9CCafRkx7NatcAuizuejFgzhq8Nsv8PP0U8GKtLhhXPnh/QCXEbMz00K2LU3PbM -b1D07fCyW0vviMlWxN4UcYpZ/Enjdtf+RQ3SGiZ/vj8oANpKu/UTMV9kR1SDMjPzGZ6y5MQwnZvwWfX7 -2RSey6SbnWPyMwAA//9KY9sL0woAAA== -`, - }, - - "/content-descriptor.json": { - local: "content-descriptor.json", - size: 1085, - modtime: 1498025574, - compressed: ` -H4sIAAAJbogA/5yTwW7UMBCG73mKUVqpl27NoeIQVb3AnQPcEAevPY6nbGwznlW1oL47mniXJoAo3Vsy -+r+Zz8n4RwfQe6yOqQjl1A/QfyiY3uUklhIy6BMmgffHUGb4WNBRIGdn4lpbXFYXcbKKR5EyGPNQc9q0 -6k3m0Xi2QTZvbk2rXTSO/AmpgzG5YHKnyXXGWtr4X9MbJ4eCSubtAzpptcK5IAth7QfQgwH0E3qyn1q4 -lf48r0SEOadNIQfQAmNAxuTQw2LGjF8yBuU8hrp5FrvRE18Yj4ESae9qnqfP7FNr0Vf6/pKPRoASbA+C -9ZVOfxGhJG9v1xKeRqzygobjQ5E8si2RHLiI7mvdT9DYk1ZzuVZdfS1WBDnB1Z3djZlJ4nQ/3OmP9ejv -r875jkfXlf+ed/Uf9hZ21BQ1CIHzBI+RXASJVI/OMNkDbBF8fky7bD36c+xmk5WbTSnLfDtWiv+77DTZ -ERcrb5b9zhBc4s2zO7r2jN/2xKhin3+/McttXS9NB/Cle+p+BgAA///HjexwPQQAAA== -`, - }, - - "/defs-descriptor.json": { - local: "defs-descriptor.json", - size: 922, - modtime: 1498025574, - compressed: ` -H4sIAAAJbogA/6STX2/TMBTF3/spLl7FgDZN4QFp0Ria2DsP42lTV93ZN/Ed8R/ZrqYy9bsjJ1naFYFA -PCSyj67Pub8b52kCIBRFGdgndlZUIK6oZst5F8FjSCw3LQZIDr56sl+cTciWAlwNx1yAa0+Sa5bYecx7 -09FFVJBzAIQhxfht62mUAASrnKpT8rEqS+fJyueMuHChKaPUZLBkgw2Vakwt927zZ6/Ue4uYAttmr3tM -iUKHd3d7Wdxg8WNZnK32y1cn09fF3XoxWz0t5+8/fNyVf1c2FV3Erk8SihuK6ZDuaLhJE8iw9ck1Ab1m -CVKT/B43Bvqz4GrIRe7+gWSaA9tuOwDA6Tm2jQuctLmozvOoFKmL03+cwMA1e/O5up0t1sVqVN6+q/L6 -srhZFmef1sVqdkS4CW38Ax9Cyz1ELoQ6OAOPmqWGpDkOVGBwC/cEyj3a1qEi9Wv/GAJu9zInMoe5vycF -ELULBvNXEJvAYtB3LzDQWpfw5fX8n7t46Dc2PQ1UZz9FdVw8RGdPyoPfojTor7ve+/cw50l+dpOfAQAA -//8aH/C2mgMAAA== -`, - }, - - "/defs.json": { - local: "defs.json", - size: 1670, - modtime: 1498025574, - compressed: ` -H4sIAAAJbogA/7STza6bMBCF9zzFyO2S9oJtbGDb7hMpy6oLSiaJq2AjY6RWEe9e8RNChFuJKneRgGc8 -3zmeMbcAgByxKa2qnTKa5EC+4klp1a8aaBs8grtY054vpnXgLgi7GvUXo12hNFo41FiqkyqLoTwceTOA -5NBLABClXTqvAIj7XWOvprTDM9qhckhUSquqrUgOn2KaPsLFrykcUzkEu3Amx2IrmlEpfPA+vsIzuhVP -Yy55ygT3aczJlZDgW4UyShmTNGIiTbiUIooij6Jn15N0+x/T8enQJFlxN8/GBxZJwtbozXPxoTnNeCYk -zdb8zePw8eOUcyE5jySTUZYk1Nf8WOxNz7VLQaNxdyI5fJsCMKeG9EeLfZZ8eFt8cG9Ty+eNXeivvp9G -t9frYvf09t3Ti1c6FPy1DhtnlT5vd3jXGOtf66kq6sOAHf99V8n8+Imle9ykunAOrd5bU6N1CptFEQD5 -fIvD7in0ryMEy+fK1G6UfmdTE+tvpoL+1wV/AgAA//96IpqyhgYAAA== -`, - }, - - "/image-index-schema.json": { - local: "image-index-schema.json", - size: 2993, - modtime: 1498025574, - compressed: ` -H4sIAAAJbogA/6yWv27bMBDGdz/FQQmQJYmKIuhgBFnaJVOHBl2KDAx5ki61SPVIJ3ELv3tBMrIlUXZt -1Zt95H33+07892cGkCm0kqlxZHQ2h+xrg/qz0U6QRob7WpQI91rhG3xrUFJBUoSplz733MoKa+HzKuea -eZ4/W6OvYvTacJkrFoW7+nCTx9hZzCPVpth5npsGtWxL2pAWZ+fky+fky8dEt2rQp5qnZ5Quxho2DbIj -tNkcvCWALOZ/R7bRVgynbh8qslAQLhTYaA8tuAohVIZQGaIYvEQ1EBaEBtIOS+SAEJQneMr7mBup1mVS -oyZN9bLO5vBxGxNvbSyE1nEkq4WmAq2zXfutsmAWqw67w7o772g7bbEv7+01W+jxr/Y+wvhrSYy+1o9N -1MOjIvHg0y67YUu/BxFFJVqXbUKPHfGRhZHI9wfSBeLXQpjtPYApwuJgLJBRS1SQWAoi54yFz1ZY2Cu1 -6cm13x1nucKCNPkKNt+SdBTWqelDOP1EIA1PK4d2EusIIGn36WY33Hv/D8GTvGqcKVk0FUmQFcqfdllD -VGhxI+Olt+H/NsI5ZA0Xt2JRGiZX1XfzW78WFaq7i+l9H66boa8lL4arJnUlYEER3U+Hgk0NrxXJCpw/ -V6IXqMUKnhCUedULIxSq6dSBaidzsxCuMFyn3Mdt5o3OgHPnNoY9WzmMCZYVOZRuyTjIA8hMz1NvD8Pe -fZxqp+OT3ed7oTvtsI5Jl9lgwnrM5inxjD0N1PVLckueAm4jexrIAoX/Dqdu4VZ3D2b/suyWTa7Ng00C -rP9p+0UwCZ0erof0cLbrX//IEFobFx50I6fdcV3dHlx5V3XyWdcVmY15aX+te8+ecUeTXmdjNv7HgAcN -mOlZmY29BDtPuBnA42w9+xsAAP//IKe/nbELAAA= -`, - }, - - "/image-layout-schema.json": { - local: "image-layout-schema.json", - size: 439, - modtime: 1498025574, - compressed: ` -H4sIAAAJbogA/2yPQUvEMBCF7/0VQ/Sg4DYVPOW6pwVhD4IX8VDTaTvLNonJVFik/12SaRXRU5g38+W9 -91kBqA6TjRSYvFMG1DGg23vHLTmMcJjaAeGxvfiZ4cmOOLXqLlPXSQYDamQORutT8m4nau3joLvY9rxr -HrRoV8JRtyHJaO0DOruZpYLJtaZsrM/FWEi+BMysfzuhXbUQfcDIhEkZyG2yQyYl8TPGJLVk97fth1yA -74FHhOP+8LvyDbmy8JZ2EgZ6OuNtsS8fbrESR3LDj45unpSBl3UGUPd1UzdqnV/Lu1QAS2kS8X2miN03 -8l+PKnNL9RUAAP//k31n5bcBAAA= -`, - }, - - "/image-manifest-schema.json": { - local: "image-manifest-schema.json", - size: 921, - modtime: 1498025574, - compressed: ` -H4sIAAAJbogA/5ySMW8iMRCF+/0VI0MJ+O501bZXUZxSJEoTpXB2x7uDWNsZmygo4r9HtnHAkCKifTvv -zTdv/dEAiB59x+QCWSNaEHcOzT9rgiKDDOtJDQj/lSGNPsC9w440dSpNL6J97rsRJxWtYwiulXLjrVlm -dWV5kD0rHZa//sqszbKP+mLxrZTWoenKVp9seVpSJJDTkSB7w95hdNuXDXZHzbF1yIHQixbiYQAiRzwi -+3xclq9vfhjJgybc9uDzheghjAhpOZTlkPPgLQeC8qAMkAk4ICeKFH7bZbKG/Uort16tmcjQtJtEC39O -mnovWpIO+YvorNE0nDcwZ9QxNqKhCcvSiOVV/H+ism/VHtmf2wuVYlb7imkdcIqjv099HJVi/ul2gENF -oYyxIb28CuXGus/TFpet9Kj9JdRM9qjJULJU9qawJlLB+Lojxoj19N07rP9JXXED8Nwcms8AAAD//7u3 -Dj+ZAwAA -`, - }, - - "/": { - isDir: true, - local: "/", - }, -} diff --git a/schema/gen.go b/schema/gen.go deleted file mode 100644 index ae78604fd..000000000 --- a/schema/gen.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2016 The Linux Foundation -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package schema - -// Generates an embbedded http.FileSystem for all schema files -// using esc (https://github.com/mjibson/esc). - -// This should generally be invoked with `make schema-fs` -//go:generate esc -private -pkg=schema -include=.*\.json$ . diff --git a/schema/image-index-schema.json b/schema/image-index-schema.json index 8a962aab2..e99131bf0 100644 --- a/schema/image-index-schema.json +++ b/schema/image-index-schema.json @@ -1,6 +1,6 @@ { "description": "OpenContainer Image Index Specification", - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "https://json-schema.org/draft-04/schema#", "id": "https://opencontainers.org/schema/image/index", "type": "object", "properties": { @@ -11,6 +11,10 @@ "minimum": 2, "maximum": 2 }, + "mediaType": { + "description": "the mediatype of the referenced object", + "$ref": "defs-descriptor.json#/definitions/mediaType" + }, "manifests": { "type": "array", "items": { diff --git a/schema/image-layout-schema.json b/schema/image-layout-schema.json index 874d2174c..714ed82a2 100644 --- a/schema/image-layout-schema.json +++ b/schema/image-layout-schema.json @@ -1,6 +1,6 @@ { "description": "OpenContainer Image Layout Schema", - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "https://json-schema.org/draft-04/schema#", "id": "https://opencontainers.org/schema/image/layout", "type": "object", "properties": { diff --git a/schema/image-manifest-schema.json b/schema/image-manifest-schema.json index ec00748e1..8286376eb 100644 --- a/schema/image-manifest-schema.json +++ b/schema/image-manifest-schema.json @@ -1,6 +1,6 @@ { "description": "OpenContainer Image Manifest Specification", - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "https://json-schema.org/draft-04/schema#", "id": "https://opencontainers.org/schema/image/manifest", "type": "object", "properties": { @@ -11,6 +11,10 @@ "minimum": 2, "maximum": 2 }, + "mediaType": { + "description": "the mediatype of the referenced object", + "$ref": "defs-descriptor.json#/definitions/mediaType" + }, "config": { "$ref": "content-descriptor.json" }, diff --git a/schema/imageindex_test.go b/schema/imageindex_test.go index e8ed82f0e..798fbbad0 100644 --- a/schema/imageindex_test.go +++ b/schema/imageindex_test.go @@ -31,6 +31,7 @@ func TestImageIndex(t *testing.T) { imageIndex: ` { "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "invalid", @@ -52,6 +53,7 @@ func TestImageIndex(t *testing.T) { imageIndex: ` { "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", @@ -73,6 +75,7 @@ func TestImageIndex(t *testing.T) { imageIndex: ` { "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", @@ -93,6 +96,7 @@ func TestImageIndex(t *testing.T) { imageIndex: ` { "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", @@ -113,6 +117,7 @@ func TestImageIndex(t *testing.T) { imageIndex: ` { "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "invalid", @@ -134,6 +139,7 @@ func TestImageIndex(t *testing.T) { imageIndex: ` { "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "", @@ -155,6 +161,7 @@ func TestImageIndex(t *testing.T) { imageIndex: ` { "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", @@ -189,6 +196,7 @@ func TestImageIndex(t *testing.T) { imageIndex: ` { "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", @@ -206,6 +214,7 @@ func TestImageIndex(t *testing.T) { imageIndex: ` { "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/customized.manifest+json", diff --git a/schema/loader.go b/schema/loader.go new file mode 100644 index 000000000..d7737582c --- /dev/null +++ b/schema/loader.go @@ -0,0 +1,125 @@ +// Copyright 2018 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package schema + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + + "github.com/xeipuuv/gojsonreference" + "github.com/xeipuuv/gojsonschema" +) + +// fsLoaderFactory implements gojsonschema.JSONLoaderFactory by reading files under the specified namespaces from the root of fs. +type fsLoaderFactory struct { + namespaces []string + fs http.FileSystem +} + +// newFSLoaderFactory returns a fsLoaderFactory reading files under the specified namespaces from the root of fs. +func newFSLoaderFactory(namespaces []string, fs http.FileSystem) *fsLoaderFactory { + return &fsLoaderFactory{ + namespaces: namespaces, + fs: fs, + } +} + +func (factory *fsLoaderFactory) New(source string) gojsonschema.JSONLoader { + return &fsLoader{ + factory: factory, + source: source, + } +} + +// refContents returns the contents of ref, if available in fsLoaderFactory. +func (factory *fsLoaderFactory) refContents(ref gojsonreference.JsonReference) ([]byte, error) { + refStr := ref.String() + path := "" + for _, ns := range factory.namespaces { + if strings.HasPrefix(refStr, ns) { + path = "/" + strings.TrimPrefix(refStr, ns) + break + } + } + if path == "" { + return nil, fmt.Errorf("schema reference %#v unexpectedly not available in fsLoaderFactory with namespaces %#v", path, factory.namespaces) + } + + f, err := factory.fs.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + return io.ReadAll(f) +} + +// fsLoader implements gojsonschema.JSONLoader by reading the document named by source from a fsLoaderFactory. +type fsLoader struct { + factory *fsLoaderFactory + source string +} + +// JsonSource implements gojsonschema.JSONLoader.JsonSource. The "Json" capitalization needs to be maintained to conform to the interface. +func (l *fsLoader) JsonSource() interface{} { // revive:disable-line:var-naming + return l.source +} + +func (l *fsLoader) LoadJSON() (interface{}, error) { + // Based on gojsonschema.jsonReferenceLoader.LoadJSON. + reference, err := gojsonreference.NewJsonReference(l.source) + if err != nil { + return nil, err + } + + refToURL := reference + refToURL.GetUrl().Fragment = "" + + body, err := l.factory.refContents(refToURL) + if err != nil { + return nil, err + } + + return decodeJSONUsingNumber(bytes.NewReader(body)) +} + +// decodeJSONUsingNumber returns JSON parsed from an io.Reader +func decodeJSONUsingNumber(r io.Reader) (interface{}, error) { + // Copied from gojsonschema. + var document interface{} + + decoder := json.NewDecoder(r) + decoder.UseNumber() + + err := decoder.Decode(&document) + if err != nil { + return nil, err + } + + return document, nil +} + +// JsonReference implements gojsonschema.JSONLoader.JsonReference. The "Json" capitalization needs to be maintained to conform to the interface. +func (l *fsLoader) JsonReference() (gojsonreference.JsonReference, error) { // revive:disable-line:var-naming + return gojsonreference.NewJsonReference(l.JsonSource().(string)) +} + +func (l *fsLoader) LoaderFactory() gojsonschema.JSONLoaderFactory { + return l.factory +} diff --git a/schema/manifest_test.go b/schema/manifest_test.go index 6c680f53a..cfc43e973 100644 --- a/schema/manifest_test.go +++ b/schema/manifest_test.go @@ -31,6 +31,7 @@ func TestManifest(t *testing.T) { manifest: ` { "schemaVersion": 2, + "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "invalid", "size": 1470, @@ -53,6 +54,8 @@ func TestManifest(t *testing.T) { manifest: ` { "schemaVersion": 2, + "mediaType" : "application/vnd.oci.image.manifest.v1+json", + "config": { "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": "1470", @@ -75,6 +78,7 @@ func TestManifest(t *testing.T) { manifest: ` { "schemaVersion": 2, + "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, @@ -97,6 +101,7 @@ func TestManifest(t *testing.T) { manifest: ` { "schemaVersion": 2, + "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, @@ -133,6 +138,7 @@ func TestManifest(t *testing.T) { manifest: ` { "schemaVersion": 2, + "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, @@ -165,6 +171,7 @@ func TestManifest(t *testing.T) { manifest: ` { "schemaVersion": 2, + "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, @@ -181,6 +188,7 @@ func TestManifest(t *testing.T) { manifest: ` { "schemaVersion": 2, + "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, @@ -188,17 +196,17 @@ func TestManifest(t *testing.T) { }, "layers": [ { - "mediaType": "application/vnd.oci.image.config.v1+json", + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 1470, "digest": "sha256+foo-bar:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, { - "mediaType": "application/vnd.oci.image.config.v1+json", + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 1470, "digest": "sha256.foo-bar:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" }, { - "mediaType": "application/vnd.oci.image.config.v1+json", + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 1470, "digest": "multihash+base58:QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8" } @@ -212,6 +220,7 @@ func TestManifest(t *testing.T) { manifest: ` { "schemaVersion": 2, + "mediaType" : "application/vnd.oci.image.manifest.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 1470, @@ -219,7 +228,7 @@ func TestManifest(t *testing.T) { }, "layers": [ { - "mediaType": "application/vnd.oci.image.config.v1+json", + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 1470, "digest": "sha256+foo+-b:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" } diff --git a/schema/schema.go b/schema/schema.go index 6a317f139..7a338d8ee 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -15,9 +15,10 @@ package schema import ( + "embed" "net/http" - "github.com/opencontainers/image-spec/specs-go/v1" + v1 "github.com/opencontainers/image-spec/specs-go/v1" ) // Media types for the OCI image formats @@ -33,20 +34,45 @@ const ( var ( // fs stores the embedded http.FileSystem // having the OCI JSON schema files in root "/". - fs = _escFS(false) + //go:embed *.json + fs embed.FS - // specs maps OCI schema media types to schema files. + // 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: "content-descriptor.json", - ValidatorMediaTypeLayoutHeader: "image-layout-schema.json", - ValidatorMediaTypeManifest: "image-manifest-schema.json", - ValidatorMediaTypeImageIndex: "image-index-schema.json", - ValidatorMediaTypeImageConfig: "config-schema.json", + 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 fs + return http.FS(fs) } diff --git a/schema/spec_test.go b/schema/spec_test.go index 2d56627ab..e8dde99f0 100644 --- a/schema/spec_test.go +++ b/schema/spec_test.go @@ -18,9 +18,10 @@ import ( "bytes" "fmt" "io" - "io/ioutil" "net/url" "os" + "path/filepath" + "reflect" "strings" "testing" @@ -53,6 +54,31 @@ 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) @@ -95,7 +121,6 @@ func validate(t *testing.T, name string) { } for _, err := range errs { - // TOOD(stevvooe): This is nearly useless without file, line no. printFields(t, "invalid", example.Mediatype, example.Title) t.Error(err) fmt.Println(example.Body, "---") @@ -157,7 +182,7 @@ func parseExample(lang, body string) (e example) { } func extractExamples(rd io.Reader) ([]example, error) { - p, err := ioutil.ReadAll(rd) + p, err := io.ReadAll(rd) if err != nil { return nil, err } diff --git a/schema/validator.go b/schema/validator.go index b859934aa..e219d38c2 100644 --- a/schema/validator.go +++ b/schema/validator.go @@ -19,11 +19,10 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "regexp" digest "github.com/opencontainers/go-digest" - "github.com/opencontainers/image-spec/specs-go/v1" + v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/xeipuuv/gojsonschema" ) @@ -52,7 +51,7 @@ func (e ValidationError) Error() string { // Validate validates the given reader against the schema of the wrapped media type. func (v Validator) Validate(src io.Reader) error { - buf, err := ioutil.ReadAll(src) + buf, err := io.ReadAll(src) if err != nil { return errors.Wrap(err, "unable to read the document file") } @@ -67,7 +66,7 @@ func (v Validator) Validate(src io.Reader) error { } } - sl := gojsonschema.NewReferenceLoaderFileSystem("file:///"+specs[v], fs) + sl := newFSLoaderFactory(schemaNamespaces, FileSystem()).New(specs[v]) ml := gojsonschema.NewStringLoader(string(buf)) result, err := gojsonschema.Validate(sl, ml) @@ -100,7 +99,7 @@ func (v unimplemented) Validate(src io.Reader) error { func validateManifest(r io.Reader) error { header := v1.Manifest{} - buf, err := ioutil.ReadAll(r) + buf, err := io.ReadAll(r) if err != nil { return errors.Wrapf(err, "error reading the io stream") } @@ -117,8 +116,10 @@ func validateManifest(r io.Reader) error { 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) && - layer.MediaType != string(v1.MediaTypeImageLayerNonDistributableGzip) { + layer.MediaType != string(v1.MediaTypeImageLayerNonDistributableGzip) && + layer.MediaType != string(v1.MediaTypeImageLayerNonDistributableZstd) { fmt.Printf("warning: layer %s has an unknown media type: %s\n", layer.Digest, layer.MediaType) } } @@ -128,7 +129,7 @@ func validateManifest(r io.Reader) error { func validateDescriptor(r io.Reader) error { header := v1.Descriptor{} - buf, err := ioutil.ReadAll(r) + buf, err := io.ReadAll(r) if err != nil { return errors.Wrapf(err, "error reading the io stream") } @@ -150,7 +151,7 @@ func validateDescriptor(r io.Reader) error { func validateIndex(r io.Reader) error { header := v1.Index{} - buf, err := ioutil.ReadAll(r) + buf, err := io.ReadAll(r) if err != nil { return errors.Wrapf(err, "error reading the io stream") } @@ -166,6 +167,7 @@ func validateIndex(r io.Reader) error { } if manifest.Platform != nil { checkPlatform(manifest.Platform.OS, manifest.Platform.Architecture) + checkArchitecture(manifest.Platform.Architecture, manifest.Platform.Variant) } } @@ -176,7 +178,7 @@ func validateIndex(r io.Reader) error { func validateConfig(r io.Reader) error { header := v1.Image{} - buf, err := ioutil.ReadAll(r) + buf, err := io.ReadAll(r) if err != nil { return errors.Wrapf(err, "error reading the io stream") } @@ -187,6 +189,7 @@ func validateConfig(r io.Reader) error { } checkPlatform(header.OS, header.Architecture) + checkArchitecture(header.Architecture, header.Variant) envRegexp := regexp.MustCompile(`^[^=]+=.*$`) for _, e := range header.Config.Env { @@ -198,6 +201,31 @@ func validateConfig(r io.Reader) error { 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": {""}, + } + 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"}, @@ -217,8 +245,8 @@ func checkPlatform(OS string, Architecture string) { return } } - fmt.Printf("warning: combination of %q and %q is invalid.", OS, Architecture) + 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.", OS) + fmt.Printf("warning: operating system %q of the bundle is not supported yet.\n", OS) } diff --git a/spec.md b/spec.md index bf8474389..9250d9452 100644 --- a/spec.md +++ b/spec.md @@ -7,7 +7,6 @@ The goal of this specification is to enable the creation of interoperable tools ### Table of Contents -- [Introduction](spec.md) - [Notational Conventions](#notational-conventions) - [Overview](#overview) - [Understanding the Specification](#understanding-the-specification) @@ -26,7 +25,7 @@ The goal of this specification is to enable the creation of interoperable tools ## Notational Conventions -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" are to be interpreted as described in [RFC 2119](http://tools.ietf.org/html/rfc2119) (Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997). +The key words "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]. @@ -65,5 +64,5 @@ Future versions of this specification may include the following OPTIONAL feature * Signatures that are based on signing image content address * Naming that is federated based on DNS and can be delegated -[c99-unspecified]: http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf#page=18 +[c99-unspecified]: https://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf#page=18 [runtime-spec]: https://github.com/opencontainers/runtime-spec diff --git a/specs-go/v1/annotations.go b/specs-go/v1/annotations.go index 35d810895..581cf7cdf 100644 --- a/specs-go/v1/annotations.go +++ b/specs-go/v1/annotations.go @@ -53,4 +53,10 @@ const ( // AnnotationDescription is the annotation key for the human-readable description of the software packaged in the image. AnnotationDescription = "org.opencontainers.image.description" + + // AnnotationBaseImageDigest is the annotation key for the digest of the image's base image. + AnnotationBaseImageDigest = "org.opencontainers.image.base.digest" + + // AnnotationBaseImageName is the annotation key for the image reference of the image's base image. + AnnotationBaseImageName = "org.opencontainers.image.base.name" ) diff --git a/specs-go/v1/config.go b/specs-go/v1/config.go index fe799bd69..ffff4b6d1 100644 --- a/specs-go/v1/config.go +++ b/specs-go/v1/config.go @@ -89,9 +89,20 @@ type Image struct { // Architecture is the CPU architecture which the binaries in this image are built to run on. Architecture string `json:"architecture"` + // Variant is the variant of the specified CPU architecture which image binaries are intended to run on. + Variant string `json:"variant,omitempty"` + // OS is the name of the operating system which the image is built to run on. 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"` + // Config defines the execution parameters which should be used as a base when running a container using the image. Config ImageConfig `json:"config,omitempty"` diff --git a/specs-go/v1/descriptor.go b/specs-go/v1/descriptor.go index 6e442a085..94f19be62 100644 --- a/specs-go/v1/descriptor.go +++ b/specs-go/v1/descriptor.go @@ -35,6 +35,11 @@ type Descriptor struct { // 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. diff --git a/specs-go/v1/index.go b/specs-go/v1/index.go index 82da6c6a8..ed4a56e59 100644 --- a/specs-go/v1/index.go +++ b/specs-go/v1/index.go @@ -21,7 +21,7 @@ import "github.com/opencontainers/image-spec/specs-go" type Index struct { specs.Versioned - // MediaType specificies the type of this document data structure e.g. `application/vnd.oci.image.index.v1+json` + // MediaType specifies the type of this document data structure e.g. `application/vnd.oci.image.index.v1+json` MediaType string `json:"mediaType,omitempty"` // Manifests references platform specific manifests. diff --git a/specs-go/v1/manifest.go b/specs-go/v1/manifest.go index d72d15ce4..8212d520c 100644 --- a/specs-go/v1/manifest.go +++ b/specs-go/v1/manifest.go @@ -20,7 +20,7 @@ import "github.com/opencontainers/image-spec/specs-go" type Manifest struct { specs.Versioned - // MediaType specificies the type of this document data structure e.g. `application/vnd.oci.image.manifest.v1+json` + // MediaType specifies the type of this document data structure e.g. `application/vnd.oci.image.manifest.v1+json` MediaType string `json:"mediaType,omitempty"` // Config references a configuration object for a container, by digest. diff --git a/specs-go/v1/mediatype.go b/specs-go/v1/mediatype.go index bad7bb97f..4f35ac134 100644 --- a/specs-go/v1/mediatype.go +++ b/specs-go/v1/mediatype.go @@ -34,6 +34,10 @@ const ( // referenced by the manifest. MediaTypeImageLayerGzip = "application/vnd.oci.image.layer.v1.tar+gzip" + // MediaTypeImageLayerZstd is the media type used for zstd compressed + // layers referenced by the manifest. + MediaTypeImageLayerZstd = "application/vnd.oci.image.layer.v1.tar+zstd" + // MediaTypeImageLayerNonDistributable is the media type for layers referenced by // the manifest but with distribution restrictions. MediaTypeImageLayerNonDistributable = "application/vnd.oci.image.layer.nondistributable.v1.tar" @@ -43,6 +47,11 @@ const ( // restrictions. 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. + MediaTypeImageLayerNonDistributableZstd = "application/vnd.oci.image.layer.nondistributable.v1.tar+zstd" + // MediaTypeImageConfig specifies the media type for the image configuration. MediaTypeImageConfig = "application/vnd.oci.image.config.v1+json" )