From fe363aa07d34aff4b3d821a22c8f72a78c088c6d Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 15 Sep 2016 10:36:42 +0200 Subject: [PATCH] *: move to opencontainers/image-tools Signed-off-by: Antonio Murdaca --- .gitignore | 7 +- .tool/lint | 1 + .travis.yml | 8 +- ChangeLog | 19 - HACKING.md | 124 -- Makefile | 99 +- README.md | 76 +- .../main.go} | 25 +- .../oci-create-runtime-bundle.1.md | 42 + cmd/oci-image-tool/README.md | 24 - cmd/oci-image-tool/main.go | 41 - .../oci-image-tool-create-runtime-bundle.1.md | 42 - .../man/oci-image-tool-validate.1.md | 39 - cmd/oci-image-tool/man/oci-image-tool.1.md | 37 - .../main.go} | 37 +- .../oci-image-validate.1.md | 39 + .../unpack.go => oci-unpack/main.go} | 23 +- .../oci-unpack.1.md} | 16 +- config.md | 367 ----- descriptor.md | 117 -- glide.lock | 22 +- glide.yaml | 17 +- image-layout.md | 109 -- {cmd/oci-image-tool => image}/autodetect.go | 28 +- img/build-diagram.png | Bin 26336 -> 0 bytes img/media-types.dot | 12 - img/media-types.png | Bin 33088 -> 0 bytes img/run-diagram.png | Bin 14368 -> 0 bytes layer.md | 282 ---- manifest.md | 205 --- media-types.md | 49 - project.md | 2 +- schema/config-schema.json | 33 - schema/config_test.go | 165 -- schema/content-descriptor.json | 24 - schema/defs-config.json | 105 -- schema/defs-image.json | 88 -- schema/defs.json | 168 -- schema/image-manifest-schema.json | 36 - schema/manifest-list-schema.json | 32 - .../manifest_backwards_compatibility_test.go | 229 --- schema/manifest_test.go | 124 -- schema/spec_test.go | 183 --- .../opencontainers/image-spec/LICENSE | 191 +++ .../opencontainers/image-spec/schema}/doc.go | 0 .../image-spec/schema}/error.go | 0 .../opencontainers/image-spec/schema}/fs.go | 0 .../opencontainers/image-spec/schema}/gen.go | 0 .../image-spec/schema}/schema.go | 0 .../image-spec/schema}/validator.go | 0 .../image-spec/specs-go}/descriptor.go | 0 .../image-spec/specs-go}/v1/config.go | 0 .../image-spec/specs-go}/v1/manifest.go | 0 .../image-spec/specs-go}/v1/manifest_list.go | 0 .../image-spec/specs-go}/v1/mediatype.go | 0 .../image-spec/specs-go}/version.go | 0 .../image-spec/specs-go}/versioned.go | 0 vendor/github.com/pkg/errors/errors.go | 143 +- vendor/github.com/pkg/errors/stack.go | 13 + .../russross/blackfriday/LICENSE.txt | 29 - .../github.com/russross/blackfriday/block.go | 1398 ----------------- .../github.com/russross/blackfriday/html.go | 949 ----------- .../github.com/russross/blackfriday/inline.go | 1133 ------------- .../github.com/russross/blackfriday/latex.go | 332 ---- .../russross/blackfriday/markdown.go | 926 ----------- .../russross/blackfriday/smartypants.go | 400 ----- .../shurcooL/sanitized_anchor_name/LICENSE | 19 - .../shurcooL/sanitized_anchor_name/main.go | 29 - .../spf13/cobra/bash_completions.go | 21 +- vendor/github.com/spf13/cobra/cobra.go | 36 +- vendor/github.com/spf13/cobra/command.go | 97 +- vendor/github.com/spf13/pflag/flag.go | 63 +- vendor/github.com/spf13/pflag/string_array.go | 110 ++ vendor/github.com/spf13/pflag/string_slice.go | 31 +- 74 files changed, 754 insertions(+), 8262 deletions(-) delete mode 100644 ChangeLog delete mode 100644 HACKING.md rename cmd/{oci-image-tool/create_runtime_bundle.go => oci-create-runtime-bundle/main.go} (85%) create mode 100644 cmd/oci-create-runtime-bundle/oci-create-runtime-bundle.1.md delete mode 100644 cmd/oci-image-tool/README.md delete mode 100644 cmd/oci-image-tool/main.go delete mode 100644 cmd/oci-image-tool/man/oci-image-tool-create-runtime-bundle.1.md delete mode 100644 cmd/oci-image-tool/man/oci-image-tool-validate.1.md delete mode 100644 cmd/oci-image-tool/man/oci-image-tool.1.md rename cmd/{oci-image-tool/validate.go => oci-image-validate/main.go} (84%) create mode 100644 cmd/oci-image-validate/oci-image-validate.1.md rename cmd/{oci-image-tool/unpack.go => oci-unpack/main.go} (85%) rename cmd/{oci-image-tool/man/oci-image-tool-unpack.1.md => oci-unpack/oci-unpack.1.md} (55%) delete mode 100644 config.md delete mode 100644 descriptor.md delete mode 100644 image-layout.md rename {cmd/oci-image-tool => image}/autodetect.go (84%) delete mode 100644 img/build-diagram.png delete mode 100644 img/media-types.dot delete mode 100644 img/media-types.png delete mode 100644 img/run-diagram.png delete mode 100644 layer.md delete mode 100644 manifest.md delete mode 100644 media-types.md delete mode 100644 schema/config-schema.json delete mode 100644 schema/config_test.go delete mode 100644 schema/content-descriptor.json delete mode 100644 schema/defs-config.json delete mode 100644 schema/defs-image.json delete mode 100644 schema/defs.json delete mode 100644 schema/image-manifest-schema.json delete mode 100644 schema/manifest-list-schema.json delete mode 100644 schema/manifest_backwards_compatibility_test.go delete mode 100644 schema/manifest_test.go delete mode 100644 schema/spec_test.go create mode 100644 vendor/github.com/opencontainers/image-spec/LICENSE rename {schema => vendor/github.com/opencontainers/image-spec/schema}/doc.go (100%) rename {schema => vendor/github.com/opencontainers/image-spec/schema}/error.go (100%) rename {schema => vendor/github.com/opencontainers/image-spec/schema}/fs.go (100%) rename {schema => vendor/github.com/opencontainers/image-spec/schema}/gen.go (100%) rename {schema => vendor/github.com/opencontainers/image-spec/schema}/schema.go (100%) rename {schema => vendor/github.com/opencontainers/image-spec/schema}/validator.go (100%) rename {specs-go => vendor/github.com/opencontainers/image-spec/specs-go}/descriptor.go (100%) rename {specs-go => vendor/github.com/opencontainers/image-spec/specs-go}/v1/config.go (100%) rename {specs-go => vendor/github.com/opencontainers/image-spec/specs-go}/v1/manifest.go (100%) rename {specs-go => vendor/github.com/opencontainers/image-spec/specs-go}/v1/manifest_list.go (100%) rename {specs-go => vendor/github.com/opencontainers/image-spec/specs-go}/v1/mediatype.go (100%) rename {specs-go => vendor/github.com/opencontainers/image-spec/specs-go}/version.go (100%) rename {specs-go => vendor/github.com/opencontainers/image-spec/specs-go}/versioned.go (100%) delete mode 100644 vendor/github.com/russross/blackfriday/LICENSE.txt delete mode 100644 vendor/github.com/russross/blackfriday/block.go delete mode 100644 vendor/github.com/russross/blackfriday/html.go delete mode 100644 vendor/github.com/russross/blackfriday/inline.go delete mode 100644 vendor/github.com/russross/blackfriday/latex.go delete mode 100644 vendor/github.com/russross/blackfriday/markdown.go delete mode 100644 vendor/github.com/russross/blackfriday/smartypants.go delete mode 100644 vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE delete mode 100644 vendor/github.com/shurcooL/sanitized_anchor_name/main.go create mode 100644 vendor/github.com/spf13/pflag/string_array.go diff --git a/.gitignore b/.gitignore index 50f67b5..d897571 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -code-of-conduct.md -/oci-image-tool -/oci-validate-examples -output +/oci-create-runtime-bundle +/oci-unpack +/oci-image-validate diff --git a/.tool/lint b/.tool/lint index a3b0ecb..4a0120e 100755 --- a/.tool/lint +++ b/.tool/lint @@ -15,6 +15,7 @@ for d in $(find . -type d -not -iwholename '*.git*' -a -not -iname '.tool' -a -n --exclude='.*_test\.go:.*error return value not checked.*\(errcheck\)$' \ --exclude='duplicate of.*_test.go.*\(dupl\)$' \ --exclude='schema/fs.go' \ + --exclude='duplicate of.*main.go.*\(dupl\)$' \ --disable=aligncheck \ --disable=gotype \ --disable=gas \ diff --git a/.travis.yml b/.travis.yml index 6dc0f18..48eecfb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,14 @@ language: go go: - 1.6 + - 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 --update @@ -24,5 +21,4 @@ script: - make lint - make check-license - make test - - make oci-image-tool - - make docs + - make tools diff --git a/ChangeLog b/ChangeLog deleted file mode 100644 index 755235d..0000000 --- a/ChangeLog +++ /dev/null @@ -1,19 +0,0 @@ -Open Container Initiative Image Format Specification - -Changes with v0.4.0: - - Additions: - - * Go package added for types - https://godoc.org/github.com/opencontainers/image-spec/specs-go/v1 (#172) - * oci-image-tool: man page docs added - https://github.com/opencontainers/image-spec/tree/master/cmd/oci-image-tool/man - (#180) - - Minor fixes and documentation: - - * oci-image-tool: replace colon with hyphen to be windows friendly (#177) - * oci-image-tool: fix various bugs in extraction of images (#177) - * manifest: size as int64 allowing for huge containers (#153) - * manifest: move duplicate descriptions to reference descriptor doc (#167) - * serialization: explain opaques and their role (#171) diff --git a/HACKING.md b/HACKING.md deleted file mode 100644 index 45f59b3..0000000 --- a/HACKING.md +++ /dev/null @@ -1,124 +0,0 @@ -# Hacking Guide - -## Overview - -This guide contains instructions for building artifacts contained in this repository. - -### Go - -This spec includes several Go packages, and a command line tool considered to be a reference implementation of the OCI image specification. - -Prerequsites: -* Go >=1.5 -* make - -The following make targets are relevant for any work involving the Go packages. - -### Linting - -The included Go source code is being examined for any linting violations not included in the standard Go compiler. Linting is done using [gometalinter](https://github.com/alecthomas/gometalinter). - -Invocation: -``` -$ make lint -``` - -### Tests - -This target executes all Go based tests. - -Invocation: -``` -$ make test -$ make validate-examples -``` - -### OCI image tool - -This target builds the `oci-image-tool` binary. - -Invocation: -``` -$ make oci-image-tool -``` - -### Virtual schema http/FileSystem - -The `oci-image-tool` uses a virtual [http/FileSystem](https://golang.org/pkg/net/http/#FileSystem) to load the JSON schema files for validating OCI images and/or manifests. The virtual file system is generated using the `esc` tool and compiled into the `oci-image-tool` binary so the JSON schema files don't have to be distributed along with the binary. - -Whenever changes are being done in any of the `schema/*.json` files, one must refresh the generated virtual file system. Otherwise schema changes will not be visible inside the `oci-image-tool`. - -Prerequisites: -* [esc](https://github.com/mjibson/esc) - -Invocation: -``` -$ make schema-fs -``` - -### JSON schema formatting - -This target auto-formats all JSON files in the `schema` directory using the `jq` tool. - -Prerequisites: -* [jq](https://stedolan.github.io/jq/) - -Invocation: -``` -$ make fmt -``` - -### OCI image specification PDF/HTML documentation files - -This target generates a PDF/HTML version of the OCI image specification. - -Prerequisites: -* [Docker](https://www.docker.com/) - -Invocation: -``` -$ make docs -``` - -### License header check - -This target checks if the source code includes necessary headers. - -Invocation: -``` -$ make check-license -``` - -### Update vendored dependencies - -This target updates all vendored depencies to their newest available versions. The `glide` tools is being used for the actual management and `glide-vc` tool is being used for stripping down the vendor directory size. - -Prerequisites: -* [glide](https://github.com/Masterminds/glide) -* [glide-vc](https://github.com/sgotti/glide-vc) - -Invocation: -``` -$ make update-deps -``` - -### Clean build artifacts - -This target cleans all generated/compiled artifacts. - -Invocation: -``` -$ make clean -``` - -### Create PNG images from dot files - -This target generates PNG image files from DOT source files in the `img` directory. - -Prerequisites: -* [graphviz](http://www.graphviz.org/) - -Invocation: -``` -$ make img/media-types.png -``` diff --git a/Makefile b/Makefile index 02c3245..3654a39 100644 --- a/Makefile +++ b/Makefile @@ -1,42 +1,6 @@ GO15VENDOREXPERIMENT=1 export GO15VENDOREXPERIMENT -DOCKER ?= $(shell command -v docker 2>/dev/null) -PANDOC ?= $(shell command -v pandoc 2>/dev/null) - -ifeq "$(strip $(PANDOC))" '' - ifneq "$(strip $(DOCKER))" '' - PANDOC = $(DOCKER) run \ - -it \ - --rm \ - -v $(shell pwd)/:/input/:ro \ - -v $(shell pwd)/$(OUTPUT_DIRNAME)/:/$(OUTPUT_DIRNAME)/ \ - -u $(shell id -u) \ - --workdir /input \ - vbatts/pandoc - PANDOC_SRC := /input/ - PANDOC_DST := / - endif -endif - -# These docs are in an order that determines how they show up in the PDF/HTML docs. -DOC_FILES := \ - README.md \ - code-of-conduct.md \ - project.md \ - media-types.md \ - descriptor.md \ - image-layout.md \ - layer.md \ - config.md \ - manifest.md - -FIGURE_FILES := \ - img/media-types.png - -OUTPUT_DIRNAME ?= output/ -DOC_FILENAME ?= oci-image-spec - EPOCH_TEST_COMMIT ?= v0.2.0 default: help @@ -44,55 +8,21 @@ default: help help: @echo "Usage: make " @echo - @echo " * 'docs' - produce document in the $(OUTPUT_DIRNAME) directory" - @echo " * 'fmt' - format the json with indentation" - @echo " * 'validate-examples' - validate the examples in the specification markdown files" - @echo " * 'oci-image-tool' - build the oci-image-tool binary" - @echo " * 'schema-fs' - regenerate the virtual schema http/FileSystem" + @echo " * 'tools' - build the oci image tools binaries" @echo " * 'check-license' - check license headers in source files" @echo " * 'lint' - Execute the source code linter" @echo " * 'test' - Execute the unit tests" @echo " * 'update-deps' - Update vendored dependencies" - @echo " * 'img/*.png' - Generate PNG from dot file" - -fmt: - for i in schema/*.json ; do jq --indent 2 -M . "$${i}" > xx && cat xx > "$${i}" && rm xx ; done - -docs: $(OUTPUT_DIRNAME)/$(DOC_FILENAME).pdf $(OUTPUT_DIRNAME)/$(DOC_FILENAME).html - -ifeq "$(strip $(PANDOC))" '' -$(OUTPUT_DIRNAME)/$(DOC_FILENAME).pdf: $(DOC_FILES) $(FIGURE_FILES) - $(error cannot build $@ without either pandoc or docker) -else -$(OUTPUT_DIRNAME)/$(DOC_FILENAME).pdf: $(DOC_FILES) $(FIGURE_FILES) - @mkdir -p $(OUTPUT_DIRNAME)/ && \ - $(PANDOC) -f markdown_github -t latex -o $(PANDOC_DST)$@ $(patsubst %,$(PANDOC_SRC)%,$(DOC_FILES)) - ls -sh $(shell readlink -f $@) - -$(OUTPUT_DIRNAME)/$(DOC_FILENAME).html: $(DOC_FILES) $(FIGURE_FILES) - @mkdir -p $(OUTPUT_DIRNAME)/ && \ - cp -ap img/ $(shell pwd)/$(OUTPUT_DIRNAME)/&& \ - $(PANDOC) -f markdown_github -t html5 -o $(PANDOC_DST)$@ $(patsubst %,$(PANDOC_SRC)%,$(DOC_FILES)) - ls -sh $(shell readlink -f $@) -endif - -code-of-conduct.md: - curl -o $@ https://raw.githubusercontent.com/opencontainers/tob/d2f9d68c1332870e40693fe077d311e0742bc73d/code-of-conduct.md - -validate-examples: - go test -run TestValidate ./schema - -oci-image-tool: - go build ./cmd/oci-image-tool - -schema-fs: - @echo "generating schema fs" - @cd schema && printf "%s\n\n%s\n" "$$(cat ../.header)" "$$(go generate)" > fs.go check-license: @echo "checking license headers" @./.tool/check-license +tools: + go build ./cmd/oci-create-runtime-bundle + go build ./cmd/oci-unpack + go build ./cmd/oci-image-validate + lint: @echo "checking lint" @./.tool/lint @@ -108,9 +38,6 @@ update-deps: # see http://sed.sourceforge.net/sed1line.txt find vendor -type f -exec sed -i -e :a -e '/^\n*$$/{$$d;N;ba' -e '}' "{}" \; -img/%.png: img/%.dot - dot -Tpng $^ > $@ - .PHONY: .gitvalidation # When this is running in travis, it will only check the travis commit range @@ -124,7 +51,7 @@ endif .PHONY: install.tools -install.tools: .install.gitvalidation .install.glide .install.glide-vc +install.tools: .install.gitvalidation .install.glide .install.glide-vc .install.gometalinter .install.gitvalidation: go get github.com/vbatts/git-validation @@ -135,15 +62,19 @@ install.tools: .install.gitvalidation .install.glide .install.glide-vc .install.glide-vc: go get github.com/sgotti/glide-vc +.install.gometalinter: + go get github.com/alecthomas/gometalinter + gometalinter --install --update + clean: rm -rf *~ $(OUTPUT_DIRNAME) - rm -f oci-image-tool + rm -f oci-create-runtime-bundle + rm -f oci-unpack + rm -f oci-image-validate .PHONY: \ - validate-examples \ - oci-image-tool \ + tools \ check-license \ clean \ lint \ - docs \ test diff --git a/README.md b/README.md index d16229a..fd0c08e 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,12 @@ -# Open Container Initiative Image Format Specification +# image-tools -The OCI Image Format project creates and maintains the software shipping container image format spec (OCI Image Format). The goal of this specification is to enable the creation of interoperable tools for building, transporting, and preparing a container image to run. - -This specification defines how to create an OCI Image, which will generally be done by a build system, and output an [image manifest](manifest.md), a set of [filesystem layers](layer.md), and an [image configuration](config.md). -At a high level the image manifest contains metadata about the contents and dependencies of the image including the content-addressable identity of one or more [filesystem layer changeset](layer.md) archives that will be unpacked to make up the final runnable filesystem. -The image configuration includes information such as application arguments, environments, etc. -The combination of the image manifest, image configuration, and one or more filesystem layers is called the "OCI Image". - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD 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). - -![](img/build-diagram.png) - -Once built the OCI Image can then be discovered by name, downloaded, verified by hash, trusted through a signature, and unpacked into an [OCI Runtime Bundle](https://github.com/opencontainers/runtime-spec/blob/master/bundle.md). - -![](img/run-diagram.png) - -## Understanding the Specification - -The [Media Types](media-types.md) document is a starting point to understanding the overall structure of the specification. This document outlines the OCI Image file format specifications, including the [filesystem layer changesets](layer.md) and image manifest described above. - -The high level components of the spec include: - -* An [image manifest](manifest.md), a set of [filesystem layers](layer.md), and [image configuration](config.md) (base layer) -* A process of hashing the image format for integrity and content-addressing (base layer) -* Signatures that are based on signing image content address (optional layer) -* Naming that is federated based on DNS and can be delegated (optional layer) - -The optional and base layers of all OCI projects are tracked in the [OCI Scope Table](https://www.opencontainers.org/governance/oci-scope-table). - -## Running an OCI Image - -The OCI Image Format partner project is the [OCI Runtime Spec project](https://github.com/opencontainers/runtime-spec). The Runtime Specification outlines how to run a "[filesystem bundle](https://github.com/opencontainers/runtime-spec/blob/master/bundle.md)" that is unpacked on disk. At a high-level an OCI implementation would download an OCI Image then unpack that image into an OCI Runtime filesystem bundle. At this point the OCI Runtime Bundle would be run by an OCI Runtime. - -This entire workflow supports the UX that users have come to expect from container engines like Docker and rkt: primarily, the ability to run an image with no additional arguments: - -* docker run example.com/org/app:v1.0.0 -* rkt run example.com/org/app,version=v1.0.0 - -To support this UX the OCI Image Format contains sufficient information to launch the application on the target platform (e.g. command, arguments, environment variables, etc). - -## FAQ - -**Q: Why doesn't this project mention distribution?** - -A: Distribution, for example using HTTP as both Docker v2.2 and AppC do today, is currently out of scope on the [OCI Scope Table](https://www.opencontainers.org/governance/oci-scope-table). There has been [some discussion on the TOB mailing list]( https://groups.google.com/a/opencontainers.org/d/msg/tob/A3JnmI-D-6Y/tLuptPDHAgAJ) to make distribution an optional layer but this topic is a work in progress. - -**Q: Why a new project?** - -A: The first OCI spec centered around defining the run side of a container. This is generally seen to be an orthogonal concern to the shipping container component. As practical examples of this separation you see many organizations separating these concerns into different teams and organizations: the Docker Distribution project and the Docker containerd project; Amazon ECS and Amazon EC2 Container Registry, etc. - -**Q: Why start this work now?** - -A: We are seeing many independent implementations of container image handling including build systems, registries, and image analysis tools. As an organization we would like to encourage this growth and bring people together to ensure a technically correct and open specification continues to evolve reflecting the OCI values. - -**Q: What happens to AppC or Docker Image Formats?** - -A: Existing formats can continue to be a proving ground for technologies, as needed. The OCI Image Format project strives to provide a dependable open specification that can be shared between different tools and be evolved for years or decades of compatibility; as the deb and rpm format have. - -## Roadmap - -The [GitHub milestones](https://github.com/opencontainers/image-spec/milestones) lays out the path to the OCI v1.0.0 release in June 2016. +`image-tools` is a collection of tools for working with the [OCI image format specification](https://github.com/opencontainers/image-spec). # Contributing -Development happens on GitHub for the spec. -Issues are used for bugs and actionable items and longer discussions can happen on the [mailing list](#mailing-list). +Development happens on GitHub. Issues are used for bugs and actionable items and longer discussions can happen on the [mailing list](#mailing-list). -The specification and code is licensed under the Apache 2.0 license found in the `LICENSE` file of this repository. +The code is licensed under the Apache 2.0 license found in the `LICENSE` file of this repository. ## Code of Conduct @@ -76,7 +16,7 @@ Participation in the OpenContainers community is governed by [OpenContainer's Co The project welcomes submissions, but please let everyone know what you are working on. -Before undertaking a nontrivial change to this specification, send mail to the [mailing list](#mailing-list) to discuss what you plan to do. +Before undertaking a nontrivial change to this repository, send mail to the [mailing list](#mailing-list) to discuss what you plan to do. This gives everyone a chance to validate the design, helps prevent duplication of effort, and ensures that the idea fits. It also guarantees that the design is sound before code is written; a GitHub pull-request is not the place for high-level discussions. @@ -98,12 +38,6 @@ You can subscribe and join the mailing list on [Google Groups](https://groups.go OCI discussion happens on #opencontainers on Freenode ([logs][irc-logs]). -## Markdown style - -To keep consistency throughout the Markdown files in the Open Container spec all files should be formatted one sentence per line. -This fixes two things: it makes diffing easier with git and it resolves fights about line wrapping length. -For example, this paragraph will span three lines in the Markdown source. - ## Git commit ### Sign your work diff --git a/cmd/oci-image-tool/create_runtime_bundle.go b/cmd/oci-create-runtime-bundle/main.go similarity index 85% rename from cmd/oci-image-tool/create_runtime_bundle.go rename to cmd/oci-create-runtime-bundle/main.go index 57b6745..e9b99ab 100644 --- a/cmd/oci-image-tool/create_runtime_bundle.go +++ b/cmd/oci-create-runtime-bundle/main.go @@ -20,14 +20,14 @@ import ( "os" "strings" - "github.com/opencontainers/image-spec/image" + "github.com/opencontainers/image-tools/image" "github.com/spf13/cobra" ) // supported bundle types var bundleTypes = []string{ - typeImageLayout, - typeImage, + image.TypeImageLayout, + image.TypeImage, } type bundleCmd struct { @@ -38,6 +38,17 @@ type bundleCmd struct { root string } +func main() { + stdout := log.New(os.Stdout, "", 0) + stderr := log.New(os.Stderr, "", 0) + + cmd := newBundleCmd(stdout, stderr) + if err := cmd.Execute(); err != nil { + stderr.Println(err) + os.Exit(1) + } +} + func newBundleCmd(stdout, stderr *log.Logger) *cobra.Command { v := &bundleCmd{ stdout: stdout, @@ -45,7 +56,7 @@ func newBundleCmd(stdout, stderr *log.Logger) *cobra.Command { } cmd := &cobra.Command{ - Use: "create-runtime-bundle [src] [dest]", + Use: "oci-create-runtime-bundle [src] [dest]", Short: "Create an OCI image runtime bundle", Long: `Creates an OCI image runtime bundle at the destination directory [dest] from an OCI image present at [src].`, Run: v.Run, @@ -88,7 +99,7 @@ func (v *bundleCmd) Run(cmd *cobra.Command, args []string) { } if v.typ == "" { - typ, err := autodetect(args[0]) + typ, err := image.Autodetect(args[0]) if err != nil { v.stderr.Printf("%q: autodetection failed: %v", args[0], err) os.Exit(1) @@ -98,10 +109,10 @@ func (v *bundleCmd) Run(cmd *cobra.Command, args []string) { var err error switch v.typ { - case typeImageLayout: + case image.TypeImageLayout: err = image.CreateRuntimeBundleLayout(args[0], args[1], v.ref, v.root) - case typeImage: + case image.TypeImage: err = image.CreateRuntimeBundle(args[0], args[1], v.ref, v.root) } diff --git a/cmd/oci-create-runtime-bundle/oci-create-runtime-bundle.1.md b/cmd/oci-create-runtime-bundle/oci-create-runtime-bundle.1.md new file mode 100644 index 0000000..ad95181 --- /dev/null +++ b/cmd/oci-create-runtime-bundle/oci-create-runtime-bundle.1.md @@ -0,0 +1,42 @@ +% OCI(1) OCI-CREATE-RUNTIME-BUNDLE User Manuals +% OCI Community +% JULY 2016 +# NAME +oci-create-runtime-bundle \- Create an OCI runtime bundle + +# SYNOPSIS +**oci-create-runtime-bundle** [src] [dest] [flags] + +# DESCRIPTION +`oci-create-runtime-bundle` validates an application/vnd.oci.image.manifest.v1+json and unpacks its layered filesystem to `dest/rootfs`, although the target directory is configurable with `--rootfs`. See **oci-unpack**(1) for more details on this process. + +Also translates the referenced config from application/vnd.oci.image.config.v1+json to a +runtime-spec-compatible `dest/config.json`. + +# OPTIONS +**--help** + Print usage statement + +**--ref** + The ref pointing to the manifest of the OCI image. This must be present in the "refs" subdirectory of the image. (default "v1.0") + +**--rootfs** + A directory representing the root filesystem of the container in the OCI runtime bundle. It is strongly recommended to keep the default value. (default "rootfs") + +**--type** + Type of the file to unpack. If unset, oci-create-runtime-bundle will try to auto-detect the type. One of "imageLayout,image" + +# EXAMPLES +``` +$ skopeo copy docker://busybox oci:busybox-oci +$ mkdir busybox-bundle +$ oci-create-runtime-bundle --ref latest busybox-oci busybox-bundle +$ cd busybox-bundle && sudo runc run busybox +[...] +``` + +# SEE ALSO +**runc**(1), **skopeo**(1) + +# HISTORY +Sept 2016, Originally compiled by Antonio Murdaca (runcom at redhat dot com) diff --git a/cmd/oci-image-tool/README.md b/cmd/oci-image-tool/README.md deleted file mode 100644 index e26d175..0000000 --- a/cmd/oci-image-tool/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# oci-image-tool - -A tool for working with OCI images - -## Building - -This project uses the Go programming language and is tested with the [Go -compiler](https://golang.org/dl/). (Results with gccgo may vary) - -Install from a particular version of this repo (i.e. the v0.4.0 tag): - -```bash -go get -u -d github.com/opencontainers/image-spec -cd $GOPATH/src/github.com/opencontainers/image-spec -git checkout -f v0.4.0 -go install github.com/opencontainers/image-spec/cmd/oci-image-tool -``` - -While the tool may be `go get`'able, it is encouraged that it is only used per -tagged releases. - -## Usage - -See the tool's own `--help` output. diff --git a/cmd/oci-image-tool/main.go b/cmd/oci-image-tool/main.go deleted file mode 100644 index 7cd350e..0000000 --- a/cmd/oci-image-tool/main.go +++ /dev/null @@ -1,41 +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 main - -import ( - "log" - "os" - - "github.com/spf13/cobra" -) - -func main() { - cmd := &cobra.Command{ - Use: "oci-image-tool", - Short: "A tool for working with OCI images", - } - - stdout := log.New(os.Stdout, "", 0) - stderr := log.New(os.Stderr, "", 0) - - cmd.AddCommand(newValidateCmd(stdout, stderr)) - cmd.AddCommand(newUnpackCmd(stdout, stderr)) - cmd.AddCommand(newBundleCmd(stdout, stderr)) - - if err := cmd.Execute(); err != nil { - stderr.Println(err) - os.Exit(1) - } -} diff --git a/cmd/oci-image-tool/man/oci-image-tool-create-runtime-bundle.1.md b/cmd/oci-image-tool/man/oci-image-tool-create-runtime-bundle.1.md deleted file mode 100644 index dbb7aa5..0000000 --- a/cmd/oci-image-tool/man/oci-image-tool-create-runtime-bundle.1.md +++ /dev/null @@ -1,42 +0,0 @@ -% OCI(1) OCI-IMAGE-TOOL User Manuals -% OCI Community -% JULY 2016 -# NAME -oci-image-tool-create-runtime-bundle \- Create an OCI runtime bundle - -# SYNOPSIS -**oci-image-tool create-runtime-bundle** [src] [dest] [flags] - -# DESCRIPTION -`oci-image-tool create-runtime-bundle` validates an application/vnd.oci.image.manifest.v1+json and unpacks its layered filesystem to `dest/rootfs`, although the target directory is configurable with `--rootfs`. See **oci-image-tool-unpack**(1) for more details on this process. - -Also translates the referenced config from application/vnd.oci.image.config.v1+json to a -runtime-spec-compatible `dest/config.json`. - -# OPTIONS -**--help** - Print usage statement - -**--ref** - The ref pointing to the manifest of the OCI image. This must be present in the "refs" subdirectory of the image. (default "v1.0") - -**--rootfs** - A directory representing the root filesystem of the container in the OCI runtime bundle. It is strongly recommended to keep the default value. (default "rootfs") - -**--type** - Type of the file to unpack. If unset, oci-image-tool will try to auto-detect the type. One of "imageLayout,image" - -# EXAMPLES -``` -$ skopeo copy docker://busybox oci:busybox-oci -$ mkdir busybox-bundle -$ oci-image-tool create-runtime-bundle --ref latest busybox-oci busybox-bundle -$ cd busybox-bundle && sudo runc run busybox -[...] -``` - -# SEE ALSO -**oci-image-tool**(1), **runc**(1), **skopeo**(1) - -# HISTORY -July 2016, Originally compiled by Antonio Murdaca (runcom at redhat dot com) diff --git a/cmd/oci-image-tool/man/oci-image-tool-validate.1.md b/cmd/oci-image-tool/man/oci-image-tool-validate.1.md deleted file mode 100644 index 49c61a8..0000000 --- a/cmd/oci-image-tool/man/oci-image-tool-validate.1.md +++ /dev/null @@ -1,39 +0,0 @@ -% OCI(1) OCI-IMAGE-TOOL User Manuals -% OCI Community -% JULY 2016 -# NAME -oci-image-tool-validate \- Validate one or more image files - -# SYNOPSIS -**oci-image-tool validate** FILE... [flags] - -# DESCRIPTION -`oci-image-tool validate` validates the given file(s) against the OCI image specification. - - -# OPTIONS -**--help** - Print usage statement - -**--ref** NAME - The reference to validate (should point to a manifest). - Can be specified multiple times to validate multiple references. - `NAME` must be present in the `refs` subdirectory of the image. - Defaults to `v1.0`. - Only applicable if type is image or imageLayout. - -**--type** - Type of the file to validate. If unset, oci-image-tool will try to auto-detect the type. One of "imageLayout,image,manifest,manifestList,config" - -# EXAMPLES -``` -$ skopeo copy docker://busybox oci:busybox-oci -$ oci-image-tool validate --type imageLayout --ref latest busybox-oci -busybox-oci: OK -``` - -# SEE ALSO -**oci-image-tool**(1), **skopeo**(1) - -# HISTORY -July 2016, Originally compiled by Antonio Murdaca (runcom at redhat dot com) diff --git a/cmd/oci-image-tool/man/oci-image-tool.1.md b/cmd/oci-image-tool/man/oci-image-tool.1.md deleted file mode 100644 index d8a5107..0000000 --- a/cmd/oci-image-tool/man/oci-image-tool.1.md +++ /dev/null @@ -1,37 +0,0 @@ -% OCI(1) OCI-IMAGE-TOOL User Manuals -% OCI Community -% JULY 2016 -# NAME -oci-image-tool \- OCI (Open Container Initiative) image tool - -# SYNOPSIS -**oci-image-tool** [OPTIONS] COMMAND [arg...] - -**oci-image-tool** [--help] - -# DESCRIPTION -`oci-image-tool` is a collection of tools for working with the [OCI image specification](https://github.com/opencontainers/image-spec). - - -# OPTIONS -**--help** - Print usage statement - -# COMMANDS -**create-runtime-bundle** - Create an OCI image runtime bundle - See **oci-image-tool-create-runtime-bundle**(1) for full documentation on the **create-runtime-bundle** command. - -**unpack** - Unpack an image or image source layout - See **oci-image-tool-unpack**(1) for full documentation on the **unpack** command. - -**validate** - Validate one or more image files - See **oci-image-tool-validate**(1) for full documentation on the **validate** command. - -# SEE ALSO -**oci-image-tool-create-runtime-bundle**(1), **ocitools-unpack**(1), **ocitools-validate**(1) - -# HISTORY -July 2016, Originally compiled by Antonio Murdaca (runcom at redhat dot com) diff --git a/cmd/oci-image-tool/validate.go b/cmd/oci-image-validate/main.go similarity index 84% rename from cmd/oci-image-tool/validate.go rename to cmd/oci-image-validate/main.go index 6172796..f8849bd 100644 --- a/cmd/oci-image-tool/validate.go +++ b/cmd/oci-image-validate/main.go @@ -20,19 +20,19 @@ import ( "os" "strings" - "github.com/opencontainers/image-spec/image" "github.com/opencontainers/image-spec/schema" + "github.com/opencontainers/image-tools/image" "github.com/pkg/errors" "github.com/spf13/cobra" ) // supported validation types var validateTypes = []string{ - typeImageLayout, - typeImage, - typeManifest, - typeManifestList, - typeConfig, + image.TypeImageLayout, + image.TypeImage, + image.TypeManifest, + image.TypeManifestList, + image.TypeConfig, } type validateCmd struct { @@ -42,6 +42,17 @@ type validateCmd struct { refs []string } +func main() { + stdout := log.New(os.Stdout, "", 0) + stderr := log.New(os.Stderr, "", 0) + + cmd := newValidateCmd(stdout, stderr) + if err := cmd.Execute(); err != nil { + stderr.Println(err) + os.Exit(1) + } +} + func newValidateCmd(stdout, stderr *log.Logger) *cobra.Command { v := &validateCmd{ stdout: stdout, @@ -49,7 +60,7 @@ func newValidateCmd(stdout, stderr *log.Logger) *cobra.Command { } cmd := &cobra.Command{ - Use: "validate FILE...", + Use: "oci-image-validate FILE...", Short: "Validate one or more image files", Run: v.Run, } @@ -118,15 +129,15 @@ func (v *validateCmd) validatePath(name string) error { ) if typ == "" { - if typ, err = autodetect(name); err != nil { + if typ, err = image.Autodetect(name); err != nil { return errors.Wrap(err, "unable to determine type") } } switch typ { - case typeImageLayout: + case image.TypeImageLayout: return image.ValidateLayout(name, v.refs, v.stdout) - case typeImage: + case image.TypeImage: return image.Validate(name, v.refs, v.stdout) } @@ -137,11 +148,11 @@ func (v *validateCmd) validatePath(name string) error { defer f.Close() switch typ { - case typeManifest: + case image.TypeManifest: return schema.MediaTypeManifest.Validate(f) - case typeManifestList: + case image.TypeManifestList: return schema.MediaTypeManifestList.Validate(f) - case typeConfig: + case image.TypeConfig: return schema.MediaTypeImageConfig.Validate(f) } diff --git a/cmd/oci-image-validate/oci-image-validate.1.md b/cmd/oci-image-validate/oci-image-validate.1.md new file mode 100644 index 0000000..33d2b49 --- /dev/null +++ b/cmd/oci-image-validate/oci-image-validate.1.md @@ -0,0 +1,39 @@ +% OCI(1) OCI-IMAGE-VALIDATE User Manuals +% OCI Community +% JULY 2016 +# NAME +oci-image-validate \- Validate one or more image files + +# SYNOPSIS +**oci-image-validate** FILE... [flags] + +# DESCRIPTION +`oci-image-validate` validates the given file(s) against the OCI image specification. + + +# OPTIONS +**--help** + Print usage statement + +**--ref** NAME + The reference to validate (should point to a manifest). + Can be specified multiple times to validate multiple references. + `NAME` must be present in the `refs` subdirectory of the image. + Defaults to `v1.0`. + Only applicable if type is image or imageLayout. + +**--type** + Type of the file to validate. If unset, oci-image-validate will try to auto-detect the type. One of "imageLayout,image,manifest,manifestList,config" + +# EXAMPLES +``` +$ skopeo copy docker://busybox oci:busybox-oci +$ oci-image-validate --type imageLayout --ref latest busybox-oci +busybox-oci: OK +``` + +# SEE ALSO +**skopeo**(1) + +# HISTORY +Sept 2016, Originally compiled by Antonio Murdaca (runcom at redhat dot com) diff --git a/cmd/oci-image-tool/unpack.go b/cmd/oci-unpack/main.go similarity index 85% rename from cmd/oci-image-tool/unpack.go rename to cmd/oci-unpack/main.go index e02ad70..463db62 100644 --- a/cmd/oci-image-tool/unpack.go +++ b/cmd/oci-unpack/main.go @@ -20,14 +20,14 @@ import ( "os" "strings" - "github.com/opencontainers/image-spec/image" + "github.com/opencontainers/image-tools/image" "github.com/spf13/cobra" ) // supported unpack types var unpackTypes = []string{ - typeImageLayout, - typeImage, + image.TypeImageLayout, + image.TypeImage, } type unpackCmd struct { @@ -37,6 +37,17 @@ type unpackCmd struct { ref string } +func main() { + stdout := log.New(os.Stdout, "", 0) + stderr := log.New(os.Stderr, "", 0) + + cmd := newUnpackCmd(stdout, stderr) + if err := cmd.Execute(); err != nil { + stderr.Println(err) + os.Exit(1) + } +} + func newUnpackCmd(stdout, stderr *log.Logger) *cobra.Command { v := &unpackCmd{ stdout: stdout, @@ -76,7 +87,7 @@ func (v *unpackCmd) Run(cmd *cobra.Command, args []string) { } if v.typ == "" { - typ, err := autodetect(args[0]) + typ, err := image.Autodetect(args[0]) if err != nil { v.stderr.Printf("%q: autodetection failed: %v", args[0], err) os.Exit(1) @@ -86,10 +97,10 @@ func (v *unpackCmd) Run(cmd *cobra.Command, args []string) { var err error switch v.typ { - case typeImageLayout: + case image.TypeImageLayout: err = image.UnpackLayout(args[0], args[1], v.ref) - case typeImage: + case image.TypeImage: err = image.Unpack(args[0], args[1], v.ref) } diff --git a/cmd/oci-image-tool/man/oci-image-tool-unpack.1.md b/cmd/oci-unpack/oci-unpack.1.md similarity index 55% rename from cmd/oci-image-tool/man/oci-image-tool-unpack.1.md rename to cmd/oci-unpack/oci-unpack.1.md index c7dac38..890bd0f 100644 --- a/cmd/oci-image-tool/man/oci-image-tool-unpack.1.md +++ b/cmd/oci-unpack/oci-unpack.1.md @@ -1,14 +1,14 @@ -% OCI(1) OCI-IMAGE-TOOL User Manuals +% OCI(1) OCI-UNPACK User Manuals % OCI Community % JULY 2016 # NAME -oci-image-tool-unpack \- Unpack an image or image source layout +oci-unpack \- Unpack an image or image source layout # SYNOPSIS -**oci-image-tool unpack** [src] [dest] [flags] +**oci-unpack** [src] [dest] [flags] # DESCRIPTION -`oci-image-tool unpack` validates an application/vnd.oci.image.manifest.v1+json and unpacks its layered filesystem to `dest`. +`oci-unpack` validates an application/vnd.oci.image.manifest.v1+json and unpacks its layered filesystem to `dest`. # OPTIONS **--help** @@ -18,13 +18,13 @@ oci-image-tool-unpack \- Unpack an image or image source layout The ref pointing to the manifest to be unpacked. This must be present in the "refs" subdirectory of the image. (default "v1.0") **--type** - Type of the file to unpack. If unset, oci-image-tool will try to auto-detect the type. One of "imageLayout,image" + Type of the file to unpack. If unset, oci-unpack will try to auto-detect the type. One of "imageLayout,image" # EXAMPLES ``` $ skopeo copy docker://busybox oci:busybox-oci $ mkdir busybox-bundle -$ oci-image-tool unpack --ref latest busybox-oci busybox-bundle +$ oci-unpack unpack --ref latest busybox-oci busybox-bundle tree busybox-bundle busybox-bundle ├── bin @@ -43,7 +43,7 @@ busybox-bundle ``` # SEE ALSO -**oci-image-tool**(1), **skopeo**(1) +**skopeo**(1) # HISTORY -July 2016, Originally compiled by Antonio Murdaca (runcom at redhat dot com) +Sept 2016, Originally compiled by Antonio Murdaca (runcom at redhat dot com) diff --git a/config.md b/config.md deleted file mode 100644 index 378f3d4..0000000 --- a/config.md +++ /dev/null @@ -1,367 +0,0 @@ -# OCI Image Serialization - -An *Image* is an ordered collection of root filesystem changes and the corresponding execution parameters for use within a container runtime. -This specification outlines the JSON format describing images for use with a container runtime and execution tool and its relationship to filesystem changesets, described in [Layers](layer.md). - -## Terminology - -This specification uses the following terms: - -
-
- Layer -
-
- Image filesystems are composed of layers. - Each layer represents a set of filesystem changes in a tar-based layer format, recording files to be added, changed, or deleted relative to its parent layer. - Layers do not have configuration metadata such as environment variables or default arguments - these are properties of the image as a whole rather than any particular layer. - Using a layer-based or union filesystem such as AUFS, or by computing the diff from filesystem snapshots, the filesystem changeset can be used to present a series of image layers as if they were one cohesive filesystem. -
-
- Image JSON -
-
- Each image has an associated JSON structure which describes some basic information about the image such as date created, author, and the ID of its parent image as well as execution/runtime configuration like its entrypoint, default arguments, CPU/memory shares, networking, and volumes. - The JSON structure also references a cryptographic hash of each layer used by the image, and provides history information for those layers. - This JSON is considered to be immutable, because changing it would change the computed ImageID. - Changing it means creating a new derived image, instead of changing the existing image. -
-
- Layer DiffID -
-
- A layer DiffID is a SHA256 digest over the layer's uncompressed tar archive and serialized in the descriptor digest format, e.g., sha256:a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9. - Layers must be packed and unpacked reproducibly to avoid changing the layer ID, for example by using tar-split to save the tar headers. - NOTE: the DiffID is different than the digest in the manifest list because the manifest digest is taken over the gzipped layer for application/vnd.oci.image.layer.tar+gzip types. -
-
- Layer ChainID -
-
- For convenience, it is sometimes useful to refer to a stack of layers with a single identifier. - This is called a ChainID. - For a - single layer (or the layer at the bottom of a stack), the - ChainID is equal to the layer's DiffID. - Otherwise the ChainID is given by the formula: - ChainID(layerN) = SHA256hex(ChainID(layerN-1) + " " + DiffID(layerN)). -
-
- ImageID -
-
- Each image's ID is given by the SHA256 hash of its configuration JSON. - It is represented as a hexadecimal encoding of 256 bits, e.g., sha256:a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9. - Since the configuration JSON that gets hashed references hashes of each layer in the image, this formulation of the ImageID makes images content-addresable. -
-
- -## Image Configuration - -Here is an example image configuration JSON document: - -```json,title=Image%20JSON&mediatype=application/vnd.oci.image.config.v1%2Bjson -{ - "created": "2015-10-31T22:22:56.015925234Z", - "author": "Alyssa P. Hacker ", - "architecture": "amd64", - "os": "linux", - "config": { - "User": "alice", - "Memory": 2048, - "MemorySwap": 4096, - "CpuShares": 8, - "ExposedPorts": { - "8080/tcp": {} - }, - "Env": [ - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "FOO=oci_is_a", - "BAR=well_written_spec" - ], - "Entrypoint": [ - "/bin/my-app-binary" - ], - "Cmd": [ - "--foreground", - "--config", - "/etc/my-app.d/default.cfg" - ], - "Volumes": { - "/var/job-result-data": {}, - "/var/log/my-app-logs": {} - }, - "WorkingDir": "/home/alice" - }, - "rootfs": { - "diff_ids": [ - "sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1", - "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" - ], - "type": "layers" - }, - "history": [ - { - "created": "2015-10-31T22:22:54.690851953Z", - "created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /" - }, - { - "created": "2015-10-31T22:22:55.613815829Z", - "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]", - "empty_layer": true - } - ] -} -``` - -Note: whitespace has been added to this example for clarity. Whitespace is OPTIONAL and implementations MAY have compact JSON with no whitespace. - -### Image JSON Field Descriptions - -
-
- created string -
-
- ISO-8601 formatted combined date and time at which the image was created. -
-
- author string -
-
- Gives the name and/or email address of the person or entity which created and is responsible for maintaining the image. -
-
- architecture string -
-
- The CPU architecture which the binaries in this image are built to run on. - Possible values include: -
    -
  • 386
  • -
  • amd64
  • -
  • arm
  • -
- More values may be supported in the future and any of these may or may not be supported by a given container runtime implementation. - New entries SHOULD be submitted to this specification for standardization and be inspired by the Go language documentation for $GOOS and $GOARCH. -
-
- os string -
-
- The name of the operating system which the image is built to run on. - Possible values include: -
    -
  • darwin
  • -
  • freebsd
  • -
  • linux
  • -
- More values may be supported in the future and any of these may or may not be supported by a given container runtime implementation. - New entries SHOULD be submitted to this specification for standardization and be inspired by the Go language documentation for $GOOS and $GOARCH. -
-
- config struct -
-
- The execution parameters which should be used as a base when running a container using the image. - This field can be null, in which case any execution parameters should be specified at creation of the container. - -

Container RunConfig Field Descriptions

- -
-
- User string -
-
-

- The username or UID which the process in the container should run as. - This acts as a default value to use when the value is not specified when creating a container. -

- -

All of the following are valid:

- -
    -
  • user
  • -
  • uid
  • -
  • user:group
  • -
  • uid:gid
  • -
  • uid:group
  • -
  • user:gid
  • -
- -

- If group/gid is not specified, the default group and supplementary groups of the given user/uid in /etc/passwd from the container are applied. -

-
-
- Memory integer -
-
- Memory limit (in bytes). - This acts as a default value to use when the value is not specified when creating a container. -
-
- MemorySwap integer -
-
- Total memory usage (memory + swap); set to -1 to disable swap. - This acts as a default value to use when the value is not specified when creating a container. -
-
- CpuShares integer -
-
- CPU shares (relative weight vs. other containers). - This acts as a default value to use when the value is not specified when creating a container. -
-
- ExposedPorts struct -
-
- A set of ports to expose from a container running this image. - 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. - Here is an example: - -
{
-    "8080": {},
-    "53/udp": {},
-    "2356/tcp": {}
-}
- - Its keys can be in the format of: -
    -
  • - "port/tcp" -
  • -
  • - "port/udp" -
  • -
  • - "port" -
  • -
- with the default protocol being "tcp" if not specified. - - These values act as defaults and are merged with any specified when creating a container. -
-
- Env array of strings -
-
- Entries are in the format of VARNAME="var value". - These values act as defaults and are merged with any specified when creating a container. -
-
- Entrypoint array of strings -
-
- A list of arguments to use as the command to execute when the container starts. - This value acts as a default and is replaced by an entrypoint specified when creating a container. This field MAY be "null". -
-
- Cmd array of strings -
-
- Default arguments to the entrypoint of the container. - These values act as defaults and are replaced with any specified when creating a container. - If an Entrypoint value is not specified, then the first entry of the Cmd array should be interpreted as the executable to run. This field MAY be "null". -
-
- Volumes struct -
-
- A set of directories which should be created as data volumes in a container running this image. This field MAY be "null". -

- If a file or folder exists within the image with the same path as a data volume, that file or folder is replaced with the data volume and is never merged. -

- 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. - Here is an example: -
{
-    "/var/my-app-data/": {},
-    "/etc/some-config.d/": {}
-}
-
-
- WorkingDir string -
-
- Sets the current working directory of the entrypoint process in the container. - This value acts as a default and is replaced by a working directory specified when creating a container. -
-
-
-
- rootfs struct -
-
- The rootfs key references the layer content addresses used by the image. - This makes the image config hash depend on the filesystem hash. - rootfs has two subkeys: - -
    -
  • - type which MUST be set to layers. - Implementations MUST generate an error if they encounter a unknown value while verifying or unpacking an image. -
  • -
  • - diff_ids is an array of layer content hashes (DiffIDs), in order from bottom-most to top-most. -
  • -
- - - Here is an example rootfs section: - -
"rootfs": {
-  "diff_ids": [
-    "sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
-    "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
-    "sha256:13f53e08df5a220ab6d13c58b2bf83a59cbdc2e04d0a3f041ddf4b0ba4112d49"
-  ],
-  "type": "layers"
-}
-
-
- history struct -
-
- history is an array of objects describing the history of each layer. - The array is ordered from bottom-most layer to top-most layer. - The object has the following fields. - -
    -
  • - created: Creation time, expressed as a ISO-8601 formatted - combined date and time -
  • -
  • - author: The author of the build point -
  • -
  • - created_by: The command which created the layer -
  • -
  • - comment: A custom message set when creating the layer -
  • -
  • - empty_layer: This field is used to mark if the history item created a filesystem diff. - It is set to true if this history item doesn't correspond to an actual layer in the rootfs section (for example, a command like ENV which results in no change to the filesystem). -
  • -
- -Here is an example history section: - -
"history": [
-  {
-    "created": "2015-10-31T22:22:54.690851953Z",
-    "created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
-  },
-  {
-    "created": "2015-10-31T22:22:55.613815829Z",
-    "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]",
-    "empty_layer": true
-  }
-]
-
-
- -Any extra fields in the Image JSON struct are considered implementation specific and should be ignored by any implementations which are unable to interpret them. diff --git a/descriptor.md b/descriptor.md deleted file mode 100644 index 70056fa..0000000 --- a/descriptor.md +++ /dev/null @@ -1,117 +0,0 @@ -# OCI Content Descriptors - -An OCI image consists of several different components, arranged in a [Merkle Directed Acyclic Graph (DAG)](https://en.wikipedia.org/wiki/Merkle_tree). -References between components in the graph are expressed through _Content Descriptors_. -A Content Descriptor (or simply _Descriptor_) describes the disposition of the targeted content. -A Content Descriptor includes the type of the content, a content identifier (_digest_), and the byte-size of the raw content. - -Descriptors SHOULD be embedded in other formats to securely reference external content. - -Other formats SHOULD use descriptors to securely reference external content. - -## Properties - -A descriptor consists of a set of properties encapsulated in key-value fields. - -The following fields contain the primary properties that constitute a Descriptor: - -- **`mediaType`** *string* - - This REQUIRED property contains the MIME type of the referenced content. - - The OCI image specification defines [several of its own MIME types](media-types.md) for resources defined in the specification. - -- **`digest`** *string* - - This REQUIRED property is the _digest_ of the targeted content, conforming to the requirements outlined in [Digests and Verification](#digests-and-verification). - Retrieved content SHOULD be verified against this digest when consumed via untrusted sources. - -- **`size`** *int64* - - This REQUIRED property specifies the size, in bytes, of the raw content. - This property exists so that a client will have an expected size for the content before processing. - If the length of the retrieved content does not match the specified length, the content SHOULD NOT be trusted. - -- **`urls`** *array* - - This OPTIONAL property specifies a list of URLs from which this object MAY be downloaded. - -### Reserved - -The following field keys MUST NOT be used in descriptors specified in other OCI specifications: - -- **`data`** *string* - - This key is RESERVED for future versions of the specification. - -All other fields may be included in other OCI specifications. -Extended _Descriptor_ field additions proposed in other OCI specifications SHOULD first be considered for addition into this specification. - -## Digests and Verification - -The _digest_ property of a Descriptor acts as a content identifier, enabling [content addressability](http://en.wikipedia.org/wiki/Content-addressable_storage). -It uniquely identifies content by taking a [collision-resistant hash](https://en.wikipedia.org/wiki/Cryptographic_hash_function) of the bytes. -If the identifier can be communicated in a secure manner, one can retrieve the content from an insecure source, calculate the digest independently, and be certain that the correct content was obtained. - -The value of the digest property, the _digest string_, is a serialized hash result, consisting of an _algorithm_ portion and a _hex_ portion. -The algorithm identifies the methodology used to calculate the digest; the hex portion is the lowercase hex-encoded result of the hash. - -The digest string MUST match the following grammar: - -``` -digest := algorithm ":" hex -algorithm := /[a-z0-9_+.-]+/ -hex := /[a-f0-9]+/ -``` - -Some example digest strings include the following: - -digest | description | -----------------------------------------------------------------------------------|------------------------------------------------ -sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b | Common sha256 based digest | - -Before consuming content targeted by a descriptor from untrusted sources, the byte content SHOULD be verified against the digest. -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 some canonicalization of the underlying content to ensure stable content identifiers. - -### Algorithms - -While the _algorithm_ component of the digest does allow one to utilize a wide variety of algorithms, compliant implementations SHOULD use [SHA-256](#sha-256). - -Let's use a simple example in pseudo-code to demonstrate a digest calculation: -A _digest_ is calculated by the following pseudo-code, where `H` is the selected hash algorithm, identified by string ``: -``` -let ID(C) = Descriptor.digest -let C = -let D = ':' + EncodeHex(H(C)) -let verified = ID(C) == D -``` -Above, we define the content identifier as `ID(C)`, extracted from the `Descriptor.digest` field. -Content `C` is a string of bytes. -Function `H` returns a the hashs of `C` in bytes and is passed to function `EncodeHex` to obtain the _digest_. -The result `verified` is true if `ID(C)` is equal to `D`, confirming that `C` is the content identified by `D`. -After verification, the following is true: - -``` -D == ID(C) == ':' + EncodeHex(H(C)) -``` - -The _digest_ is confirmed as the content identifier by independently calculating the _digest_. - -#### SHA-256 - -[SHA-256](https://tools.ietf.org/html/rfc4634#page-7) is a collision-resistant hash function, chosen for ubiquity, reasonable size and secure characteristics. -Implementations MUST implement SHA-256 digest verification for use in descriptors. - -## Examples - -The following example describes a [_Manifest_](manifest.md#image-manifest) with a content identifier of "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", of size 7682 bytes: - -```json,title=Content%20Descriptor&mediatype=application/vnd.oci.descriptor.v1%2Bjson -{ - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "size": 7682, - "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" -} -``` diff --git a/glide.lock b/glide.lock index 57280d2..bffc373 100644 --- a/glide.lock +++ b/glide.lock @@ -1,22 +1,24 @@ -hash: 223985f204597c6ed49657a4fc38273f683d40c39e8d48d13ed0dbf632107427 -updated: 2016-07-22T16:40:50.020731917+02:00 +hash: 14550151b754f92de80e864f26da93e8adfce71537c9cb7883b0bdd67e541453 +updated: 2016-09-15T10:25:30.038113538+02:00 imports: - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +- name: github.com/opencontainers/image-spec + version: 7e6e2f76d6a11cdcb3f3e334e3f12179a0f37dad + subpackages: + - schema + - specs-go/v1 + - specs-go - name: github.com/opencontainers/runtime-spec version: 06479209bdc0d4135911688c18157bd39bd99c22 subpackages: - specs-go - name: github.com/pkg/errors - version: 01fa4104b9c248c8945d14d9f128454d5b28d595 -- name: github.com/russross/blackfriday - version: 0b647d0506a698cca42caca173e55559b12a69f2 -- name: github.com/shurcooL/sanitized_anchor_name - version: 10ef21a441db47d8b13ebcc5fd2310f636973c77 + version: 17b591df37844cde689f4d5813e5cea0927d8dd2 - name: github.com/spf13/cobra - version: f62e98d28ab7ad31d707ba837a966378465c7b57 + version: 9c28e4bbd74e5c3ed7aacbc552b2cab7cfdfe744 - name: github.com/spf13/pflag - version: 1560c1005499d61b80f865c04d39ca7505bf7f0b + version: 7b17cc4658ef5ca157b986ea5c0b43af7938532b - name: github.com/xeipuuv/gojsonpointer version: e0fe6f68307607d540ed8eac07a342c33fa1b54a - name: github.com/xeipuuv/gojsonreference @@ -24,7 +26,7 @@ imports: - name: github.com/xeipuuv/gojsonschema version: d5336c75940ef31c9ceeb0ae64cf92944bccb4ee - name: go4.org - version: 85455cb60c902182109ca27131042a41bc4cb85d + version: e7a2449258501866491620d4f47472e6100ca551 subpackages: - errorutil testImports: [] diff --git a/glide.yaml b/glide.yaml index 204d144..835e2e4 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,15 +1,14 @@ -package: github.com/opencontainers/image-spec +package: github.com/opencontainers/image-tools import: +- package: github.com/opencontainers/image-spec + version: 7e6e2f76d6a11cdcb3f3e334e3f12179a0f37dad + subpackages: + - schema + - specs-go/v1 - package: github.com/opencontainers/runtime-spec - version: ^1.0.0-rc1 + version: ~1.0.0-rc1 subpackages: - specs-go - package: github.com/pkg/errors - version: ">=0.7.0" + version: ~0.7.1 - package: github.com/spf13/cobra -- package: github.com/xeipuuv/gojsonschema - version: d5336c75940ef31c9ceeb0ae64cf92944bccb4ee -- package: github.com/russross/blackfriday - version: ~v1.4 -- package: github.com/shurcooL/sanitized_anchor_name - version: 10ef21a441db47d8b13ebcc5fd2310f636973c77 diff --git a/image-layout.md b/image-layout.md deleted file mode 100644 index 700108d..0000000 --- a/image-layout.md +++ /dev/null @@ -1,109 +0,0 @@ -## Open Container Initiative Image Layout Specification - -The OCI Image Layout is a slash separated layout of 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 a tool can convert a given ref into a runnable OCI Image Format by finding an appropriate manifest from the manifest list, unpacking the [filesystem layers](layer.md) in the correct order, and then converting the image configuration into an OCI Runtime config.json. - -The image layout has two top level directories: - -- "blobs" contains content-addressable blobs. A blob has no schema and should be considered opaque. -- "refs" contains [descriptors][descriptors]. Commonly pointing to an image manifest. - - -It also contains a file that is used to identify the layout version: - -- "oci-layout" MUST contain a JSON object with a version field `{"imageLayoutVersion": "1.0.0"}` and MAY include additional fields. - -This is an example image layout: - -``` -$ cd example.com/app/ -$ find . -. -./blobs -./blobs/sha256/e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f -./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 -./blobs/sha256/5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270 -./blobs/sha256/e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f -./oci-layout -./refs -./refs/v1.0 -./refs/stable-release -``` - -Blobs are named by their contents: - -``` -$ shasum -a 256 ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 -afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 -``` - -Object names in the `refs` subdirectories MUST NOT include characters outside of the set of "A" to "Z", "a" to "z", "0" to "9", the hyphen `-`, the dot `.`, and the underscore `_`. -Object names in the `blobs` subdirectories are composed of a directory for each hash algorithm, the children of which will contain the actual content. -A blob, referenced with digest `:` (per [descriptor](descriptor.md#digests-and-verification)), MUST have its content stored in a file under `blobs//`. -The character set of the entry name for `` and `` MUST match the respective grammar elements described in [descriptor](descriptor.md#digests-and-verification). -For example `sha256:5b` will map to the layout `blobs/sha256/5b`. -The blobs directory MAY contain blobs which are not referenced by any of the refs. -The blobs directory MAY be missing referenced blobs, in which case the missing blobs SHOULD be fulfilled by an external blob store. - -No semantic restriction is given for object names in the `refs` subdirectory. -Each object in the `refs` subdirectory MUST be of type `application/vnd.oci.descriptor.v1+json`. -In general the `mediatype` of this [descriptor][descriptors] object will be either `application/vnd.oci.image.manifest.list.v1+json` or `application/vnd.oci.image.manifest.v1+json` although future versions of the spec MAY use a different mediatype. - -**Implementor's Note:** -A common use case of refs is representing "tags" for a container image. -For example, an image may have a tag for different versions or builds of the software. -In the wild you often see "tags" like "v1.0.0-vendor.0", "2.0.0-debug", etc. - -This illustrates the expected contents of a given ref, the manifest list it points to and the blobs the manifest references. - -``` -$ cat ./refs/v1.0 -{"size": 4096, "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", "mediatype": "application/vnd.oci.image.manifest.list.v1+json"} -``` -``` -$ cat ./blobs/sha256/e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f -{ - "schemaVersion": 2, - "mediaType": "application/vnd.oci.image.manifest.list.v1+json", - "manifests": [ - { - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "size": 7143, - "digest": "sha256:afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51", - "platform": { - "architecture": "ppc64le", - "os": "linux" - } - }, -... -``` -``` -$ cat ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 -{ - "schemaVersion": 2, - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "config": [ - "mediaType": "application/vnd.oci.image.config.v1+json", - "size": 7023, - "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" - }, - "layers": [ - { - "mediaType": "application/vnd.oci.image.layer.tar+gzip", - "size": 32654, - "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f" - }, -... -``` -``` -$ cat ./blobs/sha256/5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270 -{"architecture":"amd64","author":"Antonio Murdaca \u003eruncom@redhat.com\u003e","config":{"Hostname":"8dfe43d80430","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":null,"Image":"sha256:6986ae504bbf843512d680cc959484452034965db15f75ee8bdd1b107f61500b", -... -``` -``` -$ cat ./blobs/sha256/e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f -[tar stream] -``` - -[descriptors]: ./descriptor.md diff --git a/cmd/oci-image-tool/autodetect.go b/image/autodetect.go similarity index 84% rename from cmd/oci-image-tool/autodetect.go rename to image/autodetect.go index 094e7b9..c3e2f85 100644 --- a/cmd/oci-image-tool/autodetect.go +++ b/image/autodetect.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package main +package image import ( "encoding/json" @@ -27,23 +27,23 @@ import ( // supported autodetection types const ( - typeImageLayout = "imageLayout" - typeImage = "image" - typeManifest = "manifest" - typeManifestList = "manifestList" - typeConfig = "config" + TypeImageLayout = "imageLayout" + TypeImage = "image" + TypeManifest = "manifest" + TypeManifestList = "manifestList" + TypeConfig = "config" ) -// autodetect detects the validation type for the given path +// Autodetect detects the validation type for the given path // or an error if the validation type could not be resolved. -func autodetect(path string) (string, error) { +func Autodetect(path string) (string, error) { fi, err := os.Stat(path) if err != nil { return "", errors.Wrapf(err, "unable to access path") // err from os.Stat includes path name } if fi.IsDir() { - return typeImageLayout, nil + return TypeImageLayout, nil } f, err := os.Open(path) @@ -61,10 +61,10 @@ func autodetect(path string) (string, error) { switch mimeType { case "application/x-gzip": - return typeImage, nil + return TypeImage, nil case "application/octet-stream": - return typeImage, nil + return TypeImage, nil case "text/plain; charset=utf-8": // might be a JSON file, will be handled below @@ -98,14 +98,14 @@ func autodetect(path string) (string, error) { switch { case header.MediaType == string(schema.MediaTypeManifest): - return typeManifest, nil + return TypeManifest, nil case header.MediaType == string(schema.MediaTypeManifestList): - return typeManifestList, nil + return TypeManifestList, nil case header.MediaType == "" && header.SchemaVersion == 0 && header.Config != nil: // config files don't have mediaType/schemaVersion header - return typeConfig, nil + return TypeConfig, nil } return "", errors.New("unknown media type") diff --git a/img/build-diagram.png b/img/build-diagram.png deleted file mode 100644 index dfd1d602b220f86603de8ee3ef3dc26c133a5ff9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26336 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 Si+CqiBPDq?xdNHLeE$a?85dyy diff --git a/img/media-types.dot b/img/media-types.dot deleted file mode 100644 index 2e2304e..0000000 --- a/img/media-types.dot +++ /dev/null @@ -1,12 +0,0 @@ -digraph G { - { - manifestList [shape=note, label="Manifest list\n<>\napplication/vnd.oci.image.manifest.list.v1+json"] - manifest [shape=note, label="Image manifest\napplication/vnd.oci.image.manifest.v1+json"] - config [shape=note, label="Image JSON\napplication/vnd.oci.image.config.v1+json"] - layer [shape=note, label="Layer tar+gzip\napplication/vnd.oci.image.layer.tar+gzip\napplication/vnd.oci.image.layer.nondistributable.tar+gzip"] - } - - manifestList -> manifest [label="1..*"] - manifest -> config [label="1..1"] - manifest -> layer [label="1..*"] -} diff --git a/img/media-types.png b/img/media-types.png deleted file mode 100644 index 6d3049a1d9bf3db01e092f267571669d600a5965..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33088 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 BH1+@h diff --git a/img/run-diagram.png b/img/run-diagram.png deleted file mode 100644 index df6145bf06ea2997dbe722b2da958e6bf9bf3dd8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14368 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 Uig1SGgw)TOSez+3ef{452Q_iqDgXcg diff --git a/layer.md b/layer.md deleted file mode 100644 index b441b8d..0000000 --- a/layer.md +++ /dev/null @@ -1,282 +0,0 @@ -# Image Layer Filesystem Changeset - -This document describes how to serialize a filesystem and filesystem changes like removed files into a blob called a layer. -One or more layers are ordered 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. - -## Distributable Format - -Layer Changesets for the [mediatype](./media-types.md) `application/vnd.oci.image.layer.tar+gzip` MUST be packaged in [tar archive][tar-archive]. -Layer Changesets for the [mediatype](./media-types.md) `application/vnd.oci.image.layer.tar+gzip` MUST NOT include duplicate entries for file paths in the resulting [tar archive][tar-archive]. - -## Change Types - -Types of changes that can occur in a changeset are: - -* Additions -* Modifications -* Removals - -Additions and Modifications are represented the same in the changeset tar archive. - -Removals are represented using "[whiteout](#whiteouts)" file entries (See [Representing Changes](#representing-changes)). - -### File Types - -Throughout this document section, the use of word "files" or "entries" includes: - -* regular files -* directories -* sockets -* symbolic links -* block devices -* character devices -* FIFOs - -### File Attributes - -Where supported, MUST include file attributes for Additions and Modifications include: - -* Modification Time (`mtime`) -* User ID (`uid`) - * User Name (`uname`) *secondary to `uid`* -* Group ID (`gid `) - * Group Name (`gname`) *secondary to `gid`* -* Mode (`mode`) -* Extended Attributes (`xattrs`) -* Symlink reference (`linkname`) - -[Sparse files](https://en.wikipedia.org/wiki/Sparse_file) SHOULD NOT be used because they lack consistent support across tar implementations. - -## Creating - -### Initial Root Filesystem - -The initial root filesystem is the base or parent layer. - -For this example, an image root filesystem has an initial state as an empty directory. -The name of the directory is not relevant to the layer itself, only for the purpose of producing comparisons. - -Here is an initial empty directory structure for a changeset, with a unique directory name `rootfs-c9d-v1`. - -``` -rootfs-c9d-v1/ -``` - -### Populate Initial Filesystem - -Files and directories are then created: - -``` -rootfs-c9d-v1/ - etc/ - my-app-config - bin/ - my-app-binary - my-app-tools -``` - -The `rootfs-c9d-v1` directory is then created as a plain [tar archive][tar-archive] with relative path to `rootfs-c9d-v1`. -Entries for the following files: - -``` -./ -./etc/ -./etc/my-app-config -./bin/ -./bin/my-app-binary -./bin/my-app-tools -``` - -### Populate a Comparison Filesystem - -Create a new directory and initialize it with an copy or snapshot of the prior root filesystem. -Example commands that can preserve [file attributes](#file-attributes) to make this copy are: -* [cp(1)](http://linux.die.net/man/1/cp): `cp -a rootfs-c9d-v1/ rootfs-c9d-v1.s1/` -* [rsync(1)](http://linux.die.net/man/1/rsync): `rsync -aHAX rootfs-c9d-v1/ rootfs-c9d-v1.s1/` -* [tar(1)](http://linux.die.net/man/1/tar): `mkdir rootfs-c9d-v1.s1 && tar --acls --xattrs -C rootfs-c9d-v1/ -c . | tar -C rootfs-c9d-v1.s1/ --acls --xattrs -x` (including `--selinux` where supported) - -Any [changes](#change-types) to the snapshot MUST NOT change or affect the directory it was copied from. - -For example `rootfs-c9d-v1.s1` is an identical snapshot of `rootfs-c9d-v1`. -In this way `rootfs-c9d-v1.s1` is prepared for updates and alterations. - -**Implementor's Note**: *a copy-on-write or union filesystem can efficiently make directory snapshots* - -Initial layout of the snapshot: - -``` -rootfs-c9d-v1.s1/ - etc/ - my-app-config - bin/ - my-app-binary - my-app-tools -``` - -See [Change Types](#change-types) for more details on changes. - -For example, add a directory at `/etc/my-app.d` containing a default config file, removing the existing config file. -Also a change (in attribute or file content) to `./bin/my-app-tools` binary to handle the config layout change. - -Following these changes, the representation of the `rootfs-c9d-v1.s1` directory: - -``` -rootfs-c9d-v1.s1/ - etc/ - my-app.d/ - default.cfg - bin/ - my-app-binary - my-app-tools -``` - -### Determining Changes - -When two directories are compared, the relative root is the top-level directory. -The directories are compared, looking for files that have been [added, modified, or removed](#change-types). - -For this example, `rootfs-c9d-v1/` and `rootfs-c9d-v1.s1/` are recursively compared, each as relative root path. - -The following changeset is found: - -``` -Added: /etc/my-app.d/ -Added: /etc/my-app.d/default.cfg -Modified: /bin/my-app-tools -Deleted: /etc/my-app-config -``` - -This reflects the removal of `/etc/my-app-config` and creation of a file and directory at `/etc/my-app.d/default.cfg`. -`/bin/my-app-tools` has also been replaced with an updated version. - -### Representing Changes - -A [tar archive][tar-archive] is then created which contains *only* this changeset: - -- Added and modified files and directories in their entirety -- Deleted files or directories marked with a [whiteout file](#whiteouts) - -The resulting tar archive for `rootfs-c9d-v1.s1` has the following entries: - -``` -./etc/my-app.d/ -./etc/my-app.d/default.cfg -./bin/my-app-tools -./etc/.wh.my-app-config -``` - -Where the basename name of `./etc/my-app-config` is now prefixed with `.wh.`, and will therefore be removed when the changeset is applied. - -## Applying - -Layer Changesets of [mediatype](./media-types.md) `application/vnd.oci.image.layer.tar+gzip` are applied rather than strictly extracted in normal fashion for tar archives. - -Applying a layer changeset requires consideration for the [whiteout](#whiteouts) files. -In the absence of any [whiteout](#whiteouts) files in a layer changeset, the archive is extracted like a regular tar archive. - - -### Changeset over existing files - -This section covers applying an entry in a layer changeset, if the file path already exists. - -If the file path is a directory, then the existing path just has it's attribute set from the layer changeset for that filepath. -If the file path is any other file type (regular file, FIFO, etc), then the: -* file path is unlinked (See [`unlink(2)`](http://linux.die.net/man/2/unlink)) -* create the file - * If a regular file then content written. -* set attributes on the filepath - -## Whiteouts - -A whiteout file is an empty file with a special filename that signifies a path should be deleted. -A whiteout filename consists of the prefix .wh. plus the basename of the path to be deleted. -As files prefixed with `.wh.` are special whiteout markers, it is not possible to create a filesystem which has a file or directory with a name beginning with `.wh.`. - -Once a whiteout is applied, the whiteout itself MUST also be hidden. -Whiteout files MUST only apply to resources in lower/parent layers. -Files that are present in the same layer as a whiteout file can only be hidden by whiteout files in subsequent layers. -The following is a base layer with several resources: - -``` -a/ -a/b/ -a/b/c/ -a/b/c/bar -``` - -When the next layer is created, the original `a/b` directory is deleted and recreated with `a/b/c/foo`: - -``` -a/ -a/.wh..wh..opq -a/b/ -a/b/c/ -a/b/c/foo -``` - -When processing the second layer, `a/.wh..wh..opq` is applied first, before creating the new version of `a/b`, regardless of the ordering in which the whiteout file was encountered. -For example, the following layer is equivalent to the layer above: - -``` -a/ -a/b/ -a/b/c/ -a/b/c/foo -a/.wh..wh..opq -``` - -Implementations SHOULD generate layers such that the whiteout files appear before sibling directory entries. - -### Opaque Whiteout - -In addition to expressing that a single entry should be removed from a lower layer, layers may remove all of the children using an opaque whiteout entry. -An opaque whiteout entry is a file with the name `.wh..wh..opq` indicating that all siblings are hidden in the lower layer. -Let's take the following base layer as an example: - -``` -etc/ - my-app-config -bin/ - my-app-binary - my-app-tools - tools/ - my-app-tool-one -``` - -If all children of `bin/` are removed, the next layer would have the following: - -``` -bin/ - .wh..wh..opq -``` - -This is called _opaque whiteout_ format. -An _opaque whiteout_ file hides _all_ children of the `bin/` including sub-directories and all descendants. -Using _explicit whiteout_ files, this would be equivalent to the following: - -``` -bin/ - .wh.my-app-binary - .wh.my-app-tools - .wh.tools -``` - -In this case, a unique whiteout file is generated for each entry. -If there were more children of `bin/` in the base layer, there would be an entry for each. -Note that this opaque file will apply to _all_ children, including sub-directories, other resources and all descendants. - -Implementations SHOULD generate layers using _explicit whiteout_ files, but MUST accept both. - -Any given image is likely to be composed of several of these Image Filesystem Changeset tar archives. - -# Non-Distributable Layers - -Certain layers, due to legal requirements, may not be regularly distributable. -Typically, such layers are downloaded directly from a distributor but are never uploaded. - -Layers that have these restrictions SHOULD be tagged with an alternative mediatype of `application/vnd.oci.image.layer.nondistributable.tar+gzip`. -[Descriptors](descriptor.md) referencing these layers MAY include `urls` for downloading these layers. -It is implementation defined whether or not implementations upload layers tagged with this media type. - -[tar-archive]: https://en.wikipedia.org/wiki/Tar_(computing) diff --git a/manifest.md b/manifest.md deleted file mode 100644 index 010dab4..0000000 --- a/manifest.md +++ /dev/null @@ -1,205 +0,0 @@ -# OpenContainers Image Manifest Specification - -There are three main goals of the Image Manifest Specification. -The first goal is content-addressable images, by supporting an image model where the image's configuration can be hashed to generate a unique ID for the image and its components. -The second goal is to allow multi-architecture images, through a "fat manifest" which references image manifests for platform-specific versions of an image. -The third goal is to be translatable to the [OpenContainers/runtime-spec](https://github.com/opencontainers/runtime-spec) - - -# Manifest List - -The manifest list is the "fat manifest" which points to specific image manifests for one or more platforms. -While the use of a manifest list is OPTIONAL for image providers, image consumers SHOULD be prepared to process them. -A client will distinguish a manifest list from an image manifest based on the Content-Type returned in the HTTP response. - -## *Manifest List* Property Descriptions - -- **`schemaVersion`** *int* - - This REQUIRED property specifies the image manifest schema version. - For this version of the specification, this MUST be `2` to ensure backward compatibility with older versions of Docker. The value of this field will not change. This field MAY be removed in a future version of the specification. - -- **`mediaType`** *string* - - This REQUIRED property contains the media type of the manifest list. - For this version of the specification, this MUST be set to `application/vnd.oci.image.manifest.list.v1+json`. - For the media type(s) that this is compatible with see the [matrix](media-types.md#compatibility-matrix). - -- **`manifests`** *array* - - This REQUIRED property contains a list of manifests for specific platforms. - While the property MUST be present, the size of the array MAY be zero. - - Each object in the manifest is a [descriptor](descriptor.md) with the following additional properties: - - - **`platform`** *object* - - This REQUIRED property describes the platform which the image in the manifest runs on. - A full list of valid operating system and architecture values are listed in the [Go language documentation for `$GOOS` and `$GOARCH`](https://golang.org/doc/install/source#environment) - - - **`architecture`** *string* - - This REQUIRED property specified the CPU architecture, for example `amd64` or `ppc64le`. - - - **`os`** *string* - - This REQUIRED property specifies the operating system, for example `linux` or `windows`. - - - **`os.version`** *string* - - This OPTIONAL property specifies the operating system version, for example `10.0.10586`. - - - **`os.features`** *array* - - This OPTIONAL property specifies an array of strings, each specifying a mandatory OS feature (for example on Windows `win32k`). - - - **`variant`** *string* - - This OPTIONAL property specifies the variant of the CPU, for example `armv6l` to specify a particular CPU variant of the ARM CPU. - - - **`features`** *array* - - This OPTIONAL property specifies an array of strings, each specifying a mandatory CPU feature (for example `sse4` or `aes`). - -- **`annotations`** *string-string map* - - This OPTIONAL property contains arbitrary metadata for the manifest list. - Annotations MUST be a key-value map where both the key and value MUST be strings. - While the value MUST be present, it MAY be an empty string. - Keys MUST be unique within this map, and best practice is to namespace the keys. - Keys SHOULD be named using a reverse domain notation - e.g. `com.example.myKey`. - Keys using the `org.opencontainers` namespace are reserved and MUST NOT be used by other specifications. - If there are no annotations then this property MUST either be absent or be an empty map. - Implementations that are reading/processing manifest lists MUST NOT generate an error if they encounter an unknown annotation key. - - See [Pre-Defined Annotation Keys](#pre-defined-annotation-keys). - -### Extensibility -Implementations that are reading/processing manifest lists MUST NOT generate an error if they encounter an unknown property. -Instead they MUST ignore unknown properties. - -## Example Manifest List - -*Example showing a simple manifest list pointing to image manifests for two platforms:* -```json,title=Manifest%20List&mediatype=application/vnd.oci.image.manifest.list.v1%2Bjson -{ - "schemaVersion": 2, - "mediaType": "application/vnd.oci.image.manifest.list.v1+json", - "manifests": [ - { - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "size": 7143, - "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", - "platform": { - "architecture": "ppc64le", - "os": "linux" - } - }, - { - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "size": 7682, - "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", - "platform": { - "architecture": "amd64", - "os": "linux", - "features": [ - "sse4" - ] - } - } - ], - "annotations": { - "com.example.key1": "value1", - "com.example.key2": "value2" - } -} -``` - -# Image Manifest - -Unlike the [Manifest List](#manifest-list), which contains information about a set of images that can span a variety of architectures and operating systems, an image manifest provides a configuration and set of layers for a single container image for a specific architecture and operating system. - -## *Image Manifest* Property Descriptions - -- **`schemaVersion`** *int* - - This REQUIRED property specifies the image manifest schema version. - For this version of the specification, this MUST be `2` to ensure backward compatibility with older versions of Docker. The value of this field will not change. This field MAY be removed in a future version of the specification. - -- **`mediaType`** *string* - - This REQUIRED property contains the media type of the image manifest. - For this version of the specification, this MUST be set to `application/vnd.oci.image.manifest.v1+json`. - For the media type(s) that this is compatible with see the [matrix](media-types.md#compatibility-matrix). - -- **`config`** *[descriptor](descriptor.md)* - - This REQUIRED property references a configuration object for a container, by digest. - The referenced configuration object is a JSON blob that the runtime uses to set up the container, see [Image JSON Description](config.md). - -- **`layers`** *array* - - Each item in the array MUST be a [descriptor](descriptor.md). - The array MUST have the base image at index 0. - Subsequent layers MUST then follow in the order in which they are to be layered on top of each other. - The algorithm to create the final unpacked filesystem layout MUST be to first unpack the layer at index 0, then index 1, and so on. - -- **`annotations`** *string-string map* - - This OPTIONAL property contains arbitrary metadata for the image manifest. - Annotations MUST be a key-value map where both the key and value MUST be strings. - While the value MUST be present, it MAY be an empty string. - Keys MUST be unique within this map, and best practice is to namespace the keys. - Keys SHOULD be named using a reverse domain notation - e.g. `com.example.myKey`. - Keys using the `org.opencontainers` namespace are reserved and MUST NOT be used by other specifications. - If there are no annotations then this property MUST either be absent or be an empty map. - Implementations that are reading/processing the image manifest MUST NOT generate an error if they encounter an unknown annotation key. - - See [Pre-Defined Annotation Keys](#pre-defined-annotation-keys). - -### Extensibility -Implementations that are reading/processing image manifests MUST NOT generate an error if they encounter an unknown property. -Instead they MUST ignore unknown properties. - -## Example Image Manifest - -*Example showing an image manifest:* -```json,title=Manifest&mediatype=application/vnd.oci.image.manifest.v1%2Bjson -{ - "schemaVersion": 2, - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "config": { - "mediaType": "application/vnd.oci.image.config.v1+json", - "size": 7023, - "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7" - }, - "layers": [ - { - "mediaType": "application/vnd.oci.image.layer.tar+gzip", - "size": 32654, - "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f" - }, - { - "mediaType": "application/vnd.oci.image.layer.tar+gzip", - "size": 16724, - "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b" - }, - { - "mediaType": "application/vnd.oci.image.layer.tar+gzip", - "size": 73109, - "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736" - } - ], - "annotations": { - "com.example.key1": "value1", - "com.example.key2": "value2" - } -} -``` - -# Pre-Defined Annotation Keys -This specification defines the following annotation keys, which MAY be used by manifest list and image manifest authors: -* **org.opencontainers.created** date on which the image was built (string, date-time as defined by [RFC 3339](https://tools.ietf.org/html/rfc3339#section-5.6)). -* **org.opencontainers.authors** contact details of the people or organization responsible for the image (freeform string) -* **org.opencontainers.homepage** URL to find more information on the image (string, must be a URL with scheme HTTP or HTTPS) -* **org.opencontainers.documentation** URL to get documentation on the image (string, must be a URL with scheme HTTP or HTTPS) diff --git a/media-types.md b/media-types.md deleted file mode 100644 index 041a209..0000000 --- a/media-types.md +++ /dev/null @@ -1,49 +0,0 @@ -# Media Types - -The following media types identify the formats described here and their referenced resources: - -- `application/vnd.oci.descriptor.v1+json`: [Content Descriptor](descriptor.md) -- `application/vnd.oci.image.manifest.list.v1+json`: [Manifest list](manifest.md#manifest-list) -- `application/vnd.oci.image.manifest.v1+json`: [Image manifest format](manifest.md#image-manifest) -- `application/vnd.oci.image.config.v1+json`: [Container config JSON](config.md) -- `application/vnd.oci.image.layer.tar+gzip`: ["Layer", as a gzipped tar archive](layer.md) -- `application/vnd.oci.image.layer.nondistributable.tar+gzip`: ["Layer", as a gzipped tar that has distribution restrictions](layer.md#non-distributable-layers) - -## Compatibility Matrix - -The OCI Image Specification strives to be backwards and forwards compatible when possible. -Breaking compatibility with existing systems creates a burden on users whether they be build systems, distribution systems, container engines, etc. -This section shows where the OCI Image Specification is compatible with formats external to the OCI Image and different versions of this specification. - -### application/vnd.oci.image.manifest.list.v1+json - -**Similar/related schema** - -- [application/vnd.docker.distribution.manifest.list.v2+json](https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-2.md#manifest-list) - mediaType is different - -### application/vnd.oci.image.manifest.v1+json - -**Similar/related schema** - -- [application/vnd.docker.distribution.manifest.v2+json](https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-2.md#image-manifest-field-descriptions) - -### application/vnd.oci.image.rootfs.tar.gzip - -**Interchangeable and fully compatible mime-types** - -- [application/vnd.docker.image.rootfs.diff.tar.gzip](https://github.com/docker/docker/blob/master/image/spec/v1.md#creating-an-image-filesystem-changeset) - -### application/vnd.oci.image.config.v1+json - -**Similar/related schema** - -- [application/vnd.docker.container.image.v1+json](https://github.com/docker/docker/blob/master/image/spec/v1.md#image-json-description) - -## Relations - -The following figure shows how the above media types reference each other: - -![](img/media-types.png) - -A reference is defined as the target content digest, as defined by the [Registry V2 HTTP API Specificiation](https://docs.docker.com/registry/spec/api/#digest-parameter). -The manifest list being a "fat manifest" references one or more image manifests per target platform. An image manifest references exactly one target configuration and possibly many layers. diff --git a/project.md b/project.md index 440484b..90a91e2 100644 --- a/project.md +++ b/project.md @@ -3,5 +3,5 @@ ## Release Process * `git tag` the prior commit (preferably signed tag) -* Make a release on [github.com/opencontainers/image-spec](https://github.com/opencontainers/image-spec/releases) for the version. Attach the produced docs. +* Make a release on [github.com/opencontainers/image-tools](https://github.com/opencontainers/image-tools/releases) for the version. Attach the produced docs. diff --git a/schema/config-schema.json b/schema/config-schema.json deleted file mode 100644 index 3de0e73..0000000 --- a/schema/config-schema.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "description": "OpenContainer Config Specification", - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "https://opencontainers.org/schema/image/config", - "type": "object", - "properties": { - "created": { - "type": "string", - "format": "date-time" - }, - "author": { - "type": "string" - }, - "architecture": { - "type": "string" - }, - "os": { - "type": "string" - }, - "config": { - "$ref": "defs-config.json#/definitions/config" - }, - "rootfs": { - "$ref": "defs-config.json#/definitions/rootfs" - }, - "history": { - "type": "array", - "items": { - "$ref": "defs-config.json#/definitions/history" - } - } - } -} diff --git a/schema/config_test.go b/schema/config_test.go deleted file mode 100644 index 27c1487..0000000 --- a/schema/config_test.go +++ /dev/null @@ -1,165 +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_test - -import ( - "strings" - "testing" - - "github.com/opencontainers/image-spec/schema" -) - -func TestConfig(t *testing.T) { - for i, tt := range []struct { - config string - fail bool - }{ - // expected failure: field "os" has numeric value, must be string - { - config: ` -{ - "architecture": "amd64", - "os": 123 -} -`, - fail: true, - }, - - // expected failure: field "config.User" has numeric value, must be string - { - config: ` -{ - "created": "2015-10-31T22:22:56.015925234Z", - "author": "Alyssa P. Hacker ", - "architecture": "amd64", - "os": "linux", - "config": { - "User": 1234 - } -} -`, - fail: true, - }, - - // expected failue: history has string value, must be an array - { - config: ` -{ - "history": "should be an array" -} -`, - fail: true, - }, - - // expected failure: Env has numeric value, must be a string - { - config: ` -{ - "config": { - "Env": [ - 7353 - ] - } -} -`, - fail: true, - }, - - // expected failure: config.Volumes has string array, must be an object (string set) - { - config: ` -{ - "config": { - "Volumes": [ - "/var/job-result-data", - "/var/log/my-app-logs" - ] - } -} -`, - fail: true, - }, - - // expected failue: invalid JSON - { - config: `invalid JSON`, - fail: true, - }, - - { - config: ` -{ - "created": "2015-10-31T22:22:56.015925234Z", - "author": "Alyssa P. Hacker ", - "architecture": "amd64", - "os": "linux", - "config": { - "User": "1:1", - "Memory": 2048, - "MemorySwap": 4096, - "CpuShares": 8, - "ExposedPorts": { - "8080/tcp": {} - }, - "Env": [ - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "FOO=docker_is_a_really", - "BAR=great_tool_you_know" - ], - "Entrypoint": [ - "/bin/sh" - ], - "Cmd": [ - "--foreground", - "--config", - "/etc/my-app.d/default.cfg" - ], - "Volumes": { - "/var/job-result-data": {}, - "/var/log/my-app-logs": {} - }, - "WorkingDir": "/home/alice" - }, - "rootfs": { - "diff_ids": [ - "sha256:9d3dd9504c685a304985025df4ed0283e47ac9ffa9bd0326fddf4d59513f0827", - "sha256:2b689805fbd00b2db1df73fae47562faac1a626d5f61744bfe29946ecff5d73d" - ], - "type": "layers" - }, - "history": [ - { - "created": "2015-10-31T22:22:54.690851953Z", - "created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /" - }, - { - "created": "2015-10-31T22:22:55.613815829Z", - "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]", - "empty_layer": true - } - ] -} -`, - fail: false, - }, - } { - r := strings.NewReader(tt.config) - err := schema.MediaTypeImageConfig.Validate(r) - - if got := err != nil; tt.fail != got { - t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) - } - } -} diff --git a/schema/content-descriptor.json b/schema/content-descriptor.json deleted file mode 100644 index 2e6ceba..0000000 --- a/schema/content-descriptor.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "description": "OpenContainer Content Descriptor Specification", - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "https://opencontainers.org/schema/descriptor", - "type": "object", - "properties": { - "mediaType": { - "description": "the mediatype of the referenced object", - "$ref": "defs-image.json#definitions/mediaType" - }, - "size": { - "description": "the size in bytes of the referenced object", - "$ref": "defs.json#/definitions/int64" - }, - "digest": { - "$ref": "defs-image.json#definitions/digest" - } - }, - "required": [ - "mediaType", - "size", - "digest" - ] -} diff --git a/schema/defs-config.json b/schema/defs-config.json deleted file mode 100644 index 47ad2c6..0000000 --- a/schema/defs-config.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "description": "Definitions particular to OpenContainer Config Specification", - "definitions": { - "config": { - "type": "object", - "properties": { - "User": { - "type": "string" - }, - "Memory": { - "$ref": "defs.json#/definitions/int64" - }, - "MemorySwap": { - "$ref": "defs.json#/definitions/int64" - }, - "CpuShares": { - "$ref": "defs.json#/definitions/int64" - }, - "ExposedPorts": { - "$ref": "defs.json#/definitions/mapStringObject" - }, - "Env": { - "type": "array", - "items": { - "type": "string" - } - }, - "Entrypoint": { - "oneOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "null" - } - ] - }, - "Cmd": { - "oneOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "null" - } - ] - }, - "Volumes": { - "oneOf": [ - { - "$ref": "defs.json#/definitions/mapStringObject" - }, - { - "type": "null" - } - ] - }, - "WorkingDir": { - "type": "string" - } - } - }, - "rootfs": { - "type": "object", - "properties": { - "diff_ids": { - "type": "array", - "items": { - "type": "string" - } - }, - "layers": { - "type": "string" - } - } - }, - "history": { - "type": "object", - "properties": { - "created": { - "type": "string", - "format": "date-time" - }, - "author": { - "type": "string" - }, - "created_by": { - "type": "string" - }, - "comment": { - "type": "string" - }, - "empty_layer": { - "type": "boolean" - } - } - } - } -} diff --git a/schema/defs-image.json b/schema/defs-image.json deleted file mode 100644 index c188463..0000000 --- a/schema/defs-image.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "description": "Definitions particular to OpenContainer Image Specification", - "definitions": { - "mediaType": { - "id": "https://opencontainers.org/schema/image/mediaType", - "type": "string", - "pattern": "^[a-z]+/[0-9a-zA-Z.+]+$" - }, - "digest": { - "description": "the cryptographic checksum digest of the object, in the pattern ':'", - "type": "string", - "pattern": "^[a-z0-9_+.-]+:[a-f0-9]+$" - }, - "manifestDescriptor": { - "id": "https://opencontainers.org/schema/image/manifestDescriptor", - "type": "object", - "required": [ - "mediaType", - "size", - "digest", - "platform" - ], - "properties": { - "mediaType": { - "description": "the mediatype of the referenced object", - "$ref": "#definitions/mediaType" - }, - "size": { - "description": "the size in bytes of the referenced object", - "$ref": "defs.json#/definitions/int64" - }, - "digest": { - "$ref": "#definitions/digest" - }, - "platform": { - "id": "https://opencontainers.org/schema/image/platform", - "type": "object", - "required": [ - "architecture", - "os" - ], - "properties": { - "architecture": { - "id": "https://opencontainers.org/schema/image/platform/architecture", - "type": "string" - }, - "os": { - "id": "https://opencontainers.org/schema/image/platform/os", - "type": "string" - }, - "os.version": { - "id": "https://opencontainers.org/schema/image/platform/os.version", - "type": "string" - }, - "os.features": { - "id": "https://opencontainers.org/schema/image/platform/os.features", - "type": "array", - "items": { - "type": "string" - } - }, - "variant": { - "type": "string" - }, - "features": { - "type": "array", - "items": { - "type": "string" - }, - "additionalProperties": false - } - } - } - } - }, - "annotations": { - "id": "https://opencontainers.org/schema/image/annotations", - "oneOf": [ - { - "$ref": "defs.json#/definitions/mapStringString" - }, - { - "type": "null" - } - ] - } - } -} diff --git a/schema/defs.json b/schema/defs.json deleted file mode 100644 index 4eccc5e..0000000 --- a/schema/defs.json +++ /dev/null @@ -1,168 +0,0 @@ -{ - "description": "Definitions used throughout the OpenContainer Specification", - "definitions": { - "int8": { - "type": "integer", - "minimum": -128, - "maximum": 127 - }, - "int16": { - "type": "integer", - "minimum": -32768, - "maximum": 32767 - }, - "int32": { - "type": "integer", - "minimum": -2147483648, - "maximum": 2147483647 - }, - "int64": { - "type": "integer", - "minimum": -9223372036854776000, - "maximum": 9223372036854776000 - }, - "uint8": { - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - "uint16": { - "type": "integer", - "minimum": 0, - "maximum": 65535 - }, - "uint32": { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - }, - "uint64": { - "type": "integer", - "minimum": 0, - "maximum": 18446744073709552000 - }, - "uint16Pointer": { - "oneOf": [ - { - "$ref": "#/definitions/uint16" - }, - { - "type": "null" - } - ] - }, - "uint64Pointer": { - "oneOf": [ - { - "$ref": "#/definitions/uint64" - }, - { - "type": "null" - } - ] - }, - "stringPointer": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "mapStringString": { - "type": "object", - "patternProperties": { - ".{1,}": { - "type": "string" - } - } - }, - "mapStringObject": { - "type": "object", - "patternProperties": { - ".{1,}": { - "type": "object" - } - } - }, - "UID": { - "$ref": "#/definitions/uint32" - }, - "GID": { - "$ref": "#/definitions/uint32" - }, - "ArrayOfGIDs": { - "type": "array", - "items": { - "$ref": "#/definitions/GID" - } - }, - "ArrayOfStrings": { - "type": "array", - "items": { - "type": "string" - } - }, - "FilePath": { - "type": "string" - }, - "Env": { - "$ref": "#/definitions/ArrayOfStrings" - }, - "Hook": { - "properties": { - "path": { - "$ref": "#/definitions/FilePath" - }, - "args": { - "$ref": "#/definitions/ArrayOfStrings" - }, - "env": { - "$ref": "#/definitions/Env" - } - } - }, - "ArrayOfHooks": { - "type": "array", - "items": { - "$ref": "#/definitions/Hook" - } - }, - "IDMapping": { - "properties": { - "hostID": { - "$ref": "#/definitions/uint32" - }, - "containerID": { - "$ref": "#/definitions/uint32" - }, - "size": { - "$ref": "#/definitions/uint32" - } - } - }, - "Mount": { - "properties": { - "source": { - "$ref": "#/definitions/FilePath" - }, - "destination": { - "$ref": "#/definitions/FilePath" - }, - "options": { - "$ref": "#/definitions/ArrayOfStrings" - }, - "type": { - "type": "string" - } - }, - "required": [ - "destination", - "source", - "type" - ] - } - } -} diff --git a/schema/image-manifest-schema.json b/schema/image-manifest-schema.json deleted file mode 100644 index f25c85f..0000000 --- a/schema/image-manifest-schema.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "description": "OpenContainer Image Manifest Specification", - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "https://opencontainers.org/schema/image/manifest", - "type": "object", - "properties": { - "schemaVersion": { - "description": "This field specifies the image manifest schema version as an integer", - "id": "https://opencontainers.org/schema/image/manifest/schemaVersion", - "type": "integer" - }, - "mediaType": { - "id": "https://opencontainers.org/schema/image/manifest/mediaType", - "$ref": "defs-image.json#/definitions/mediaType" - }, - "config": { - "$ref": "content-descriptor.json" - }, - "layers": { - "type": "array", - "items": { - "$ref": "content-descriptor.json" - } - }, - "annotations": { - "id": "https://opencontainers.org/schema/image/manifest-list/annotations", - "$ref": "defs-image.json#/definitions/annotations" - } - }, - "required": [ - "schemaVersion", - "mediaType", - "config", - "layers" - ] -} diff --git a/schema/manifest-list-schema.json b/schema/manifest-list-schema.json deleted file mode 100644 index 261db7d..0000000 --- a/schema/manifest-list-schema.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "description": "OpenContainer Image Manifest List Specification", - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "https://opencontainers.org/schema/image/manifest-list", - "type": "object", - "properties": { - "schemaVersion": { - "description": "This field specifies the image manifest-list schema version as an integer", - "id": "https://opencontainers.org/schema/image/manifest-list/schemaVersion", - "type": "integer" - }, - "mediaType": { - "id": "https://opencontainers.org/schema/image/manifest-list/mediaType", - "$ref": "defs-image.json#/definitions/mediaType" - }, - "manifests": { - "type": "array", - "items": { - "$ref": "defs-image.json#/definitions/manifestDescriptor" - } - }, - "annotations": { - "id": "https://opencontainers.org/schema/image/manifest-list/annotations", - "$ref": "defs-image.json#/definitions/annotations" - } - }, - "required": [ - "schemaVersion", - "mediaType", - "manifests" - ] -} diff --git a/schema/manifest_backwards_compatibility_test.go b/schema/manifest_backwards_compatibility_test.go deleted file mode 100644 index 89e1866..0000000 --- a/schema/manifest_backwards_compatibility_test.go +++ /dev/null @@ -1,229 +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_test - -import ( - "crypto/sha256" - "encoding/hex" - "fmt" - "strings" - "testing" - - "github.com/opencontainers/image-spec/schema" -) - -var compatMap = map[string]string{ - "application/vnd.docker.distribution.manifest.list.v2+json": "application/vnd.oci.image.manifest.list.v1+json", - "application/vnd.docker.distribution.manifest.v2+json": "application/vnd.oci.image.manifest.v1+json", - "application/vnd.docker.image.rootfs.diff.tar.gzip": "application/vnd.oci.image.rootfs.tar.gzip", - "application/vnd.docker.container.image.v1+json": "application/vnd.oci.image.config.v1+json", -} - -// convertFormats converts Docker v2.2 image format JSON documents to OCI -// format by simply replacing instances of the strings found in the compatMap -// found in the input string. -func convertFormats(input string) string { - out := input - for k, v := range compatMap { - out = strings.Replace(out, v, k, -1) - } - return out -} - -func TestBackwardsCompatibilityManifestList(t *testing.T) { - for i, tt := range []struct { - manifest string - digest string - fail bool - }{ - { - digest: "sha256:e588eb8123f2031a41f2e60bc27f30a4388e181e07410aff392f7dc96b585969", - manifest: `{ - "schemaVersion": 2, - "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", - "manifests": [ - { - "mediaType": "application/vnd.docker.distribution.manifest.v1+json", - "size": 2094, - "digest": "sha256:7820f9a86d4ad15a2c4f0c0e5479298df2aa7c2f6871288e2ef8546f3e7b6783", - "platform": { - "architecture": "ppc64le", - "os": "linux" - } - }, - { - "mediaType": "application/vnd.docker.distribution.manifest.v1+json", - "size": 1922, - "digest": "sha256:ae1b0e06e8ade3a11267564a26e750585ba2259c0ecab59ab165ad1af41d1bdd", - "platform": { - "architecture": "amd64", - "os": "linux", - "features": [ - "sse" - ] - } - }, - { - "mediaType": "application/vnd.docker.distribution.manifest.v1+json", - "size": 2084, - "digest": "sha256:e4c0df75810b953d6717b8f8f28298d73870e8aa2a0d5e77b8391f16fdfbbbe2", - "platform": { - "architecture": "s390x", - "os": "linux" - } - }, - { - "mediaType": "application/vnd.docker.distribution.manifest.v1+json", - "size": 2084, - "digest": "sha256:07ebe243465ef4a667b78154ae6c3ea46fdb1582936aac3ac899ea311a701b40", - "platform": { - "architecture": "arm", - "os": "linux", - "variant": "armv7" - } - }, - { - "mediaType": "application/vnd.docker.distribution.manifest.v1+json", - "size": 2090, - "digest": "sha256:fb2fc0707b86dafa9959fe3d29e66af8787aee4d9a23581714be65db4265ad8a", - "platform": { - "architecture": "arm64", - "os": "linux", - "variant": "armv8" - } - } - ] -}`, - fail: false, - }, - } { - sum := sha256.Sum256([]byte(tt.manifest)) - got := fmt.Sprintf("sha256:%s", hex.EncodeToString(sum[:])) - if tt.digest != got { - t.Errorf("test %d: expected digest %s but got %s", i, tt.digest, got) - } - - manifest := convertFormats(tt.manifest) - r := strings.NewReader(manifest) - err := schema.MediaTypeManifestList.Validate(r) - - if got := err != nil; tt.fail != got { - t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) - } - } -} - -func TestBackwardsCompatibilityManifest(t *testing.T) { - for i, tt := range []struct { - manifest string - digest string - fail bool - }{ - // manifest pulled from docker hub using hash value - // - // curl -L -H "Authorization: Bearer ..." -H \ - // "Accept: application/vnd.docker.distribution.manifest.v2+json" \ - // https://registry-1.docker.io/v2/library/docker/manifests/sha256:888206c77cd2811ec47e752ba291e5b7734e3ef137dfd222daadaca39a9f17bc - { - digest: "sha256:888206c77cd2811ec47e752ba291e5b7734e3ef137dfd222daadaca39a9f17bc", - manifest: `{ - "schemaVersion": 2, - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "config": { - "mediaType": "application/octet-stream", - "size": 3210, - "digest": "sha256:5359a4f250650c20227055957e353e8f8a74152f35fe36f00b6b1f9fc19c8861" - }, - "layers": [ - { - "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "size": 2310272, - "digest": "sha256:fae91920dcd4542f97c9350b3157139a5d901362c2abec284de5ebd1b45b4957" - }, - { - "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "size": 913022, - "digest": "sha256:f384f6ab36adad485192f09379c0b58dc612a3cde82c551e082a7c29a87c95da" - }, - { - "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "size": 9861668, - "digest": "sha256:ed0d2dd5e1a0e5e650a330a864c8a122e9aa91fa6ba9ac6f0bd1882e59df55e7" - }, - { - "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "size": 465, - "digest": "sha256:ec4d00b58417c45f7ddcfde7bcad8c9d62a7d6d5d17cdc1f7d79bcb2e22c1491" - } - ] -}`, - fail: false, - }, - } { - sum := sha256.Sum256([]byte(tt.manifest)) - got := fmt.Sprintf("sha256:%s", hex.EncodeToString(sum[:])) - if tt.digest != got { - t.Errorf("test %d: expected digest %s but got %s", i, tt.digest, got) - } - - manifest := convertFormats(tt.manifest) - r := strings.NewReader(manifest) - err := schema.MediaTypeManifest.Validate(r) - - if got := err != nil; tt.fail != got { - t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) - } - } -} - -func TestBackwardsCompatibilityConfig(t *testing.T) { - for i, tt := range []struct { - manifest string - digest string - fail bool - }{ - // manifest pulled from docker hub blob store - // - // curl -L -H "Authorization: Bearer ..." -H \ - // -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \ - // https://registry-1.docker.io/v2/library/docker/blobs/sha256:5359a4f250650c20227055957e353e8f8a74152f35fe36f00b6b1f9fc19c8861 - { - digest: "sha256:5359a4f250650c20227055957e353e8f8a74152f35fe36f00b6b1f9fc19c8861", - manifest: `{"architecture":"amd64","config":{"Hostname":"e5e5b3910a57","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","DOCKER_BUCKET=get.docker.com","DOCKER_VERSION=1.10.3","DOCKER_SHA256=d0df512afa109006a450f41873634951e19ddabf8c7bd419caeb5a526032d86d"],"Cmd":["sh"],"ArgsEscaped":true,"Image":"sha256:bda352ba7ab5823b7dc74b380c5ad1699edee278a6d2ebbe451129b108778742","Volumes":null,"WorkingDir":"","Entrypoint":["docker-entrypoint.sh"],"OnBuild":[],"Labels":{}},"container":"881be788b4387039b52fa195da9fe26f264385aa497ce650cfdcf3806c2d2021","container_config":{"Hostname":"e5e5b3910a57","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","DOCKER_BUCKET=get.docker.com","DOCKER_VERSION=1.10.3","DOCKER_SHA256=d0df512afa109006a450f41873634951e19ddabf8c7bd419caeb5a526032d86d"],"Cmd":["/bin/sh","-c","#(nop) CMD [\"sh\"]"],"ArgsEscaped":true,"Image":"sha256:bda352ba7ab5823b7dc74b380c5ad1699edee278a6d2ebbe451129b108778742","Volumes":null,"WorkingDir":"","Entrypoint":["docker-entrypoint.sh"],"OnBuild":[],"Labels":{}},"created":"2016-06-08T00:52:29.30472774Z","docker_version":"1.10.3","history":[{"created":"2016-06-08T00:48:01.932532048Z","created_by":"/bin/sh -c #(nop) ADD file:bca92e550bd2ce926584aef2032464b6ebf543ce69133b6602c781866165d703 in /"},{"created":"2016-06-08T00:52:10.503417531Z","created_by":"/bin/sh -c apk add --no-cache \t\tca-certificates \t\tcurl \t\topenssl"},{"created":"2016-06-08T00:52:10.700704697Z","created_by":"/bin/sh -c #(nop) ENV DOCKER_BUCKET=get.docker.com","empty_layer":true},{"created":"2016-06-08T00:52:25.746175479Z","created_by":"/bin/sh -c #(nop) ENV DOCKER_VERSION=1.10.3","empty_layer":true},{"created":"2016-06-08T00:52:25.954613633Z","created_by":"/bin/sh -c #(nop) ENV DOCKER_SHA256=d0df512afa109006a450f41873634951e19ddabf8c7bd419caeb5a526032d86d","empty_layer":true},{"created":"2016-06-08T00:52:28.173693898Z","created_by":"/bin/sh -c curl -fSL \"https://${DOCKER_BUCKET}/builds/Linux/x86_64/docker-$DOCKER_VERSION\" -o /usr/local/bin/docker \t\u0026\u0026 echo \"${DOCKER_SHA256} /usr/local/bin/docker\" | sha256sum -c - \t\u0026\u0026 chmod +x /usr/local/bin/docker"},{"created":"2016-06-08T00:52:28.924486515Z","created_by":"/bin/sh -c #(nop) COPY file:50006c902e7677711aeffe4ab7b7042d649618b96dec760f322a8566dd83ab25 in /usr/local/bin/"},{"created":"2016-06-08T00:52:29.121963047Z","created_by":"/bin/sh -c #(nop) ENTRYPOINT \u0026{[\"docker-entrypoint.sh\"]}","empty_layer":true},{"created":"2016-06-08T00:52:29.30472774Z","created_by":"/bin/sh -c #(nop) CMD [\"sh\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:77f08abee8bf9334407f52d104e1891283018450b3c196118ddfe31505126b87","sha256:707d16737060172b977d5f7eaaddfcfaae1008472193d7e8e5a01111a5f8dd5c","sha256:44da042e7b2458ee0b3877f2321cdf4fd45a49b9b51e00492c2ba68055573eff","sha256:1bc2be83dce13b9bac9476c9c1d2ca6e0db3e07b443f7298fc5a1af75b2cb4ef"]}}`, - fail: false, - }, - { - // fedora:23 from docker hub - // both Entrypoint and Cmd can be nullable - digest: "sha256:a20665eb1fe2912accb3d5dadaed360430df0d1aa46874875886947d61d3d4ee", - manifest: `{"architecture":"amd64","author":"Patrick Uiterwijk \u003cpatrick@puiterwijk.org\u003e","config":{"Hostname":"8dfe43d80430","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":null,"Image":"sha256:6986ae504bbf843512d680cc959484452034965db15f75ee8bdd1b107f61500b","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"container":"6249cd2c4b1d6b1bf05903364cbcb95781508994d6407c1564d494e748ea1b41","container_config":{"Hostname":"8dfe43d80430","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ADD file:293a6e463aa402bb8f80eb5cfc937f375cedc6843abaeb9eccfe3923bb3fc80b in /"],"Image":"sha256:6986ae504bbf843512d680cc959484452034965db15f75ee8bdd1b107f61500b","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2016-06-10T18:44:31.784795904Z","docker_version":"1.10.3","history":[{"created":"2016-06-10T18:44:03.360264073Z","author":"Patrick Uiterwijk \u003cpatrick@puiterwijk.org\u003e","created_by":"/bin/sh -c #(nop) MAINTAINER Patrick Uiterwijk \u003cpatrick@puiterwijk.org\u003e","empty_layer":true},{"created":"2016-06-10T18:44:31.784795904Z","author":"Patrick Uiterwijk \u003cpatrick@puiterwijk.org\u003e","created_by":"/bin/sh -c #(nop) ADD file:293a6e463aa402bb8f80eb5cfc937f375cedc6843abaeb9eccfe3923bb3fc80b in /"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:d43f38155a799dc53d8fbb9f3bc11f51805f4027cd5c3d10b9823201cd5b9400"]}}`, - fail: false, - }, - } { - sum := sha256.Sum256([]byte(tt.manifest)) - got := fmt.Sprintf("sha256:%s", hex.EncodeToString(sum[:])) - if tt.digest != got { - t.Errorf("test %d: expected digest %s but got %s", i, tt.digest, got) - } - - manifest := convertFormats(tt.manifest) - r := strings.NewReader(manifest) - err := schema.MediaTypeImageConfig.Validate(r) - - if got := err != nil; tt.fail != got { - t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) - } - } -} diff --git a/schema/manifest_test.go b/schema/manifest_test.go deleted file mode 100644 index 821e668..0000000 --- a/schema/manifest_test.go +++ /dev/null @@ -1,124 +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_test - -import ( - "strings" - "testing" - - "github.com/opencontainers/image-spec/schema" -) - -func TestManifest(t *testing.T) { - for i, tt := range []struct { - manifest string - fail bool - }{ - // expected failure: mediaType does not match pattern - { - manifest: ` -{ - "schemaVersion": 2, - "mediaType": "invalid" -} -`, - fail: true, - }, - - // expected failure: config.size is a string, expected integer - { - manifest: ` -{ - "schemaVersion": 2, - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "config": { - "mediaType": "application/vnd.oci.image.config.v1+json", - "size": "1470", - "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" - }, - "layers": [] -} -`, - fail: true, - }, - - // expected failure: layers.size is string, expected integer - { - manifest: ` -{ - "schemaVersion": 2, - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "config": { - "mediaType": "application/vnd.oci.image.config.v1+json", - "size": 1470, - "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" - }, - "layers": [ - { - "mediaType": "application/vnd.oci.image.layer.tar+gzip", - "size": "675598", - "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" - } - ] -} -`, - fail: true, - }, - - // valid manifest - { - manifest: ` -{ - "schemaVersion": 2, - "mediaType": "application/vnd.oci.image.manifest.v1+json", - "config": { - "mediaType": "application/vnd.oci.image.config.v1+json", - "size": 1470, - "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" - }, - "layers": [ - { - "mediaType": "application/vnd.oci.image.layer.tar+gzip", - "size": 675598, - "digest": "sha256:9d3dd9504c685a304985025df4ed0283e47ac9ffa9bd0326fddf4d59513f0827" - }, - { - "mediaType": "application/vnd.oci.image.layer.tar+gzip", - "size": 156, - "digest": "sha256:2b689805fbd00b2db1df73fae47562faac1a626d5f61744bfe29946ecff5d73d" - }, - { - "mediaType": "application/vnd.oci.image.layer.tar+gzip", - "size": 148, - "digest": "sha256:c57089565e894899735d458f0fd4bb17a0f1e0df8d72da392b85c9b35ee777cd" - } - ], - "annotations": { - "key1": "value1", - "key2": "value2" - } -} -`, - fail: false, - }, - } { - r := strings.NewReader(tt.manifest) - err := schema.MediaTypeManifest.Validate(r) - - if got := err != nil; tt.fail != got { - t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) - } - } -} diff --git a/schema/spec_test.go b/schema/spec_test.go deleted file mode 100644 index 1e9da5a..0000000 --- a/schema/spec_test.go +++ /dev/null @@ -1,183 +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_test - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "net/url" - "os" - "strings" - "testing" - - "github.com/opencontainers/image-spec/schema" - "github.com/pkg/errors" - "github.com/russross/blackfriday" -) - -var ( - errFormatInvalid = errors.New("format: invalid") -) - -func TestValidateDescriptor(t *testing.T) { - validate(t, "../descriptor.md") -} - -func TestValidateManifest(t *testing.T) { - validate(t, "../manifest.md") -} - -func TestValidateConfig(t *testing.T) { - validate(t, "../config.md") -} - -// TODO(sur): include examples from all specification files -func validate(t *testing.T, name string) { - m, err := os.Open(name) - if err != nil { - t.Fatal(err) - } - defer m.Close() - - examples, err := extractExamples(m) - if err != nil { - t.Fatal(err) - } - - for _, example := range examples { - if example.Err == errFormatInvalid && example.Mediatype == "" { // ignore - continue - } - - if example.Err != nil { - printFields(t, "error", example.Mediatype, example.Title, example.Err) - t.Error(err) - continue - } - - err = schema.Validator(example.Mediatype).Validate(strings.NewReader(example.Body)) - if err == nil { - printFields(t, "ok", example.Mediatype, example.Title) - t.Log(example.Body, "---") - continue - } - - var errs []error - if verr, ok := errors.Cause(err).(schema.ValidationError); ok { - errs = verr.Errs - } else { - printFields(t, "error", example.Mediatype, example.Title, err) - t.Error(err) - t.Log(example.Body, "---") - continue - } - - for _, err := range errs { - // TOOD(stevvooe): This is nearly useless without file, line no. - printFields(t, "invalid", example.Mediatype, example.Title) - t.Error(err) - fmt.Println(example.Body, "---") - continue - } - } -} - -// renderer allows one to incercept fenced blocks in markdown documents. -type renderer struct { - blackfriday.Renderer - fn func(text []byte, lang string) -} - -func (r *renderer) BlockCode(out *bytes.Buffer, text []byte, lang string) { - r.fn(text, lang) - r.Renderer.BlockCode(out, text, lang) -} - -type example struct { - Lang string // gets raw "lang" field - Title string - Mediatype string - Body string - Err error - - // TODO(stevvooe): Figure out how to keep track of revision, file, line so - // that we can trace back verification output. -} - -// parseExample treats the field as a syntax,attribute tuple separated by a comma. -// Attributes are encoded as a url values. -// -// An example of this is `json,title=Foo%20Bar&mediatype=application/json. We -// get that the "lang" is json, the title is "Foo Bar" and the mediatype is -// "application/json". -// -// This preserves syntax highlighting and lets us tag examples with further -// metadata. -func parseExample(lang, body string) (e example) { - e.Lang = lang - e.Body = body - - parts := strings.SplitN(lang, ",", 2) - if len(parts) < 2 { - e.Err = errFormatInvalid - return - } - - m, err := url.ParseQuery(parts[1]) - if err != nil { - e.Err = err - return - } - - e.Mediatype = m.Get("mediatype") - e.Title = m.Get("title") - return -} - -func extractExamples(rd io.Reader) ([]example, error) { - p, err := ioutil.ReadAll(rd) - if err != nil { - return nil, err - } - - var examples []example - renderer := &renderer{ - Renderer: blackfriday.HtmlRenderer(0, "test test", ""), - fn: func(text []byte, lang string) { - examples = append(examples, parseExample(lang, string(text))) - }, - } - - // just pass over the markdown and ignore the rendered result. We just want - // the side-effect of calling back for each code block. - // TODO(stevvooe): Consider just parsing these with a scanner. It will be - // faster and we can retain file, line no. - blackfriday.MarkdownOptions(p, renderer, blackfriday.Options{ - Extensions: blackfriday.EXTENSION_FENCED_CODE, - }) - - return examples, nil -} - -// printFields prints each value tab separated. -func printFields(t *testing.T, vs ...interface{}) { - var ss []string - for _, f := range vs { - ss = append(ss, fmt.Sprint(f)) - } - t.Log(strings.Join(ss, "\t")) -} diff --git a/vendor/github.com/opencontainers/image-spec/LICENSE b/vendor/github.com/opencontainers/image-spec/LICENSE new file mode 100644 index 0000000..9fdc20f --- /dev/null +++ b/vendor/github.com/opencontainers/image-spec/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2016 The Linux Foundation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/schema/doc.go b/vendor/github.com/opencontainers/image-spec/schema/doc.go similarity index 100% rename from schema/doc.go rename to vendor/github.com/opencontainers/image-spec/schema/doc.go diff --git a/schema/error.go b/vendor/github.com/opencontainers/image-spec/schema/error.go similarity index 100% rename from schema/error.go rename to vendor/github.com/opencontainers/image-spec/schema/error.go diff --git a/schema/fs.go b/vendor/github.com/opencontainers/image-spec/schema/fs.go similarity index 100% rename from schema/fs.go rename to vendor/github.com/opencontainers/image-spec/schema/fs.go diff --git a/schema/gen.go b/vendor/github.com/opencontainers/image-spec/schema/gen.go similarity index 100% rename from schema/gen.go rename to vendor/github.com/opencontainers/image-spec/schema/gen.go diff --git a/schema/schema.go b/vendor/github.com/opencontainers/image-spec/schema/schema.go similarity index 100% rename from schema/schema.go rename to vendor/github.com/opencontainers/image-spec/schema/schema.go diff --git a/schema/validator.go b/vendor/github.com/opencontainers/image-spec/schema/validator.go similarity index 100% rename from schema/validator.go rename to vendor/github.com/opencontainers/image-spec/schema/validator.go diff --git a/specs-go/descriptor.go b/vendor/github.com/opencontainers/image-spec/specs-go/descriptor.go similarity index 100% rename from specs-go/descriptor.go rename to vendor/github.com/opencontainers/image-spec/specs-go/descriptor.go diff --git a/specs-go/v1/config.go b/vendor/github.com/opencontainers/image-spec/specs-go/v1/config.go similarity index 100% rename from specs-go/v1/config.go rename to vendor/github.com/opencontainers/image-spec/specs-go/v1/config.go diff --git a/specs-go/v1/manifest.go b/vendor/github.com/opencontainers/image-spec/specs-go/v1/manifest.go similarity index 100% rename from specs-go/v1/manifest.go rename to vendor/github.com/opencontainers/image-spec/specs-go/v1/manifest.go diff --git a/specs-go/v1/manifest_list.go b/vendor/github.com/opencontainers/image-spec/specs-go/v1/manifest_list.go similarity index 100% rename from specs-go/v1/manifest_list.go rename to vendor/github.com/opencontainers/image-spec/specs-go/v1/manifest_list.go diff --git a/specs-go/v1/mediatype.go b/vendor/github.com/opencontainers/image-spec/specs-go/v1/mediatype.go similarity index 100% rename from specs-go/v1/mediatype.go rename to vendor/github.com/opencontainers/image-spec/specs-go/v1/mediatype.go diff --git a/specs-go/version.go b/vendor/github.com/opencontainers/image-spec/specs-go/version.go similarity index 100% rename from specs-go/version.go rename to vendor/github.com/opencontainers/image-spec/specs-go/version.go diff --git a/specs-go/versioned.go b/vendor/github.com/opencontainers/image-spec/specs-go/versioned.go similarity index 100% rename from specs-go/versioned.go rename to vendor/github.com/opencontainers/image-spec/specs-go/versioned.go diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go index 65bf7a0..1c9731a 100644 --- a/vendor/github.com/pkg/errors/errors.go +++ b/vendor/github.com/pkg/errors/errors.go @@ -28,7 +28,7 @@ // to reverse the operation of errors.Wrap to retrieve the original error // for inspection. Any error value which implements this interface // -// type Causer interface { +// type causer interface { // Cause() error // } // @@ -43,6 +43,9 @@ // // unknown error // } // +// causer interface is not exported by this package, but is considered a part +// of stable public API. +// // Formatted printing of errors // // All error values returned from this package implement fmt.Formatter and can @@ -59,7 +62,7 @@ // New, Errorf, Wrap, and Wrapf record a stack trace at the point they are // invoked. This information can be retrieved with the following interface. // -// type StackTrace interface { +// type stackTracer interface { // StackTrace() errors.StackTrace // } // @@ -67,16 +70,19 @@ // // type StackTrace []Frame // -// The Frame type represents a call site in the stacktrace. Frame supports +// The Frame type represents a call site in the stack trace. Frame supports // the fmt.Formatter interface that can be used for printing information about -// the stacktrace of this error. For example: +// the stack trace of this error. For example: // -// if err, ok := err.(StackTrace); ok { +// if err, ok := err.(stackTracer); ok { // for _, f := range err.StackTrace() { // fmt.Printf("%+s:%d", f) // } // } // +// stackTracer interface is not exported by this package, but is considered a part +// of stable public API. +// // See the documentation for Frame.Format for more details. package errors @@ -85,72 +91,69 @@ import ( "io" ) -// _error is an error implementation returned by New and Errorf -// that implements its own fmt.Formatter. -type _error struct { +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { msg string *stack } -func (e _error) Error() string { return e.msg } +func (f *fundamental) Error() string { return f.msg } -func (e _error) Format(s fmt.State, verb rune) { +func (f *fundamental) Format(s fmt.State, verb rune) { switch verb { case 'v': if s.Flag('+') { - io.WriteString(s, e.msg) - fmt.Fprintf(s, "%+v", e.StackTrace()) + io.WriteString(s, f.msg) + f.stack.Format(s, verb) return } fallthrough case 's': - io.WriteString(s, e.msg) + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) } } -// New returns an error with the supplied message. -func New(message string) error { - return _error{ - message, - callers(), - } -} - -// Errorf formats according to a format specifier and returns the string -// as a value that satisfies error. -func Errorf(format string, args ...interface{}) error { - return _error{ - fmt.Sprintf(format, args...), - callers(), - } -} - -type cause struct { - cause error - msg string -} - -func (c cause) Error() string { return fmt.Sprintf("%s: %v", c.msg, c.Cause()) } -func (c cause) Cause() error { return c.cause } - -// wrapper is an error implementation returned by Wrap and Wrapf -// that implements its own fmt.Formatter. -type wrapper struct { - cause +type withStack struct { + error *stack } -func (w wrapper) Format(s fmt.State, verb rune) { +func (w *withStack) Cause() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { switch verb { case 'v': if s.Flag('+') { - fmt.Fprintf(s, "%+v\n", w.Cause()) - fmt.Fprintf(s, "%+v: %s", w.StackTrace()[0], w.msg) + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) return } fallthrough case 's': io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) } } @@ -160,12 +163,13 @@ func Wrap(err error, message string) error { if err == nil { return nil } - return wrapper{ - cause: cause{ - cause: err, - msg: message, - }, - stack: callers(), + err = &withMessage{ + cause: err, + msg: message, + } + return &withStack{ + err, + callers(), } } @@ -175,12 +179,35 @@ func Wrapf(err error, format string, args ...interface{}) error { if err == nil { return nil } - return wrapper{ - cause: cause{ - cause: err, - msg: fmt.Sprintf(format, args...), - }, - stack: callers(), + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } + return &withStack{ + err, + callers(), + } +} + +type withMessage struct { + cause error + msg string +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) } } @@ -188,7 +215,7 @@ func Wrapf(err error, format string, args ...interface{}) error { // An error value has a cause if it implements the following // interface: // -// type Causer interface { +// type causer interface { // Cause() error // } // diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go index 243a64a..6b1f289 100644 --- a/vendor/github.com/pkg/errors/stack.go +++ b/vendor/github.com/pkg/errors/stack.go @@ -100,6 +100,19 @@ func (st StackTrace) Format(s fmt.State, verb rune) { // stack represents a stack of program counters. type stack []uintptr +func (s *stack) Format(st fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case st.Flag('+'): + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + func (s *stack) StackTrace() StackTrace { f := make([]Frame, len(*s)) for i := 0; i < len(f); i++ { diff --git a/vendor/github.com/russross/blackfriday/LICENSE.txt b/vendor/github.com/russross/blackfriday/LICENSE.txt deleted file mode 100644 index 2885af3..0000000 --- a/vendor/github.com/russross/blackfriday/LICENSE.txt +++ /dev/null @@ -1,29 +0,0 @@ -Blackfriday is distributed under the Simplified BSD License: - -> Copyright © 2011 Russ Ross -> All rights reserved. -> -> Redistribution and use in source and binary forms, with or without -> modification, are permitted provided that the following conditions -> are met: -> -> 1. Redistributions of source code must retain the above copyright -> notice, this list of conditions and the following disclaimer. -> -> 2. Redistributions in binary form must reproduce the above -> copyright notice, this list of conditions and the following -> disclaimer in the documentation and/or other materials provided with -> the distribution. -> -> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -> COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -> BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -> LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -> ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -> POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/russross/blackfriday/block.go b/vendor/github.com/russross/blackfriday/block.go deleted file mode 100644 index b5b0841..0000000 --- a/vendor/github.com/russross/blackfriday/block.go +++ /dev/null @@ -1,1398 +0,0 @@ -// -// Blackfriday Markdown Processor -// Available at http://github.com/russross/blackfriday -// -// Copyright © 2011 Russ Ross . -// Distributed under the Simplified BSD License. -// See README.md for details. -// - -// -// Functions to parse block-level elements. -// - -package blackfriday - -import ( - "bytes" - - "github.com/shurcooL/sanitized_anchor_name" -) - -// Parse block-level data. -// Note: this function and many that it calls assume that -// the input buffer ends with a newline. -func (p *parser) block(out *bytes.Buffer, data []byte) { - if len(data) == 0 || data[len(data)-1] != '\n' { - panic("block input is missing terminating newline") - } - - // this is called recursively: enforce a maximum depth - if p.nesting >= p.maxNesting { - return - } - p.nesting++ - - // parse out one block-level construct at a time - for len(data) > 0 { - // prefixed header: - // - // # Header 1 - // ## Header 2 - // ... - // ###### Header 6 - if p.isPrefixHeader(data) { - data = data[p.prefixHeader(out, data):] - continue - } - - // block of preformatted HTML: - // - //
- // ... - //
- if data[0] == '<' { - if i := p.html(out, data, true); i > 0 { - data = data[i:] - continue - } - } - - // title block - // - // % stuff - // % more stuff - // % even more stuff - if p.flags&EXTENSION_TITLEBLOCK != 0 { - if data[0] == '%' { - if i := p.titleBlock(out, data, true); i > 0 { - data = data[i:] - continue - } - } - } - - // blank lines. note: returns the # of bytes to skip - if i := p.isEmpty(data); i > 0 { - data = data[i:] - continue - } - - // indented code block: - // - // func max(a, b int) int { - // if a > b { - // return a - // } - // return b - // } - if p.codePrefix(data) > 0 { - data = data[p.code(out, data):] - continue - } - - // fenced code block: - // - // ``` go - // func fact(n int) int { - // if n <= 1 { - // return n - // } - // return n * fact(n-1) - // } - // ``` - if p.flags&EXTENSION_FENCED_CODE != 0 { - if i := p.fencedCode(out, data, true); i > 0 { - data = data[i:] - continue - } - } - - // horizontal rule: - // - // ------ - // or - // ****** - // or - // ______ - if p.isHRule(data) { - p.r.HRule(out) - var i int - for i = 0; data[i] != '\n'; i++ { - } - data = data[i:] - continue - } - - // block quote: - // - // > A big quote I found somewhere - // > on the web - if p.quotePrefix(data) > 0 { - data = data[p.quote(out, data):] - continue - } - - // table: - // - // Name | Age | Phone - // ------|-----|--------- - // Bob | 31 | 555-1234 - // Alice | 27 | 555-4321 - if p.flags&EXTENSION_TABLES != 0 { - if i := p.table(out, data); i > 0 { - data = data[i:] - continue - } - } - - // an itemized/unordered list: - // - // * Item 1 - // * Item 2 - // - // also works with + or - - if p.uliPrefix(data) > 0 { - data = data[p.list(out, data, 0):] - continue - } - - // a numbered/ordered list: - // - // 1. Item 1 - // 2. Item 2 - if p.oliPrefix(data) > 0 { - data = data[p.list(out, data, LIST_TYPE_ORDERED):] - continue - } - - // definition lists: - // - // Term 1 - // : Definition a - // : Definition b - // - // Term 2 - // : Definition c - if p.flags&EXTENSION_DEFINITION_LISTS != 0 { - if p.dliPrefix(data) > 0 { - data = data[p.list(out, data, LIST_TYPE_DEFINITION):] - continue - } - } - - // anything else must look like a normal paragraph - // note: this finds underlined headers, too - data = data[p.paragraph(out, data):] - } - - p.nesting-- -} - -func (p *parser) isPrefixHeader(data []byte) bool { - if data[0] != '#' { - return false - } - - if p.flags&EXTENSION_SPACE_HEADERS != 0 { - level := 0 - for level < 6 && data[level] == '#' { - level++ - } - if data[level] != ' ' { - return false - } - } - return true -} - -func (p *parser) prefixHeader(out *bytes.Buffer, data []byte) int { - level := 0 - for level < 6 && data[level] == '#' { - level++ - } - i := skipChar(data, level, ' ') - end := skipUntilChar(data, i, '\n') - skip := end - id := "" - if p.flags&EXTENSION_HEADER_IDS != 0 { - j, k := 0, 0 - // find start/end of header id - for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ { - } - for k = j + 1; k < end && data[k] != '}'; k++ { - } - // extract header id iff found - if j < end && k < end { - id = string(data[j+2 : k]) - end = j - skip = k + 1 - for end > 0 && data[end-1] == ' ' { - end-- - } - } - } - for end > 0 && data[end-1] == '#' { - if isBackslashEscaped(data, end-1) { - break - } - end-- - } - for end > 0 && data[end-1] == ' ' { - end-- - } - if end > i { - if id == "" && p.flags&EXTENSION_AUTO_HEADER_IDS != 0 { - id = sanitized_anchor_name.Create(string(data[i:end])) - } - work := func() bool { - p.inline(out, data[i:end]) - return true - } - p.r.Header(out, work, level, id) - } - return skip -} - -func (p *parser) isUnderlinedHeader(data []byte) int { - // test of level 1 header - if data[0] == '=' { - i := skipChar(data, 1, '=') - i = skipChar(data, i, ' ') - if data[i] == '\n' { - return 1 - } else { - return 0 - } - } - - // test of level 2 header - if data[0] == '-' { - i := skipChar(data, 1, '-') - i = skipChar(data, i, ' ') - if data[i] == '\n' { - return 2 - } else { - return 0 - } - } - - return 0 -} - -func (p *parser) titleBlock(out *bytes.Buffer, data []byte, doRender bool) int { - if data[0] != '%' { - return 0 - } - splitData := bytes.Split(data, []byte("\n")) - var i int - for idx, b := range splitData { - if !bytes.HasPrefix(b, []byte("%")) { - i = idx // - 1 - break - } - } - - data = bytes.Join(splitData[0:i], []byte("\n")) - p.r.TitleBlock(out, data) - - return len(data) -} - -func (p *parser) html(out *bytes.Buffer, data []byte, doRender bool) int { - var i, j int - - // identify the opening tag - if data[0] != '<' { - return 0 - } - curtag, tagfound := p.htmlFindTag(data[1:]) - - // handle special cases - if !tagfound { - // check for an HTML comment - if size := p.htmlComment(out, data, doRender); size > 0 { - return size - } - - // check for an
tag - if size := p.htmlHr(out, data, doRender); size > 0 { - return size - } - - // no special case recognized - return 0 - } - - // look for an unindented matching closing tag - // followed by a blank line - found := false - /* - closetag := []byte("\n") - j = len(curtag) + 1 - for !found { - // scan for a closing tag at the beginning of a line - if skip := bytes.Index(data[j:], closetag); skip >= 0 { - j += skip + len(closetag) - } else { - break - } - - // see if it is the only thing on the line - if skip := p.isEmpty(data[j:]); skip > 0 { - // see if it is followed by a blank line/eof - j += skip - if j >= len(data) { - found = true - i = j - } else { - if skip := p.isEmpty(data[j:]); skip > 0 { - j += skip - found = true - i = j - } - } - } - } - */ - - // if not found, try a second pass looking for indented match - // but not if tag is "ins" or "del" (following original Markdown.pl) - if !found && curtag != "ins" && curtag != "del" { - i = 1 - for i < len(data) { - i++ - for i < len(data) && !(data[i-1] == '<' && data[i] == '/') { - i++ - } - - if i+2+len(curtag) >= len(data) { - break - } - - j = p.htmlFindEnd(curtag, data[i-1:]) - - if j > 0 { - i += j - 1 - found = true - break - } - } - } - - if !found { - return 0 - } - - // the end of the block has been found - if doRender { - // trim newlines - end := i - for end > 0 && data[end-1] == '\n' { - end-- - } - p.r.BlockHtml(out, data[:end]) - } - - return i -} - -// HTML comment, lax form -func (p *parser) htmlComment(out *bytes.Buffer, data []byte, doRender bool) int { - i := p.inlineHtmlComment(out, data) - // needs to end with a blank line - if j := p.isEmpty(data[i:]); j > 0 { - size := i + j - if doRender { - // trim trailing newlines - end := size - for end > 0 && data[end-1] == '\n' { - end-- - } - p.r.BlockHtml(out, data[:end]) - } - return size - } - return 0 -} - -// HR, which is the only self-closing block tag considered -func (p *parser) htmlHr(out *bytes.Buffer, data []byte, doRender bool) int { - if data[0] != '<' || (data[1] != 'h' && data[1] != 'H') || (data[2] != 'r' && data[2] != 'R') { - return 0 - } - if data[3] != ' ' && data[3] != '/' && data[3] != '>' { - // not an
tag after all; at least not a valid one - return 0 - } - - i := 3 - for data[i] != '>' && data[i] != '\n' { - i++ - } - - if data[i] == '>' { - i++ - if j := p.isEmpty(data[i:]); j > 0 { - size := i + j - if doRender { - // trim newlines - end := size - for end > 0 && data[end-1] == '\n' { - end-- - } - p.r.BlockHtml(out, data[:end]) - } - return size - } - } - - return 0 -} - -func (p *parser) htmlFindTag(data []byte) (string, bool) { - i := 0 - for isalnum(data[i]) { - i++ - } - key := string(data[:i]) - if _, ok := blockTags[key]; ok { - return key, true - } - return "", false -} - -func (p *parser) htmlFindEnd(tag string, data []byte) int { - // assume data[0] == '<' && data[1] == '/' already tested - - // check if tag is a match - closetag := []byte("") - if !bytes.HasPrefix(data, closetag) { - return 0 - } - i := len(closetag) - - // check that the rest of the line is blank - skip := 0 - if skip = p.isEmpty(data[i:]); skip == 0 { - return 0 - } - i += skip - skip = 0 - - if i >= len(data) { - return i - } - - if p.flags&EXTENSION_LAX_HTML_BLOCKS != 0 { - return i - } - if skip = p.isEmpty(data[i:]); skip == 0 { - // following line must be blank - return 0 - } - - return i + skip -} - -func (p *parser) isEmpty(data []byte) int { - // it is okay to call isEmpty on an empty buffer - if len(data) == 0 { - return 0 - } - - var i int - for i = 0; i < len(data) && data[i] != '\n'; i++ { - if data[i] != ' ' && data[i] != '\t' { - return 0 - } - } - return i + 1 -} - -func (p *parser) isHRule(data []byte) bool { - i := 0 - - // skip up to three spaces - for i < 3 && data[i] == ' ' { - i++ - } - - // look at the hrule char - if data[i] != '*' && data[i] != '-' && data[i] != '_' { - return false - } - c := data[i] - - // the whole line must be the char or whitespace - n := 0 - for data[i] != '\n' { - switch { - case data[i] == c: - n++ - case data[i] != ' ': - return false - } - i++ - } - - return n >= 3 -} - -func (p *parser) isFencedCode(data []byte, syntax **string, oldmarker string) (skip int, marker string) { - i, size := 0, 0 - skip = 0 - - // skip up to three spaces - for i < len(data) && i < 3 && data[i] == ' ' { - i++ - } - if i >= len(data) { - return - } - - // check for the marker characters: ~ or ` - if data[i] != '~' && data[i] != '`' { - return - } - - c := data[i] - - // the whole line must be the same char or whitespace - for i < len(data) && data[i] == c { - size++ - i++ - } - - if i >= len(data) { - return - } - - // the marker char must occur at least 3 times - if size < 3 { - return - } - marker = string(data[i-size : i]) - - // if this is the end marker, it must match the beginning marker - if oldmarker != "" && marker != oldmarker { - return - } - - if syntax != nil { - syn := 0 - i = skipChar(data, i, ' ') - - if i >= len(data) { - return - } - - syntaxStart := i - - if data[i] == '{' { - i++ - syntaxStart++ - - for i < len(data) && data[i] != '}' && data[i] != '\n' { - syn++ - i++ - } - - if i >= len(data) || data[i] != '}' { - return - } - - // strip all whitespace at the beginning and the end - // of the {} block - for syn > 0 && isspace(data[syntaxStart]) { - syntaxStart++ - syn-- - } - - for syn > 0 && isspace(data[syntaxStart+syn-1]) { - syn-- - } - - i++ - } else { - for i < len(data) && !isspace(data[i]) { - syn++ - i++ - } - } - - language := string(data[syntaxStart : syntaxStart+syn]) - *syntax = &language - } - - i = skipChar(data, i, ' ') - if i >= len(data) || data[i] != '\n' { - return - } - - skip = i + 1 - return -} - -func (p *parser) fencedCode(out *bytes.Buffer, data []byte, doRender bool) int { - var lang *string - beg, marker := p.isFencedCode(data, &lang, "") - if beg == 0 || beg >= len(data) { - return 0 - } - - var work bytes.Buffer - - for { - // safe to assume beg < len(data) - - // check for the end of the code block - fenceEnd, _ := p.isFencedCode(data[beg:], nil, marker) - if fenceEnd != 0 { - beg += fenceEnd - break - } - - // copy the current line - end := skipUntilChar(data, beg, '\n') + 1 - - // did we reach the end of the buffer without a closing marker? - if end >= len(data) { - return 0 - } - - // verbatim copy to the working buffer - if doRender { - work.Write(data[beg:end]) - } - beg = end - } - - syntax := "" - if lang != nil { - syntax = *lang - } - - if doRender { - p.r.BlockCode(out, work.Bytes(), syntax) - } - - return beg -} - -func (p *parser) table(out *bytes.Buffer, data []byte) int { - var header bytes.Buffer - i, columns := p.tableHeader(&header, data) - if i == 0 { - return 0 - } - - var body bytes.Buffer - - for i < len(data) { - pipes, rowStart := 0, i - for ; data[i] != '\n'; i++ { - if data[i] == '|' { - pipes++ - } - } - - if pipes == 0 { - i = rowStart - break - } - - // include the newline in data sent to tableRow - i++ - p.tableRow(&body, data[rowStart:i], columns, false) - } - - p.r.Table(out, header.Bytes(), body.Bytes(), columns) - - return i -} - -// check if the specified position is preceded by an odd number of backslashes -func isBackslashEscaped(data []byte, i int) bool { - backslashes := 0 - for i-backslashes-1 >= 0 && data[i-backslashes-1] == '\\' { - backslashes++ - } - return backslashes&1 == 1 -} - -func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns []int) { - i := 0 - colCount := 1 - for i = 0; data[i] != '\n'; i++ { - if data[i] == '|' && !isBackslashEscaped(data, i) { - colCount++ - } - } - - // doesn't look like a table header - if colCount == 1 { - return - } - - // include the newline in the data sent to tableRow - header := data[:i+1] - - // column count ignores pipes at beginning or end of line - if data[0] == '|' { - colCount-- - } - if i > 2 && data[i-1] == '|' && !isBackslashEscaped(data, i-1) { - colCount-- - } - - columns = make([]int, colCount) - - // move on to the header underline - i++ - if i >= len(data) { - return - } - - if data[i] == '|' && !isBackslashEscaped(data, i) { - i++ - } - i = skipChar(data, i, ' ') - - // each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3 - // and trailing | optional on last column - col := 0 - for data[i] != '\n' { - dashes := 0 - - if data[i] == ':' { - i++ - columns[col] |= TABLE_ALIGNMENT_LEFT - dashes++ - } - for data[i] == '-' { - i++ - dashes++ - } - if data[i] == ':' { - i++ - columns[col] |= TABLE_ALIGNMENT_RIGHT - dashes++ - } - for data[i] == ' ' { - i++ - } - - // end of column test is messy - switch { - case dashes < 3: - // not a valid column - return - - case data[i] == '|' && !isBackslashEscaped(data, i): - // marker found, now skip past trailing whitespace - col++ - i++ - for data[i] == ' ' { - i++ - } - - // trailing junk found after last column - if col >= colCount && data[i] != '\n' { - return - } - - case (data[i] != '|' || isBackslashEscaped(data, i)) && col+1 < colCount: - // something else found where marker was required - return - - case data[i] == '\n': - // marker is optional for the last column - col++ - - default: - // trailing junk found after last column - return - } - } - if col != colCount { - return - } - - p.tableRow(out, header, columns, true) - size = i + 1 - return -} - -func (p *parser) tableRow(out *bytes.Buffer, data []byte, columns []int, header bool) { - i, col := 0, 0 - var rowWork bytes.Buffer - - if data[i] == '|' && !isBackslashEscaped(data, i) { - i++ - } - - for col = 0; col < len(columns) && i < len(data); col++ { - for data[i] == ' ' { - i++ - } - - cellStart := i - - for (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' { - i++ - } - - cellEnd := i - - // skip the end-of-cell marker, possibly taking us past end of buffer - i++ - - for cellEnd > cellStart && data[cellEnd-1] == ' ' { - cellEnd-- - } - - var cellWork bytes.Buffer - p.inline(&cellWork, data[cellStart:cellEnd]) - - if header { - p.r.TableHeaderCell(&rowWork, cellWork.Bytes(), columns[col]) - } else { - p.r.TableCell(&rowWork, cellWork.Bytes(), columns[col]) - } - } - - // pad it out with empty columns to get the right number - for ; col < len(columns); col++ { - if header { - p.r.TableHeaderCell(&rowWork, nil, columns[col]) - } else { - p.r.TableCell(&rowWork, nil, columns[col]) - } - } - - // silently ignore rows with too many cells - - p.r.TableRow(out, rowWork.Bytes()) -} - -// returns blockquote prefix length -func (p *parser) quotePrefix(data []byte) int { - i := 0 - for i < 3 && data[i] == ' ' { - i++ - } - if data[i] == '>' { - if data[i+1] == ' ' { - return i + 2 - } - return i + 1 - } - return 0 -} - -// blockquote ends with at least one blank line -// followed by something without a blockquote prefix -func (p *parser) terminateBlockquote(data []byte, beg, end int) bool { - if p.isEmpty(data[beg:]) <= 0 { - return false - } - if end >= len(data) { - return true - } - return p.quotePrefix(data[end:]) == 0 && p.isEmpty(data[end:]) == 0 -} - -// parse a blockquote fragment -func (p *parser) quote(out *bytes.Buffer, data []byte) int { - var raw bytes.Buffer - beg, end := 0, 0 - for beg < len(data) { - end = beg - // Step over whole lines, collecting them. While doing that, check for - // fenced code and if one's found, incorporate it altogether, - // irregardless of any contents inside it - for data[end] != '\n' { - if p.flags&EXTENSION_FENCED_CODE != 0 { - if i := p.fencedCode(out, data[end:], false); i > 0 { - // -1 to compensate for the extra end++ after the loop: - end += i - 1 - break - } - } - end++ - } - end++ - - if pre := p.quotePrefix(data[beg:]); pre > 0 { - // skip the prefix - beg += pre - } else if p.terminateBlockquote(data, beg, end) { - break - } - - // this line is part of the blockquote - raw.Write(data[beg:end]) - beg = end - } - - var cooked bytes.Buffer - p.block(&cooked, raw.Bytes()) - p.r.BlockQuote(out, cooked.Bytes()) - return end -} - -// returns prefix length for block code -func (p *parser) codePrefix(data []byte) int { - if data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' { - return 4 - } - return 0 -} - -func (p *parser) code(out *bytes.Buffer, data []byte) int { - var work bytes.Buffer - - i := 0 - for i < len(data) { - beg := i - for data[i] != '\n' { - i++ - } - i++ - - blankline := p.isEmpty(data[beg:i]) > 0 - if pre := p.codePrefix(data[beg:i]); pre > 0 { - beg += pre - } else if !blankline { - // non-empty, non-prefixed line breaks the pre - i = beg - break - } - - // verbatim copy to the working buffeu - if blankline { - work.WriteByte('\n') - } else { - work.Write(data[beg:i]) - } - } - - // trim all the \n off the end of work - workbytes := work.Bytes() - eol := len(workbytes) - for eol > 0 && workbytes[eol-1] == '\n' { - eol-- - } - if eol != len(workbytes) { - work.Truncate(eol) - } - - work.WriteByte('\n') - - p.r.BlockCode(out, work.Bytes(), "") - - return i -} - -// returns unordered list item prefix -func (p *parser) uliPrefix(data []byte) int { - i := 0 - - // start with up to 3 spaces - for i < 3 && data[i] == ' ' { - i++ - } - - // need a *, +, or - followed by a space - if (data[i] != '*' && data[i] != '+' && data[i] != '-') || - data[i+1] != ' ' { - return 0 - } - return i + 2 -} - -// returns ordered list item prefix -func (p *parser) oliPrefix(data []byte) int { - i := 0 - - // start with up to 3 spaces - for i < 3 && data[i] == ' ' { - i++ - } - - // count the digits - start := i - for data[i] >= '0' && data[i] <= '9' { - i++ - } - - // we need >= 1 digits followed by a dot and a space - if start == i || data[i] != '.' || data[i+1] != ' ' { - return 0 - } - return i + 2 -} - -// returns definition list item prefix -func (p *parser) dliPrefix(data []byte) int { - i := 0 - - // need a : followed by a spaces - if data[i] != ':' || data[i+1] != ' ' { - return 0 - } - for data[i] == ' ' { - i++ - } - return i + 2 -} - -// parse ordered or unordered list block -func (p *parser) list(out *bytes.Buffer, data []byte, flags int) int { - i := 0 - flags |= LIST_ITEM_BEGINNING_OF_LIST - work := func() bool { - for i < len(data) { - skip := p.listItem(out, data[i:], &flags) - i += skip - - if skip == 0 || flags&LIST_ITEM_END_OF_LIST != 0 { - break - } - flags &= ^LIST_ITEM_BEGINNING_OF_LIST - } - return true - } - - p.r.List(out, work, flags) - return i -} - -// Parse a single list item. -// Assumes initial prefix is already removed if this is a sublist. -func (p *parser) listItem(out *bytes.Buffer, data []byte, flags *int) int { - // keep track of the indentation of the first line - itemIndent := 0 - for itemIndent < 3 && data[itemIndent] == ' ' { - itemIndent++ - } - - i := p.uliPrefix(data) - if i == 0 { - i = p.oliPrefix(data) - } - if i == 0 { - i = p.dliPrefix(data) - // reset definition term flag - if i > 0 { - *flags &= ^LIST_TYPE_TERM - } - } - if i == 0 { - // if in defnition list, set term flag and continue - if *flags&LIST_TYPE_DEFINITION != 0 { - *flags |= LIST_TYPE_TERM - } else { - return 0 - } - } - - // skip leading whitespace on first line - for data[i] == ' ' { - i++ - } - - // find the end of the line - line := i - for i > 0 && data[i-1] != '\n' { - i++ - } - - // get working buffer - var raw bytes.Buffer - - // put the first line into the working buffer - raw.Write(data[line:i]) - line = i - - // process the following lines - containsBlankLine := false - sublist := 0 - -gatherlines: - for line < len(data) { - i++ - - // find the end of this line - for data[i-1] != '\n' { - i++ - } - - // if it is an empty line, guess that it is part of this item - // and move on to the next line - if p.isEmpty(data[line:i]) > 0 { - containsBlankLine = true - line = i - continue - } - - // calculate the indentation - indent := 0 - for indent < 4 && line+indent < i && data[line+indent] == ' ' { - indent++ - } - - chunk := data[line+indent : i] - - // evaluate how this line fits in - switch { - // is this a nested list item? - case (p.uliPrefix(chunk) > 0 && !p.isHRule(chunk)) || - p.oliPrefix(chunk) > 0 || - p.dliPrefix(chunk) > 0: - - if containsBlankLine { - *flags |= LIST_ITEM_CONTAINS_BLOCK - } - - // to be a nested list, it must be indented more - // if not, it is the next item in the same list - if indent <= itemIndent { - break gatherlines - } - - // is this the first item in the nested list? - if sublist == 0 { - sublist = raw.Len() - } - - // is this a nested prefix header? - case p.isPrefixHeader(chunk): - // if the header is not indented, it is not nested in the list - // and thus ends the list - if containsBlankLine && indent < 4 { - *flags |= LIST_ITEM_END_OF_LIST - break gatherlines - } - *flags |= LIST_ITEM_CONTAINS_BLOCK - - // anything following an empty line is only part - // of this item if it is indented 4 spaces - // (regardless of the indentation of the beginning of the item) - case containsBlankLine && indent < 4: - if *flags&LIST_TYPE_DEFINITION != 0 && i < len(data)-1 { - // is the next item still a part of this list? - next := i - for data[next] != '\n' { - next++ - } - for next < len(data)-1 && data[next] == '\n' { - next++ - } - if i < len(data)-1 && data[i] != ':' && data[next] != ':' { - *flags |= LIST_ITEM_END_OF_LIST - } - } else { - *flags |= LIST_ITEM_END_OF_LIST - } - break gatherlines - - // a blank line means this should be parsed as a block - case containsBlankLine: - raw.WriteByte('\n') - *flags |= LIST_ITEM_CONTAINS_BLOCK - } - - // if this line was preceeded by one or more blanks, - // re-introduce the blank into the buffer - if containsBlankLine { - containsBlankLine = false - raw.WriteByte('\n') - - } - - // add the line into the working buffer without prefix - raw.Write(data[line+indent : i]) - - line = i - } - - rawBytes := raw.Bytes() - - // render the contents of the list item - var cooked bytes.Buffer - if *flags&LIST_ITEM_CONTAINS_BLOCK != 0 && *flags&LIST_TYPE_TERM == 0 { - // intermediate render of block item, except for definition term - if sublist > 0 { - p.block(&cooked, rawBytes[:sublist]) - p.block(&cooked, rawBytes[sublist:]) - } else { - p.block(&cooked, rawBytes) - } - } else { - // intermediate render of inline item - if sublist > 0 { - p.inline(&cooked, rawBytes[:sublist]) - p.block(&cooked, rawBytes[sublist:]) - } else { - p.inline(&cooked, rawBytes) - } - } - - // render the actual list item - cookedBytes := cooked.Bytes() - parsedEnd := len(cookedBytes) - - // strip trailing newlines - for parsedEnd > 0 && cookedBytes[parsedEnd-1] == '\n' { - parsedEnd-- - } - p.r.ListItem(out, cookedBytes[:parsedEnd], *flags) - - return line -} - -// render a single paragraph that has already been parsed out -func (p *parser) renderParagraph(out *bytes.Buffer, data []byte) { - if len(data) == 0 { - return - } - - // trim leading spaces - beg := 0 - for data[beg] == ' ' { - beg++ - } - - // trim trailing newline - end := len(data) - 1 - - // trim trailing spaces - for end > beg && data[end-1] == ' ' { - end-- - } - - work := func() bool { - p.inline(out, data[beg:end]) - return true - } - p.r.Paragraph(out, work) -} - -func (p *parser) paragraph(out *bytes.Buffer, data []byte) int { - // prev: index of 1st char of previous line - // line: index of 1st char of current line - // i: index of cursor/end of current line - var prev, line, i int - - // keep going until we find something to mark the end of the paragraph - for i < len(data) { - // mark the beginning of the current line - prev = line - current := data[i:] - line = i - - // did we find a blank line marking the end of the paragraph? - if n := p.isEmpty(current); n > 0 { - // did this blank line followed by a definition list item? - if p.flags&EXTENSION_DEFINITION_LISTS != 0 { - if i < len(data)-1 && data[i+1] == ':' { - return p.list(out, data[prev:], LIST_TYPE_DEFINITION) - } - } - - p.renderParagraph(out, data[:i]) - return i + n - } - - // an underline under some text marks a header, so our paragraph ended on prev line - if i > 0 { - if level := p.isUnderlinedHeader(current); level > 0 { - // render the paragraph - p.renderParagraph(out, data[:prev]) - - // ignore leading and trailing whitespace - eol := i - 1 - for prev < eol && data[prev] == ' ' { - prev++ - } - for eol > prev && data[eol-1] == ' ' { - eol-- - } - - // render the header - // this ugly double closure avoids forcing variables onto the heap - work := func(o *bytes.Buffer, pp *parser, d []byte) func() bool { - return func() bool { - pp.inline(o, d) - return true - } - }(out, p, data[prev:eol]) - - id := "" - if p.flags&EXTENSION_AUTO_HEADER_IDS != 0 { - id = sanitized_anchor_name.Create(string(data[prev:eol])) - } - - p.r.Header(out, work, level, id) - - // find the end of the underline - for data[i] != '\n' { - i++ - } - return i - } - } - - // if the next line starts a block of HTML, then the paragraph ends here - if p.flags&EXTENSION_LAX_HTML_BLOCKS != 0 { - if data[i] == '<' && p.html(out, current, false) > 0 { - // rewind to before the HTML block - p.renderParagraph(out, data[:i]) - return i - } - } - - // if there's a prefixed header or a horizontal rule after this, paragraph is over - if p.isPrefixHeader(current) || p.isHRule(current) { - p.renderParagraph(out, data[:i]) - return i - } - - // if there's a fenced code block, paragraph is over - if p.flags&EXTENSION_FENCED_CODE != 0 { - if p.fencedCode(out, current, false) > 0 { - p.renderParagraph(out, data[:i]) - return i - } - } - - // if there's a definition list item, prev line is a definition term - if p.flags&EXTENSION_DEFINITION_LISTS != 0 { - if p.dliPrefix(current) != 0 { - return p.list(out, data[prev:], LIST_TYPE_DEFINITION) - } - } - - // if there's a list after this, paragraph is over - if p.flags&EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK != 0 { - if p.uliPrefix(current) != 0 || - p.oliPrefix(current) != 0 || - p.quotePrefix(current) != 0 || - p.codePrefix(current) != 0 { - p.renderParagraph(out, data[:i]) - return i - } - } - - // otherwise, scan to the beginning of the next line - for data[i] != '\n' { - i++ - } - i++ - } - - p.renderParagraph(out, data[:i]) - return i -} diff --git a/vendor/github.com/russross/blackfriday/html.go b/vendor/github.com/russross/blackfriday/html.go deleted file mode 100644 index 74e67ee..0000000 --- a/vendor/github.com/russross/blackfriday/html.go +++ /dev/null @@ -1,949 +0,0 @@ -// -// Blackfriday Markdown Processor -// Available at http://github.com/russross/blackfriday -// -// Copyright © 2011 Russ Ross . -// Distributed under the Simplified BSD License. -// See README.md for details. -// - -// -// -// HTML rendering backend -// -// - -package blackfriday - -import ( - "bytes" - "fmt" - "regexp" - "strconv" - "strings" -) - -// Html renderer configuration options. -const ( - HTML_SKIP_HTML = 1 << iota // skip preformatted HTML blocks - HTML_SKIP_STYLE // skip embedded