From 764b51a91c7b3906946b72827b355b3cf4fc6794 Mon Sep 17 00:00:00 2001 From: Chris Aniszczyk Date: Wed, 23 Mar 2016 16:21:04 -0500 Subject: [PATCH 001/245] Create README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..0907ef0 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# image-spec + +https://github.com/opencontainers/tob/tree/master/proposals/image-format + +TODO From 33d56bc3d7165ee7c080689ceb7295f89f115202 Mon Sep 17 00:00:00 2001 From: Chris Aniszczyk Date: Mon, 4 Apr 2016 10:52:06 -0500 Subject: [PATCH 002/245] List maintainers --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0907ef0..a25cd21 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,12 @@ https://github.com/opencontainers/tob/tree/master/proposals/image-format -TODO +## Maintainers + +* Vincent Batts, Red Hat +* Brandon Philips, CoreOS +* Brendan Burns, Google +* Jason Bouzane, Google +* John Starks, Microsoft +* Jonathan Boulle, CoreOS +* Stephen Day, Docker From 99af847cba03e588e697c754c8bbf37b4f9d5d9c Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Mon, 4 Apr 2016 08:53:51 -0700 Subject: [PATCH 003/245] MAINTAINERS: initial commit Based on election results: https://groups.google.com/a/opencontainers.org/forum/#!topic/tob/B_u26EhnDm8 Which was decided via a vote started in late March: https://groups.google.com/a/opencontainers.org/forum/#!topic/tob/o5DLbYfZxQo Based on a nomination process started early March: https://groups.google.com/a/opencontainers.org/forum/#!topic/tob/OKQmg0k-9bU --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 MAINTAINERS diff --git a/MAINTAINERS b/MAINTAINERS new file mode 100644 index 0000000..1dedc43 --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1,7 @@ +Brandon Philips (@philips) +Brendan Burns (@brendandburns) +Jason Bouzane (@jbouzane) +John Starks (@jstarks) +Jonathan Boulle (@jonboulle) +Stephen Day (@stevvooe) +Vincent Batts (@vbatts) From 1fda7c4a2d7220fcc4051ccfd2c7b05528075335 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 4 Apr 2016 14:17:55 -0400 Subject: [PATCH 004/245] *: add common files from runtime-spec Signed-off-by: Vincent Batts --- LICENSE | 191 +++++++++++++++++++++++++++++++++++++++++++++ code-of-conduct.md | 37 +++++++++ project.md | 7 ++ 3 files changed, 235 insertions(+) create mode 100644 LICENSE create mode 100644 code-of-conduct.md create mode 100644 project.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9fdc20f --- /dev/null +++ b/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2016 The Linux Foundation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/code-of-conduct.md b/code-of-conduct.md new file mode 100644 index 0000000..06cb2b8 --- /dev/null +++ b/code-of-conduct.md @@ -0,0 +1,37 @@ +# OpenContainers Code of Conduct + +Behave as a community member, follow the code of conduct. + +## Code of Conduct + +The OpenContainers community is made up of a mixture of professionals and volunteers from all over the world. + +When we disagree, we try to understand why. +Disagreements, both social and technical, happen all the time and OpenContainers is no exception. +It is important that we resolve disagreements and differing views constructively. + +This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. +Participants should be aware of these concerns. + +We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic addresses, without explicit permission +* Other unethical or unprofessional conduct + +The OpenContainers team does not condone any statements by speakers contrary to these standards. +The OpenContainers team reserves the right to deny participation any individual found to be engaging in discriminatory or harassing actions. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. +By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. + +## Thanks + +Thanks to the [Fedora Code of Conduct](https://getfedora.org/code-of-conduct) and [Contributor Covenant](http://contributor-covenant.org) for inspiration and ideas. + +Portions of this Code of Conduct are adapted from the Contributor Covenant, version 1.2.0, available at http://contributor-covenant.org/version/1/2/0/ diff --git a/project.md b/project.md new file mode 100644 index 0000000..440484b --- /dev/null +++ b/project.md @@ -0,0 +1,7 @@ +# Project docs + +## Release Process + +* `git tag` the prior commit (preferably signed tag) +* Make a release on [github.com/opencontainers/image-spec](https://github.com/opencontainers/image-spec/releases) for the version. Attach the produced docs. + From ab2b489e4a37442080987151641c36713d357510 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 4 Apr 2016 14:23:35 -0400 Subject: [PATCH 005/245] README: carry-over content Signed-off-by: Vincent Batts --- README.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a25cd21..96e5ba1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# image-spec +# Open Container - Container Distributable Image Specification + +[Open Container Initiative](http://www.opencontainers.org/) Specifications for standards on Operating System process and application containers. https://github.com/opencontainers/tob/tree/master/proposals/image-format @@ -11,3 +13,110 @@ https://github.com/opencontainers/tob/tree/master/proposals/image-format * John Starks, Microsoft * Jonathan Boulle, CoreOS * Stephen Day, Docker + +# Contributing + +Development happens on GitHub for the spec. +Issues are used for bugs and actionable items and longer discussions can happen on the [mailing list](#mailing-list). + +The specification and code is licensed under the Apache 2.0 license found in the `LICENSE` file of this repository. + +## Code of Conduct + +Participation in the OpenContainers community is governed by [OpenContainer's Code of Conduct](code-of-conduct.md). + +## Discuss your design + +The project welcomes submissions, but please let everyone know what you are working on. + +Before undertaking a nontrivial change to this specification, send mail to the [mailing list](#mailing-list) to discuss what you plan to do. +This gives everyone a chance to validate the design, helps prevent duplication of effort, and ensures that the idea fits. +It also guarantees that the design is sound before code is written; a GitHub pull-request is not the place for high-level discussions. + +Typos and grammatical errors can go straight to a pull-request. +When in doubt, start on the [mailing-list](#mailing-list). + +## Mailing List + +You can subscribe and join the mailing list on [Google Groups](https://groups.google.com/a/opencontainers.org/forum/#!forum/dev). + +## IRC + +OCI discussion happens on #opencontainers on Freenode ([logs][irc-logs]). + +## Markdown style + +To keep consistency throughout the Markdown files in the Open Container spec all files should be formatted one sentence per line. +This fixes two things: it makes diffing easier with git and it resolves fights about line wrapping length. +For example, this paragraph will span three lines in the Markdown source. + +## Git commit + +### Sign your work + +The sign-off is a simple line at the end of the explanation for the patch, which certifies that you wrote it or otherwise have the right to pass it on as an open-source patch. +The rules are pretty simple: if you can certify the below (from [developercertificate.org](http://developercertificate.org/)): + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +then you just add a line to every git commit message: + + Signed-off-by: Joe Smith + +using your real name (sorry, no pseudonyms or anonymous contributions.) + +You can add the sign off when creating the git commit via `git commit -s`. + +### Commit Style + +Simple house-keeping for clean git history. +Read more on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/) or the Discussion section of [`git-commit(1)`](http://git-scm.com/docs/git-commit). + +1. Separate the subject from body with a blank line +2. Limit the subject line to 50 characters +3. Capitalize the subject line +4. Do not end the subject line with a period +5. Use the imperative mood in the subject line +6. Wrap the body at 72 characters +7. Use the body to explain what and why vs. how + * If there was important/useful/essential conversation or information, copy or include a reference +8. When possible, one keyword to scope the change in the subject (i.e. "README: ...", "runtime: ...") + +[irc-logs]: http://ircbot.wl.linuxfoundation.org/eavesdrop/%23opencontainers/ From ac37c93324e7c1d7e04eeacc3bb7ade6ec53acc2 Mon Sep 17 00:00:00 2001 From: Jonathan Boulle Date: Mon, 4 Apr 2016 21:31:42 +0200 Subject: [PATCH 006/245] README: import proposal information --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 96e5ba1..f0b2f72 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,58 @@ -# Open Container - Container Distributable Image Specification +# Open Container - Distributable Image Specification -[Open Container Initiative](http://www.opencontainers.org/) Specifications for standards on Operating System process and application containers. +The [Open Container Initiative](http://www.opencontainers.org/) develops specifications for standards on Operating System process and application containers. -https://github.com/opencontainers/tob/tree/master/proposals/image-format +This OCI project is tasked with creating a software shipping container image format spec (OCI Image Format) with security and naming as components. + +## Initial Proposal + +This new OCI project intends to start with the Docker v2.2 specification, improve any remaining technical concerns, and standardize and improve the understood properties of a container image format. This new project will have the objectives of: + +* A serialized image format (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) + +## Cooperation with OCI Runtime Project + +The [OCI Runtime Spec project](https://github.com/opencontainers/runtime-spec) is developing a specification for the lifecycle of a running container. The OCI Image Format Spec project should work with the OCI Runtime Spec project so that the image can support 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 + +This implies that the OCI Image Format must contain 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 should strive 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. + +## Proposed Roadmap + +* April ??? v0.0.0 + * Import Docker v2.2 format +* April ??? v0.1.0 + * Spec factored for top to bottom reading with three audiences in-mind: + * Build system creators + * Image registry creators + * Container engine creators +* May ??? v0.2.0 + * Release version of spec with improvements from two independent experimental implementations from OCI members e.g. Amazon Container Registry and rkt +* June ??? v1.0.0 + * Release initial version of spec with two independent non-experimental implementations from OCI members ## Maintainers From ab63a51f2ffcf8e5f7dca0cff6865a1c327a7757 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 5 Apr 2016 09:54:00 -0400 Subject: [PATCH 007/245] travis: add initial checks Starting with git commit validation. Signed-off-by: Vincent Batts --- .travis.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..bbee276 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +language: go +go: + - 1.6 + +sudo: false + +before_install: + - go get github.com/vbatts/git-validation + +install: true + +script: + - $HOME/gopath/bin/git-validation -run DCO,short-subject -v -range ${TRAVIS_COMMIT_RANGE} + From 735b4e567402b2c4225e979894da1fa59285812a Mon Sep 17 00:00:00 2001 From: Jonathan Boulle Date: Wed, 6 Apr 2016 14:48:02 +0200 Subject: [PATCH 008/245] README: remove Maintainers Redundant with MAINTAINERS file. Signed-off-by: Jonathan Boulle --- README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.md b/README.md index f0b2f72..7492ff5 100644 --- a/README.md +++ b/README.md @@ -54,16 +54,6 @@ A: Existing formats can continue to be a proving ground for technologies, as nee * June ??? v1.0.0 * Release initial version of spec with two independent non-experimental implementations from OCI members -## Maintainers - -* Vincent Batts, Red Hat -* Brandon Philips, CoreOS -* Brendan Burns, Google -* Jason Bouzane, Google -* John Starks, Microsoft -* Jonathan Boulle, CoreOS -* Stephen Day, Docker - # Contributing Development happens on GitHub for the spec. From a9000a5a080810f2ac533b9f324bb71fa272aff9 Mon Sep 17 00:00:00 2001 From: Mrunal Patel Date: Wed, 6 Apr 2016 11:08:27 -0700 Subject: [PATCH 009/245] Make title similar to the runtime specification Signed-off-by: Mrunal Patel --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7492ff5..5afd3ae 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Open Container - Distributable Image Specification +# Open Container Distributable Image Specification The [Open Container Initiative](http://www.opencontainers.org/) develops specifications for standards on Operating System process and application containers. From c22ca79954b45f90d1f10b2201a3c2e32cc6ce04 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 4 Apr 2016 13:55:05 -0400 Subject: [PATCH 010/245] serialization: docker v1 image format media type from https://github.com/docker/docker/blob/99a396902f0ea9d81ef87a683489b2435408f415/image/spec/v1.md Signed-off-by: Vincent Batts --- serialization.md | 573 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 573 insertions(+) create mode 100644 serialization.md diff --git a/serialization.md b/serialization.md new file mode 100644 index 0000000..57a599b --- /dev/null +++ b/serialization.md @@ -0,0 +1,573 @@ +# Docker Image Specification v1.0.0 + +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 format of these filesystem changes and corresponding +parameters and describes how to create and use them for use with a container +runtime and execution tool. + +## Terminology + +This specification uses the following terms: + +
+
+ Layer +
+
+ Images are composed of layers. Image layer is a general + term which may be used to refer to one or both of the following: + +
    +
  1. The metadata for the layer, described in the JSON format.
  2. +
  3. The filesystem changes described by a layer.
  4. +
+ + To refer to the former you may use the term Layer JSON or + Layer Metadata. To refer to the latter you may use the term + Image Filesystem Changeset or Image Diff. +
+
+ Image JSON +
+
+ Each layer 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 entry point, default arguments, CPU/memory shares, networking, and + volumes. +
+
+ Image Filesystem Changeset +
+
+ Each layer has an archive of the files which have been added, changed, + or deleted relative to its parent 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 ID +
+
+ Each layer is given an ID upon its creation. It is + represented as a hexadecimal encoding of 256 bits, e.g., + a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9. + Image IDs should be sufficiently random so as to be globally unique. + 32 bytes read from /dev/urandom is sufficient for all + practical purposes. Alternatively, an image ID may be derived as a + cryptographic hash of image contents as the result is considered + indistinguishable from random. The choice is left up to implementors. +
+
+ Image Parent +
+
+ Most layer metadata structs contain a parent field which + refers to the Image from which another directly descends. An image + contains a separate JSON metadata file and set of changes relative to + the filesystem of its parent image. Image Ancestor and + Image Descendant are also common terms. +
+
+ Image Checksum +
+
+ Layer metadata structs contain a cryptographic hash of the contents of + the layer's filesystem changeset. Though the set of changes exists as a + simple Tar archive, two archives with identical filenames and content + will have different SHA digests if the last-access or last-modified + times of any entries differ. For this reason, image checksums are + generated using the TarSum algorithm which produces a cryptographic + hash of file contents and selected headers only. Details of this + algorithm are described in the separate TarSum specification. +
+
+ Tag +
+
+ A tag serves to map a descriptive, user-given name to any single image + ID. An image name suffix (the name component after :) is + often referred to as a tag as well, though it strictly refers to the + full name of an image. Acceptable values for a tag suffix are + implementation specific, but they SHOULD be limited to the set of + alphanumeric characters [a-zA-z0-9], punctuation + characters [._-], and MUST NOT contain a : + character. +
+
+ Repository +
+
+ A collection of tags grouped under a common prefix (the name component + before :). For example, in an image tagged with the name + my-app:3.1.4, my-app is the Repository + component of the name. Acceptable values for repository name are + implementation specific, but they SHOULD be limited to the set of + alphanumeric characters [a-zA-z0-9], and punctuation + characters [._-], however it MAY contain additional + / and : characters for organizational + purposes, with the last : character being interpreted + dividing the repository component of the name from the tag suffix + component. +
+
+ +## Image JSON Description + +Here is an example image JSON file: + +``` +{ + "id": "a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9", + "parent": "c6e3cedcda2e3982a1a6760e178355e8e65f7b80e4e5248743fa3549d284e024", + "checksum": "tarsum.v1+sha256:e58fcf7418d2390dec8e8fb69d88c06ec07039d651fedc3aa72af9972e7d046b", + "created": "2014-10-13T21:19:18.674353812Z", + "author": "Alyssa P. Hacker <alyspdev@example.com>", + "architecture": "amd64", + "os": "linux", + "Size": 271828, + "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=docker_is_a_really", + "BAR=great_tool_you_know" + ], + "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", + } +} +``` + +### Image JSON Field Descriptions + +
+
+ id string +
+
+ Randomly generated, 256-bit, hexadecimal encoded. Uniquely identifies + the image. +
+
+ parent string +
+
+ ID of the parent image. If there is no parent image then this field + should be omitted. A collection of images may share many of the same + ancestor layers. This organizational structure is strictly a tree with + any one layer having either no parent or a single parent and zero or + more descendant layers. Cycles are not allowed and implementations + should be careful to avoid creating them or iterating through a cycle + indefinitely. +
+
+ 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. +
+
+ 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. +
+
+ checksum string +
+
+ Image Checksum of the filesystem changeset associated with the image + layer. +
+
+ Size integer +
+
+ The size in bytes of the filesystem changeset associated with the image + layer. +
+
+ 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. +
+
+ Cmd array of strings +
+
+ Default arguments to the entry point 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. +
+
+ Volumes struct +
+
+ A set of directories which should be created as data volumes in + 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: +
{
+    "/var/my-app-data/": {},
+    "/etc/some-config.d/": {},
+}
+
+
+ WorkingDir string +
+
+ Sets the current working directory of the entry point process + in the container. This value acts as a default and is replaced + by a working directory specified when creating a container. +
+
+
+
+ +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. + +## Creating an Image Filesystem Changeset + +An example of creating an Image Filesystem Changeset follows. + +An image root filesystem is first created as an empty directory named with the +ID of the image being created. Here is the initial empty directory structure +for the changeset for an image with ID `c3167915dc9d` ([real IDs are much +longer](#id_desc), but this example use a truncated one here for brevity. +Implementations need not name the rootfs directory in this way but it may be +convenient for keeping record of a large number of image layers.): + +``` +c3167915dc9d/ +``` + +Files and directories are then created: + +``` +c3167915dc9d/ + etc/ + my-app-config + bin/ + my-app-binary + my-app-tools +``` + +The `c3167915dc9d` directory is then committed as a plain Tar archive with +entries for the following files: + +``` +etc/my-app-config +bin/my-app-binary +bin/my-app-tools +``` + +The TarSum checksum for the archive file is then computed and placed in the +JSON metadata along with the execution parameters. + +To make changes to the filesystem of this container image, create a new +directory named with a new ID, such as `f60c56784b83`, and initialize it with +a snapshot of the parent image's root filesystem, so that the directory is +identical to that of `c3167915dc9d`. NOTE: a copy-on-write or union filesystem +can make this very efficient: + +``` +f60c56784b83/ + etc/ + my-app-config + bin/ + my-app-binary + my-app-tools +``` + +This example change is going add a configuration directory at `/etc/my-app.d` +which contains a default config file. There's also a change to the +`my-app-tools` binary to handle the config layout change. The `f60c56784b83` +directory then looks like this: + +``` +f60c56784b83/ + etc/ + my-app.d/ + default.cfg + bin/ + my-app-binary + my-app-tools +``` + +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. Before committing this directory to a +changeset, because it has a parent image, it is first compared with the +directory tree of the parent snapshot, `f60c56784b83`, looking for files and +directories that have been added, modified, or removed. The following changeset +is found: + +``` +Added: /etc/my-app.d/default.cfg +Modified: /bin/my-app-tools +Deleted: /etc/my-app-config +``` + +A Tar Archive is then created which contains *only* this changeset: The added +and modified files and directories in their entirety, and for each deleted item +an entry for an empty file at the same location but with the basename of the +deleted file or directory prefixed with `.wh.`. The filenames prefixed with +`.wh.` are known as "whiteout" files. NOTE: For this reason, it is not possible +to create an image root filesystem which contains a file or directory with a +name beginning with `.wh.`. The resulting Tar archive for `f60c56784b83` has +the following entries: + +``` +/etc/my-app.d/default.cfg +/bin/my-app-tools +/etc/.wh.my-app-config +``` + +Any given image is likely to be composed of several of these Image Filesystem +Changeset tar archives. + +## Combined Image JSON + Filesystem Changeset Format + +There is also a format for a single archive which contains complete information +about an image, including: + + - repository names/tags + - all image layer JSON files + - all tar archives of each layer filesystem changesets + +For example, here's what the full archive of `library/busybox` is (displayed in +`tree` format): + +``` +. +├── 5785b62b697b99a5af6cd5d0aabc804d5748abbb6d3d07da5d1d3795f2dcc83e +│   ├── VERSION +│   ├── json +│   └── layer.tar +├── a7b8b41220991bfc754d7ad445ad27b7f272ab8b4a2c175b9512b97471d02a8a +│   ├── VERSION +│   ├── json +│   └── layer.tar +├── a936027c5ca8bf8f517923169a233e391cbb38469a75de8383b5228dc2d26ceb +│   ├── VERSION +│   ├── json +│   └── layer.tar +├── f60c56784b832dd990022afc120b8136ab3da9528094752ae13fe63a2d28dc8c +│   ├── VERSION +│   ├── json +│   └── layer.tar +└── repositories +``` + +There are one or more directories named with the ID for each layer in a full +image. Each of these directories contains 3 files: + + * `VERSION` - The schema version of the `json` file + * `json` - The JSON metadata for an image layer + * `layer.tar` - The Tar archive of the filesystem changeset for an image + layer. + +The content of the `VERSION` files is simply the semantic version of the JSON +metadata schema: + +``` +1.0 +``` + +And the `repositories` file is another JSON file which describes names/tags: + +``` +{ + "busybox":{ + "latest":"5785b62b697b99a5af6cd5d0aabc804d5748abbb6d3d07da5d1d3795f2dcc83e" + } +} +``` + +Every key in this object is the name of a repository, and maps to a collection +of tag suffixes. Each tag maps to the ID of the image represented by that tag. + +## Loading an Image Filesystem Changeset + +Unpacking a bundle of image layer JSON files and their corresponding filesystem +changesets can be done using a series of steps: + +1. Follow the parent IDs of image layers to find the root ancestor (an image +with no parent ID specified). +2. For every image layer, in order from root ancestor and descending down, +extract the contents of that layer's filesystem changeset archive into a +directory which will be used as the root of a container filesystem. + + - Extract all contents of each archive. + - Walk the directory tree once more, removing any files with the prefix + `.wh.` and the corresponding file or directory named without this prefix. + + +## Implementations + +This specification is an admittedly imperfect description of an +imperfectly-understood problem. The Docker project is, in turn, an attempt to +implement this specification. Our goal and our execution toward it will evolve +over time, but our primary concern in this specification and in our +implementation is compatibility and interoperability. From c1ff36f071482d8d2891aaa0a5cc5dd21a2bed24 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 4 Apr 2016 13:57:27 -0400 Subject: [PATCH 011/245] manifest: docker v2.s2 as the manifest from https://github.com/docker/distribution/blob/bf9f80eaffb0eabc768762bc4ff03ded277ea594/docs/spec/manifest-v2-2.md Signed-off-by: Vincent Batts --- manifest.md | 284 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 manifest.md diff --git a/manifest.md b/manifest.md new file mode 100644 index 0000000..904366e --- /dev/null +++ b/manifest.md @@ -0,0 +1,284 @@ + + +# Image Manifest Version 2, Schema 2 + +This document outlines the format of of the V2 image manifest, schema version 2. +The original (and provisional) image manifest for V2 (schema 1), was introduced +in the Docker daemon in the [v1.3.0 +release](https://github.com/docker/docker/commit/9f482a66ab37ec396ac61ed0c00d59122ac07453) +and is specified in the [schema 1 manifest definition](./manifest-v2-1.md) + +This second schema version has two primary goals. The first is to allow +multi-architecture images, through a "fat manifest" which references image +manifests for platform-specific versions of an image. The second is to +move the Docker engine towards content-addressable images, by supporting +an image model where the image's configuration can be hashed to generate +an ID for the image. + +# Media Types + +The following media types are used by the manifest formats described here, and +the resources they reference: + +- `application/vnd.docker.distribution.manifest.v1+json`: schema1 (existing manifest format) +- `application/vnd.docker.distribution.manifest.v2+json`: New image manifest format (schemaVersion = 2) +- `application/vnd.docker.distribution.manifest.list.v2+json`: Manifest list, aka "fat manifest" +- `application/vnd.docker.image.rootfs.diff.tar.gzip`: "Layer", as a gzipped tar +- `application/vnd.docker.container.image.v1+json`: Container config JSON + +## Manifest List + +The manifest list is the "fat manifest" which points to specific image manifests +for one or more platforms. Its use is optional, and relatively few images will +use one of these manifests. A client will distinguish a manifest list from an +image manifest based on the Content-Type returned in the HTTP response. + +## *Manifest List* Field Descriptions + +- **`schemaVersion`** *int* + + This field specifies the image manifest schema version as an integer. This + schema uses the version `2`. + +- **`mediaType`** *string* + + The MIME type of the manifest list. This should be set to + `application/vnd.docker.distribution.manifest.list.v2+json`. + +- **`manifests`** *array* + + The manifests field contains a list of manifests for specific platforms. + + Fields of a object in the manifests list are: + + - **`mediaType`** *string* + + The MIME type of the referenced object. This will generally be + `application/vnd.docker.image.manifest.v2+json`, but it could also + be `application/vnd.docker.image.manifest.v1+json` if the manifest + list references a legacy schema-1 manifest. + + - **`size`** *int* + + The size in bytes of the object. This field exists so that a client + will have an expected size for the content before validating. If the + length of the retrieved content does not match the specified length, + the content should not be trusted. + + - **`digest`** *string* + + The digest of the content, as defined by the + [Registry V2 HTTP API Specificiation](https://docs.docker.com/registry/spec/api/#digest-parameter). + + - **`platform`** *object* + + The platform object 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* + + The architecture field specifies the CPU architecture, for example + `amd64` or `ppc64le`. + + - **`os`** *string* + + The os field specifies the operating system, for example + `linux` or `windows`. + + - **`os.version`** *string* + + The optional os.version field specifies the operating system version, + for example `10.0.10586`. + + - **`os.features`** *array* + + The optional os.features field specifies an array of strings, + each listing a required OS feature (for example on Windows + `win32k`). + + - **`variant`** *string* + + The optional variant field specifies a variant of the CPU, for + example `armv6l` to specify a particular CPU variant of the ARM CPU. + + - **`features`** *array* + + The optional features field specifies an array of strings, each + listing a required CPU feature (for example `sse4` or `aes`). + +## Example Manifest List + +*Example showing a simple manifest list pointing to image manifests for two platforms:* +```json +{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", + "manifests": [ + { + "mediaType": "application/vnd.docker.image.manifest.v2+json", + "size": 7143, + "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", + "platform": { + "architecture": "ppc64le", + "os": "linux", + } + }, + { + "mediaType": "application/vnd.docker.image.manifest.v2+json", + "size": 7682, + "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", + "platform": { + "architecture": "amd64", + "os": "linux", + "features": [ + "sse4" + ] + } + } + ] +} +``` + +# Image Manifest + +The image manifest provides a configuration and a set of layers for a container +image. It's the direct replacement for the schema-1 manifest. + +## *Image Manifest* Field Descriptions + +- **`schemaVersion`** *int* + + This field specifies the image manifest schema version as an integer. This + schema uses version `2`. + +- **`mediaType`** *string* + + The MIME type of the manifest. This should be set to + `application/vnd.docker.distribution.manifest.v2+json`. + +- **`config`** *object* + + The config field references a configuration object for a container, by + digest. This configuration item is a JSON blob that the runtime uses + to set up the container. This new schema uses a tweaked version + of this configuration to allow image content-addressability on the + daemon side. + + Fields of a config object are: + + - **`mediaType`** *string* + + The MIME type of the referenced object. This should generally be + `application/vnd.docker.container.image.v1+json`. + + - **`size`** *int* + + The size in bytes of the object. This field exists so that a client + will have an expected size for the content before validating. If the + length of the retrieved content does not match the specified length, + the content should not be trusted. + + - **`digest`** *string* + + The digest of the content, as defined by the + [Registry V2 HTTP API Specificiation](https://docs.docker.com/registry/spec/api/#digest-parameter). + +- **`layers`** *array* + + The layer list is ordered starting from the base image (opposite order of schema1). + + Fields of an item in the layers list are: + + - **`mediaType`** *string* + + The MIME type of the referenced object. This should + generally be `application/vnd.docker.image.rootfs.diff.tar.gzip`. + + - **`size`** *int* + + The size in bytes of the object. This field exists so that a client + will have an expected size for the content before validating. If the + length of the retrieved content does not match the specified length, + the content should not be trusted. + + - **`digest`** *string* + + The digest of the content, as defined by the + [Registry V2 HTTP API Specificiation](https://docs.docker.com/registry/spec/api/#digest-parameter). + +## Example Image Manifest + +*Example showing an image manifest:* +```json +{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 7023, + "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 32654, + "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 16724, + "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 73109, + "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736" + } + ], +} +``` + +# Backward compatibility + +The registry will continue to accept uploads of manifests in both the old and +new formats. + +When pushing images, clients which support the new manifest format should first +construct a manifest in the new format. If uploading this manifest fails, +presumably because the registry only supports the old format, the client may +fall back to uploading a manifest in the old format. + +When pulling images, clients indicate support for this new version of the +manifest format by sending the +`application/vnd.docker.distribution.manifest.v2+json` and +`application/vnd.docker.distribution.manifest.list.v2+json` media types in an +`Accept` header when making a request to the `manifests` endpoint. Updated +clients should check the `Content-Type` header to see whether the manifest +returned from the endpoint is in the old format, or is an image manifest or +manifest list in the new format. + +If the manifest being requested uses the new format, and the appropriate media +type is not present in an `Accept` header, the registry will assume that the +client cannot handle the manifest as-is, and rewrite it on the fly into the old +format. If the object that would otherwise be returned is a manifest list, the +registry will look up the appropriate manifest for the amd64 platform and +linux OS, rewrite that manifest into the old format if necessary, and return +the result to the client. If no suitable manifest is found in the manifest +list, the registry will return a 404 error. + +One of the challenges in rewriting manifests to the old format is that the old +format involves an image configuration for each layer in the manifest, but the +new format only provides one image configuration. To work around this, the +registry will create synthetic image configurations for all layers except the +top layer. These image configurations will not result in runnable images on +their own, but only serve to fill in the parent chain in a compatible way. +The IDs in these synthetic configurations will be derived from hashes of their +respective blobs. The registry will create these configurations and their IDs +using the same scheme as Docker 1.10 when it creates a legacy manifest to push +to a registry which doesn't support the new format. From 9b4866fc884d497bc6a3f99ed9b2d5aad44c6ee6 Mon Sep 17 00:00:00 2001 From: "Rob Dolin (MSFT)" Date: Thu, 7 Apr 2016 17:40:20 -0700 Subject: [PATCH 012/245] ReadMe: Link to shared Code of Conduct in TOB repo Signed-off-by: Rob Dolin --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5afd3ae..2021e89 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ The specification and code is licensed under the Apache 2.0 license found in the ## Code of Conduct -Participation in the OpenContainers community is governed by [OpenContainer's Code of Conduct](code-of-conduct.md). +Participation in the OpenContainers community is governed by [OpenContainer's Code of Conduct](https://github.com/opencontainers/tob/blob/master/code-of-conduct.md). ## Discuss your design From 4516e154b87d3ce197c454d34018438f62d61837 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 12 Apr 2016 13:38:19 -0400 Subject: [PATCH 013/245] manifest: fix JSON commas Signed-off-by: Vincent Batts --- manifest.md | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/manifest.md b/manifest.md index 904366e..9bcd495 100644 --- a/manifest.md +++ b/manifest.md @@ -126,7 +126,7 @@ image manifest based on the Content-Type returned in the HTTP response. "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", "platform": { "architecture": "ppc64le", - "os": "linux", + "os": "linux" } }, { @@ -217,30 +217,30 @@ image. It's the direct replacement for the schema-1 manifest. *Example showing an image manifest:* ```json { - "schemaVersion": 2, - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "config": { - "mediaType": "application/vnd.docker.container.image.v1+json", - "size": 7023, - "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7" + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 7023, + "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 32654, + "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 16724, + "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b" }, - "layers": [ - { - "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "size": 32654, - "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f" - }, - { - "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "size": 16724, - "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b" - }, - { - "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", - "size": 73109, - "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736" - } - ], + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 73109, + "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736" + } + ] } ``` From 363d55a128e2779e536f97418052b9ed9c31cc01 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 12 Apr 2016 11:14:14 -0400 Subject: [PATCH 014/245] schema: copy common from runtime From https://github.com/opencontainers/runtime-spec/commit/cdcabdeb6b1555f76b57dd5b8ce51da2121ea76f Signed-off-by: Vincent Batts --- schema/Makefile | 15 +++++ schema/defs.json | 160 +++++++++++++++++++++++++++++++++++++++++++++ schema/validate.go | 45 +++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 schema/Makefile create mode 100644 schema/defs.json create mode 100644 schema/validate.go diff --git a/schema/Makefile b/schema/Makefile new file mode 100644 index 0000000..ae94071 --- /dev/null +++ b/schema/Makefile @@ -0,0 +1,15 @@ + +default: help + +help: + @echo "Usage: make " + @echo + @echo " * 'fmt' - format the json with indentation" + @echo " * 'validate' - build the validation tool" + +fmt: + for i in *.json ; do jq --indent 2 -M . "$${i}" > xx && cat xx > "$${i}" && rm xx ; done + +validate: validate.go + go build ./validate.go + diff --git a/schema/defs.json b/schema/defs.json new file mode 100644 index 0000000..85f62da --- /dev/null +++ b/schema/defs.json @@ -0,0 +1,160 @@ +{ + "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" + } + } + }, + "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/validate.go b/schema/validate.go new file mode 100644 index 0000000..a4d60ec --- /dev/null +++ b/schema/validate.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/xeipuuv/gojsonschema" +) + +func main() { + if len(os.Args[1:]) != 2 { + fmt.Printf("ERROR: usage is: %s ", os.Args[0]) + os.Exit(1) + } + + schemaPath, err := filepath.Abs(os.Args[1]) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + documentPath, err := filepath.Abs(os.Args[2]) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + schemaLoader := gojsonschema.NewReferenceLoader("file://" + schemaPath) + documentLoader := gojsonschema.NewReferenceLoader("file://" + documentPath) + + result, err := gojsonschema.Validate(schemaLoader, documentLoader) + if err != nil { + panic(err.Error()) + } + + if result.Valid() { + fmt.Printf("The document is valid\n") + } else { + fmt.Printf("The document is not valid. see errors :\n") + for _, desc := range result.Errors() { + fmt.Printf("- %s\n", desc) + } + os.Exit(1) + } +} From 818a56a55969a39aaf6e57b93104a07393a08d57 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 12 Apr 2016 13:35:47 -0400 Subject: [PATCH 015/245] schema: image manifest Here is a validating schema for the Image Manifest https://github.com/opencontainers/image-spec/blob/master/manifest.md#image-manifest Signed-off-by: Vincent Batts --- schema/defs-image.json | 52 +++++++++++++++++++++++++++++++ schema/image-manifest-schema.json | 43 +++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 schema/defs-image.json create mode 100644 schema/image-manifest-schema.json diff --git a/schema/defs-image.json b/schema/defs-image.json new file mode 100644 index 0000000..82f6e95 --- /dev/null +++ b/schema/defs-image.json @@ -0,0 +1,52 @@ +{ + "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.+]+$" + }, + "blobObject": { + "id": "https://opencontainers.org/schema/image/blobObject", + "type": "object", + "required": [ + "mediaType", + "size", + "digest" + ], + "properties": { + "mediaType": { + "description": "the mediatype of the referenced object", + "$ref": "#definitions/mediaType" + }, + "size": { + "description": "the size in bytes of the referenced object", + "type": "integer" + }, + "digest": { + "description": "the cryptographic checksum digest of this object, in the pattern ':'", + "type": "string", + "pattern": "^[a-z0-9]+:[a-fA-F0-9]+$" + } + } + }, + "platform": { + "id": "https://opencontainers.org/schema/image/platform", + "type": "object", + "required": [ + "arch", + "os" + ], + "properties": { + "arch": { + "id": "https://opencontainers.org/schema/image/platform/arch", + "type": "string" + }, + "os": { + "id": "https://opencontainers.org/schema/image/platform/os", + "type": "string" + } + } + } + } +} diff --git a/schema/image-manifest-schema.json b/schema/image-manifest-schema.json new file mode 100644 index 0000000..1434725 --- /dev/null +++ b/schema/image-manifest-schema.json @@ -0,0 +1,43 @@ +{ + "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": "defs-image.json#/definitions/blobObject" + }, + "layers": { + "type": "array", + "items": { + "$ref": "defs-image.json#/definitions/blobObject" + } + }, + "annotations": { + "id": "https://opencontainers.org/schema/image/manifest/annotations", + "oneOf": [ + { + "$ref": "defs.json#/definitions/mapStringString" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "schemaVersion", + "mediaType", + "config", + "layers" + ] +} From 64b5003c9985ba8714201f1ecc01460770ab5828 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 12 Apr 2016 14:43:43 -0400 Subject: [PATCH 016/245] schema: image manifest list https://github.com/opencontainers/image-spec/blob/master/manifest.md#manifest-list Signed-off-by: Vincent Batts --- schema/defs-image.json | 73 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/schema/defs-image.json b/schema/defs-image.json index 82f6e95..cf4ad3c 100644 --- a/schema/defs-image.json +++ b/schema/defs-image.json @@ -6,6 +6,11 @@ "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-fA-F0-9]+$" + }, "blobObject": { "id": "https://opencontainers.org/schema/image/blobObject", "type": "object", @@ -24,27 +29,69 @@ "type": "integer" }, "digest": { - "description": "the cryptographic checksum digest of this object, in the pattern ':'", - "type": "string", - "pattern": "^[a-z0-9]+:[a-fA-F0-9]+$" + "$ref": "#definitions/digest" } } }, - "platform": { - "id": "https://opencontainers.org/schema/image/platform", + "manifestObject": { + "id": "https://opencontainers.org/schema/image/manifestObject", "type": "object", "required": [ - "arch", - "os" + "mediaType", + "size", + "digest", + "platform" ], "properties": { - "arch": { - "id": "https://opencontainers.org/schema/image/platform/arch", - "type": "string" + "mediaType": { + "description": "the mediatype of the referenced object", + "$ref": "#definitions/mediaType" + }, + "size": { + "description": "the size in bytes of the referenced object", + "type": "integer" + }, + "digest": { + "$ref": "#definitions/digest" }, - "os": { - "id": "https://opencontainers.org/schema/image/platform/os", - "type": "string" + "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 + } + } } } } From 66230f219ef3a4e0f9b79992dffe4e3ed47e37f2 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Thu, 14 Apr 2016 01:30:07 -0400 Subject: [PATCH 017/245] README: update to point to GitHub milestones Open question is whether we should just delete this section and replace it with milestones all together. Signed-off-by: Brandon Philips --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2021e89..794996a 100644 --- a/README.md +++ b/README.md @@ -40,18 +40,20 @@ A: We are seeing many independent implementations of container image handling in A: Existing formats can continue to be a proving ground for technologies, as needed. The OCI Image Format project should strive 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. -## Proposed Roadmap +## Roadmap -* April ??? v0.0.0 +The current roadmap can be found in the [GitHub milestones](https://github.com/opencontainers/image-spec/milestones) + +* April v0.0.0 * Import Docker v2.2 format -* April ??? v0.1.0 +* April v0.1.0 * Spec factored for top to bottom reading with three audiences in-mind: * Build system creators * Image registry creators * Container engine creators -* May ??? v0.2.0 +* May v0.2.0 * Release version of spec with improvements from two independent experimental implementations from OCI members e.g. Amazon Container Registry and rkt -* June ??? v1.0.0 +* June v1.0.0 * Release initial version of spec with two independent non-experimental implementations from OCI members # Contributing From f7ffdbde065efb4cd6864d70706d1ac9c9d776d3 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 4 Apr 2016 15:22:29 -0400 Subject: [PATCH 018/245] *: update for oc/image-spec Signed-off-by: Vincent Batts --- manifest.md | 263 ++++++++++++++++++------------------------- serialization.md | 282 ++++++++++++++++------------------------------- 2 files changed, 205 insertions(+), 340 deletions(-) diff --git a/manifest.md b/manifest.md index 9bcd495..0fec0e1 100644 --- a/manifest.md +++ b/manifest.md @@ -4,113 +4,90 @@ draft = true +++ -# Image Manifest Version 2, Schema 2 - -This document outlines the format of of the V2 image manifest, schema version 2. -The original (and provisional) image manifest for V2 (schema 1), was introduced -in the Docker daemon in the [v1.3.0 -release](https://github.com/docker/docker/commit/9f482a66ab37ec396ac61ed0c00d59122ac07453) -and is specified in the [schema 1 manifest definition](./manifest-v2-1.md) +# Image Manifest -This second schema version has two primary goals. The first is to allow -multi-architecture images, through a "fat manifest" which references image -manifests for platform-specific versions of an image. The second is to -move the Docker engine towards content-addressable images, by supporting -an image model where the image's configuration can be hashed to generate -an ID for the image. +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) # Media Types -The following media types are used by the manifest formats described here, and -the resources they reference: +The following media types are used by the manifest formats described here, and the resources they reference: -- `application/vnd.docker.distribution.manifest.v1+json`: schema1 (existing manifest format) -- `application/vnd.docker.distribution.manifest.v2+json`: New image manifest format (schemaVersion = 2) -- `application/vnd.docker.distribution.manifest.list.v2+json`: Manifest list, aka "fat manifest" -- `application/vnd.docker.image.rootfs.diff.tar.gzip`: "Layer", as a gzipped tar -- `application/vnd.docker.container.image.v1+json`: Container config JSON +- `application/vnd.oci.image.manifest.list.v1+json`: Manifest list, aka "fat manifest" +- `application/vnd.oci.image.manifest.v1+json`: Image manifest format +- `application/vnd.oci.image.rootfs.tar.gzip`: "Layer", as a gzipped tar archive +- `application/vnd.oci.image.serialization.v1+json`: Container config JSON ## Manifest List -The manifest list is the "fat manifest" which points to specific image manifests -for one or more platforms. Its use is optional, and relatively few images will -use one of these manifests. A client will distinguish a manifest list from an -image manifest based on the Content-Type returned in the HTTP response. +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* Field Descriptions - **`schemaVersion`** *int* - - This field specifies the image manifest schema version as an integer. This - schema uses the version `2`. + + This REQUIRED property specifies the image manifest schema version. + This schema uses the version `2`. - **`mediaType`** *string* - The MIME type of the manifest list. This should be set to - `application/vnd.docker.distribution.manifest.list.v2+json`. + This REQUIRED property contains the MIME type of the manifest list. + For this version of the specification, this MUST be set to `application/vnd.oci.image.manifest.list.v1+json`. - **`manifests`** *array* - The manifests field contains a list of manifests for specific platforms. + 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. - Fields of a object in the manifests list are: - - - **`mediaType`** *string* - - The MIME type of the referenced object. This will generally be - `application/vnd.docker.image.manifest.v2+json`, but it could also - be `application/vnd.docker.image.manifest.v1+json` if the manifest - list references a legacy schema-1 manifest. - - - **`size`** *int* - - The size in bytes of the object. This field exists so that a client - will have an expected size for the content before validating. If the - length of the retrieved content does not match the specified length, - the content should not be trusted. - - - **`digest`** *string* + Fields of each object in the manifests list are: + + - **`mediaType`** *string* - The digest of the content, as defined by the - [Registry V2 HTTP API Specificiation](https://docs.docker.com/registry/spec/api/#digest-parameter). + This REQUIRED property contains the MIME type of the referenced object. + (i.e. `application/vnd.oci.image.manifest.v1+json`) - - **`platform`** *object* + - **`size`** *int* - The platform object 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) + This REQUIRED property specifies the size in bytes of the object. + This field exists so that a client will have an expected size for the content before validating. + If the length of the retrieved content does not match the specified length, the content should not be trusted. - - **`architecture`** *string* + - **`digest`** *string* - The architecture field specifies the CPU architecture, for example - `amd64` or `ppc64le`. + The digest of the content, as defined by the [Registry V2 HTTP API Specificiation](https://docs.docker.com/registry/spec/api/#digest-parameter). - - **`os`** *string* + - **`platform`** *object* - The os field specifies the operating system, for example - `linux` or `windows`. + 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) - - **`os.version`** *string* + - **`architecture`** *string* - The optional os.version field specifies the operating system version, - for example `10.0.10586`. + This REQUIRED property specified the CPU architecture, for example `amd64` or `ppc64le`. - - **`os.features`** *array* + - **`os`** *string* - The optional os.features field specifies an array of strings, - each listing a required OS feature (for example on Windows - `win32k`). + This REQUIRED property specifies the operating system, for example `linux` or `windows`. - - **`variant`** *string* + - **`os.version`** *string* - The optional variant field specifies a variant of the CPU, for - example `armv6l` to specify a particular CPU variant of the ARM CPU. + This optional property specifies the operating system version, for example `10.0.10586`. - - **`features`** *array* + - **`os.features`** *array* - The optional features field specifies an array of strings, each - listing a required CPU feature (for example `sse4` or `aes`). + 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`). ## Example Manifest List @@ -118,10 +95,10 @@ image manifest based on the Content-Type returned in the HTTP response. ```json { "schemaVersion": 2, - "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", + "mediaType": "application/vnd.oci.image.manifest.list.v1+json", "manifests": [ { - "mediaType": "application/vnd.docker.image.manifest.v2+json", + "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7143, "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", "platform": { @@ -130,7 +107,7 @@ image manifest based on the Content-Type returned in the HTTP response. } }, { - "mediaType": "application/vnd.docker.image.manifest.v2+json", + "mediaType": "application/vnd.oci.image.manifest.v1+json", "size": 7682, "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270", "platform": { @@ -147,70 +124,63 @@ image manifest based on the Content-Type returned in the HTTP response. # Image Manifest -The image manifest provides a configuration and a set of layers for a container -image. It's the direct replacement for the schema-1 manifest. +The image manifest provides a configuration and a set of layers for a container image. ## *Image Manifest* Field Descriptions - **`schemaVersion`** *int* - - This field specifies the image manifest schema version as an integer. This - schema uses version `2`. + + This REQUIRED property specifies the image manifest schema version. + This schema uses version `2`. - **`mediaType`** *string* - The MIME type of the manifest. This should be set to - `application/vnd.docker.distribution.manifest.v2+json`. + This REQUIRED property contains the MIME type of the image manifest. + For this version of the specification, this MUST be set to `application/vnd.oci.image.manifest.v1+json`. - **`config`** *object* - The config field references a configuration object for a container, by - digest. This configuration item is a JSON blob that the runtime uses - to set up the container. This new schema uses a tweaked version - of this configuration to allow image content-addressability on the - daemon side. + The config field references a configuration object for a container, by digest. + This configuration item is a JSON blob that the runtime uses to set up the container. + This new schema uses a tweaked version of this configuration to allow image content-addressability on the daemon side. Fields of a config object are: - + - **`mediaType`** *string* - - The MIME type of the referenced object. This should generally be - `application/vnd.docker.container.image.v1+json`. - + + This REQUIRED property contains the MIME type of the referenced object. + (i.e. `application/vnd.oci.image.serialization.v1+json`) + - **`size`** *int* - - The size in bytes of the object. This field exists so that a client - will have an expected size for the content before validating. If the - length of the retrieved content does not match the specified length, - the content should not be trusted. - + + This REQUIRED property specifies the size in bytes of the object. + This field exists so that a client will have an expected size for the content before validating. + If the length of the retrieved content does not match the specified length, the content should not be trusted. + - **`digest`** *string* - The digest of the content, as defined by the - [Registry V2 HTTP API Specificiation](https://docs.docker.com/registry/spec/api/#digest-parameter). + The digest of the content, as defined by the [Registry V2 HTTP API Specificiation](https://docs.docker.com/registry/spec/api/#digest-parameter). - **`layers`** *array* The layer list is ordered starting from the base image (opposite order of schema1). Fields of an item in the layers list are: - + - **`mediaType`** *string* - - The MIME type of the referenced object. This should - generally be `application/vnd.docker.image.rootfs.diff.tar.gzip`. - + + This REQUIRED property contains the MIME type of the referenced object. + (i.e. `application/vnd.oci.image.rootfs.tar.gzip`) + - **`size`** *int* - - The size in bytes of the object. This field exists so that a client - will have an expected size for the content before validating. If the - length of the retrieved content does not match the specified length, - the content should not be trusted. - + + This REQUIRED property specifies the size in bytes of the object. + This field exists so that a client will have an expected size for the content before validating. + If the length of the retrieved content does not match the specified length, the content should not be trusted. + - **`digest`** *string* - The digest of the content, as defined by the - [Registry V2 HTTP API Specificiation](https://docs.docker.com/registry/spec/api/#digest-parameter). + The digest of the content, as defined by the [Registry V2 HTTP API Specificiation](https://docs.docker.com/registry/spec/api/#digest-parameter). ## Example Image Manifest @@ -218,25 +188,25 @@ image. It's the direct replacement for the schema-1 manifest. ```json { "schemaVersion": 2, - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "mediaType": "application/vnd.oci.image.manifest.v1+json", "config": { - "mediaType": "application/vnd.docker.container.image.v1+json", + "mediaType": "application/vnd.oci.image.serialization.v1+json", "size": 7023, "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7" }, "layers": [ { - "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "mediaType": "application/vnd.oci.image.rootfs.tar.gzip", "size": 32654, "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f" }, { - "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "mediaType": "application/vnd.oci.image.rootfs.tar.gzip", "size": 16724, "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b" }, { - "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "mediaType": "application/vnd.oci.image.rootfs.tar.gzip", "size": 73109, "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736" } @@ -246,39 +216,22 @@ image. It's the direct replacement for the schema-1 manifest. # Backward compatibility -The registry will continue to accept uploads of manifests in both the old and -new formats. - -When pushing images, clients which support the new manifest format should first -construct a manifest in the new format. If uploading this manifest fails, -presumably because the registry only supports the old format, the client may -fall back to uploading a manifest in the old format. - -When pulling images, clients indicate support for this new version of the -manifest format by sending the -`application/vnd.docker.distribution.manifest.v2+json` and -`application/vnd.docker.distribution.manifest.list.v2+json` media types in an -`Accept` header when making a request to the `manifests` endpoint. Updated -clients should check the `Content-Type` header to see whether the manifest -returned from the endpoint is in the old format, or is an image manifest or -manifest list in the new format. - -If the manifest being requested uses the new format, and the appropriate media -type is not present in an `Accept` header, the registry will assume that the -client cannot handle the manifest as-is, and rewrite it on the fly into the old -format. If the object that would otherwise be returned is a manifest list, the -registry will look up the appropriate manifest for the amd64 platform and -linux OS, rewrite that manifest into the old format if necessary, and return -the result to the client. If no suitable manifest is found in the manifest -list, the registry will return a 404 error. - -One of the challenges in rewriting manifests to the old format is that the old -format involves an image configuration for each layer in the manifest, but the -new format only provides one image configuration. To work around this, the -registry will create synthetic image configurations for all layers except the -top layer. These image configurations will not result in runnable images on -their own, but only serve to fill in the parent chain in a compatible way. -The IDs in these synthetic configurations will be derived from hashes of their -respective blobs. The registry will create these configurations and their IDs -using the same scheme as Docker 1.10 when it creates a legacy manifest to push -to a registry which doesn't support the new format. +The registry will continue to accept uploads of manifests in both the old and new formats. + +When pushing images, clients which support the new manifest format should first construct a manifest in the new format. +If uploading this manifest fails, presumably because the registry only supports the old format, the client may fall back to uploading a manifest in the old format. + +When pulling images, clients indicate support for this new version of the manifest format by sending the +`application/vnd.oci.image.manifest.v1+json` and +`application/vnd.oci.image.manifest.list.v1+json` media types in an `Accept` header when making a request to the `manifests` endpoint. +Updated clients should check the `Content-Type` header to see whether the manifest returned from the endpoint is in the old format, or is an image manifest or manifest list in the new format. + +If the manifest being requested uses the new format, and the appropriate media type is not present in an `Accept` header, the registry will assume that the client cannot handle the manifest as-is, and rewrite it on the fly into the old format. +If the object that would otherwise be returned is a manifest list, the registry will look up the appropriate manifest for the amd64 platform and linux OS, rewrite that manifest into the old format if necessary, and return the result to the client. +If no suitable manifest is found in the manifest list, the registry will return a 404 error. + +One of the challenges in rewriting manifests to the old format is that the old format involves an image configuration for each layer in the manifest, but the new format only provides one image configuration. +To work around this, the registry will create synthetic image configurations for all layers except the top layer. +These image configurations will not result in runnable images on their own, but only serve to fill in the parent chain in a compatible way. +The IDs in these synthetic configurations will be derived from hashes of their respective blobs. +The registry will create these configurations and their IDs using the same scheme as Docker 1.10 when it creates a legacy manifest to push to a registry which doesn't support the new format. diff --git a/serialization.md b/serialization.md index 57a599b..d32029d 100644 --- a/serialization.md +++ b/serialization.md @@ -1,10 +1,7 @@ -# Docker Image Specification v1.0.0 +# OpenContainers Image Serialization Specification -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 format of these filesystem changes and corresponding -parameters and describes how to create and use them for use with a container -runtime and execution tool. +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 format of these filesystem changes and corresponding parameters and describes how to create and use them for use with a container runtime and execution tool. ## Terminology @@ -15,102 +12,70 @@ This specification uses the following terms: Layer
- Images are composed of layers. Image layer is a general - term which may be used to refer to one or both of the following: + Images are composed of layers. + Image layer is a general term which may be used to refer to one or both of the following:
  1. The metadata for the layer, described in the JSON format.
  2. The filesystem changes described by a layer.
- To refer to the former you may use the term Layer JSON or - Layer Metadata. To refer to the latter you may use the term - Image Filesystem Changeset or Image Diff. + To refer to the former you may use the term Layer JSON or Layer Metadata. + To refer to the latter you may use the term Image Filesystem Changeset or Image Diff.
Image JSON
- Each layer 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 entry point, default arguments, CPU/memory shares, networking, and - volumes. + Each layer 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 entry point, default arguments, CPU/memory shares, networking, and volumes.
Image Filesystem Changeset
- Each layer has an archive of the files which have been added, changed, - or deleted relative to its parent 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. + Each layer has an archive of the files which have been added, changed, or deleted relative to its parent 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 ID
- Each layer is given an ID upon its creation. It is - represented as a hexadecimal encoding of 256 bits, e.g., - a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9. + Each layer is given an ID upon its creation. + It is represented as a hexadecimal encoding of 256 bits, e.g., a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9. Image IDs should be sufficiently random so as to be globally unique. - 32 bytes read from /dev/urandom is sufficient for all - practical purposes. Alternatively, an image ID may be derived as a - cryptographic hash of image contents as the result is considered - indistinguishable from random. The choice is left up to implementors. + 32 bytes read from /dev/urandom is sufficient for all practical purposes. + Alternatively, an image ID may be derived as a cryptographic hash of image contents as the result is considered indistinguishable from random. + The choice is left up to implementors.
Image Parent
- Most layer metadata structs contain a parent field which - refers to the Image from which another directly descends. An image - contains a separate JSON metadata file and set of changes relative to - the filesystem of its parent image. Image Ancestor and - Image Descendant are also common terms. + Most layer metadata structs contain a parent field which refers to the Image from which another directly descends. + An image contains a separate JSON metadata file and set of changes relative to the filesystem of its parent image. + Image Ancestor and Image Descendant are also common terms.
Image Checksum
- Layer metadata structs contain a cryptographic hash of the contents of - the layer's filesystem changeset. Though the set of changes exists as a - simple Tar archive, two archives with identical filenames and content - will have different SHA digests if the last-access or last-modified - times of any entries differ. For this reason, image checksums are - generated using the TarSum algorithm which produces a cryptographic - hash of file contents and selected headers only. Details of this - algorithm are described in the separate TarSum specification. + The checksum is a cryptographic digest of the artifact.
Tag
- A tag serves to map a descriptive, user-given name to any single image - ID. An image name suffix (the name component after :) is - often referred to as a tag as well, though it strictly refers to the - full name of an image. Acceptable values for a tag suffix are - implementation specific, but they SHOULD be limited to the set of - alphanumeric characters [a-zA-z0-9], punctuation - characters [._-], and MUST NOT contain a : - character. + A tag serves to map a descriptive, user-given name to any single image ID. + An image name suffix (the name component after :) is often referred to as a tag as well, though it strictly refers to the full name of an image. + Acceptable values for a tag suffix are implementation specific, but they SHOULD be limited to the set of alphanumeric characters [a-zA-z0-9], punctuation characters [._-], and MUST NOT contain a : character.
Repository
- A collection of tags grouped under a common prefix (the name component - before :). For example, in an image tagged with the name - my-app:3.1.4, my-app is the Repository - component of the name. Acceptable values for repository name are - implementation specific, but they SHOULD be limited to the set of - alphanumeric characters [a-zA-z0-9], and punctuation - characters [._-], however it MAY contain additional - / and : characters for organizational - purposes, with the last : character being interpreted - dividing the repository component of the name from the tag suffix - component. + A collection of tags grouped under a common prefix (the name component before :). + For example, in an image tagged with the name my-app:3.1.4, my-app is the Repository component of the name. + Acceptable values for repository name are implementation specific, but they SHOULD be limited to the set of alphanumeric characters [a-zA-z0-9], and punctuation characters [._-], however it MAY contain additional / and : characters for organizational purposes, with the last : character being interpreted dividing the repository component of the name from the tag suffix component.
@@ -165,27 +130,24 @@ Here is an example image JSON file: id string
- Randomly generated, 256-bit, hexadecimal encoded. Uniquely identifies - the image. + Randomly generated, 256-bit, hexadecimal encoded. + Uniquely identifies the image.
parent string
- ID of the parent image. If there is no parent image then this field - should be omitted. A collection of images may share many of the same - ancestor layers. This organizational structure is strictly a tree with - any one layer having either no parent or a single parent and zero or - more descendant layers. Cycles are not allowed and implementations - should be careful to avoid creating them or iterating through a cycle - indefinitely. + ID of the parent image. + If there is no parent image then this field should be omitted. + A collection of images may share many of the same ancestor layers. + This organizational structure is strictly a tree with any one layer having either no parent or a single parent and zero or more descendant layers. + Cycles are not allowed and implementations should be careful to avoid creating them or iterating through a cycle indefinitely.
created string
- ISO-8601 formatted combined date and time at which the image was - created. + ISO-8601 formatted combined date and time at which the image was created.
author string @@ -198,15 +160,14 @@ Here is an example image JSON file: architecture string
- The CPU architecture which the binaries in this image are built to run - on. Possible values include: + 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. + More values may be supported in the future and any of these may or may not be supported by a given container runtime implementation.
os string @@ -226,24 +187,20 @@ Here is an example image JSON file: checksum string
- Image Checksum of the filesystem changeset associated with the image - layer. + Image Checksum of the filesystem changeset associated with the image layer.
Size integer
- The size in bytes of the filesystem changeset associated with the image - layer. + The size in bytes of the filesystem changeset associated with the image layer.
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. + 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

@@ -252,9 +209,10 @@ Here is an example image JSON file: 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.

+

+ 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:

@@ -267,44 +225,38 @@ Here is an example image JSON file:
  • 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.

    +

    + 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. + 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. + 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. + 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: + 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": {},
    @@ -324,48 +276,39 @@ Here is an example image JSON file:
                             "port"
                         
                     
    -                with the default protocol being "tcp" if not
    -                specified.
    +                with the default protocol being "tcp" if not specified.
     
    -                These values act as defaults and are merged with any specified
    -                when creating a container.
    +                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. + 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. + 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.
    Cmd array of strings
    - Default arguments to the entry point 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. + Default arguments to the entry point 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.
    Volumes struct
    - A set of directories which should be created as data volumes in - 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: + A set of directories which should be created as data volumes in 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:
    {
         "/var/my-app-data/": {},
         "/etc/some-config.d/": {},
    @@ -375,28 +318,22 @@ Here is an example image JSON file:
                     WorkingDir string
                 
                 
    - Sets the current working directory of the entry point process - in the container. This value acts as a default and is replaced - by a working directory specified when creating a container. + Sets the current working directory of the entry point process in the container. + This value acts as a default and is replaced by a working directory specified when creating a container.
    -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. +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. ## Creating an Image Filesystem Changeset An example of creating an Image Filesystem Changeset follows. -An image root filesystem is first created as an empty directory named with the -ID of the image being created. Here is the initial empty directory structure -for the changeset for an image with ID `c3167915dc9d` ([real IDs are much -longer](#id_desc), but this example use a truncated one here for brevity. -Implementations need not name the rootfs directory in this way but it may be -convenient for keeping record of a large number of image layers.): +An image root filesystem is first created as an empty directory named with the ID of the image being created. +Here is the initial empty directory structure for the changeset for an image with ID `c3167915dc9d` ([real IDs are much longer](#id_desc), but this example use a truncated one here for brevity. +Implementations need not name the rootfs directory in this way but it may be convenient for keeping record of a large number of image layers.): ``` c3167915dc9d/ @@ -422,14 +359,13 @@ bin/my-app-binary bin/my-app-tools ``` -The TarSum checksum for the archive file is then computed and placed in the -JSON metadata along with the execution parameters. +The digest checksum for the archive file is then computed and placed in the JSON metadata along with the execution parameters. To make changes to the filesystem of this container image, create a new directory named with a new ID, such as `f60c56784b83`, and initialize it with a snapshot of the parent image's root filesystem, so that the directory is -identical to that of `c3167915dc9d`. NOTE: a copy-on-write or union filesystem -can make this very efficient: +identical to that of `c3167915dc9d`. +NOTE: a copy-on-write or union filesystem can make this very efficient: ``` f60c56784b83/ @@ -440,10 +376,9 @@ f60c56784b83/ my-app-tools ``` -This example change is going add a configuration directory at `/etc/my-app.d` -which contains a default config file. There's also a change to the -`my-app-tools` binary to handle the config layout change. The `f60c56784b83` -directory then looks like this: +This example change is going add a configuration directory at `/etc/my-app.d` which contains a default config file. +There's also a change to the `my-app-tools` binary to handle the config layout change. +The `f60c56784b83` directory then looks like this: ``` f60c56784b83/ @@ -455,13 +390,10 @@ f60c56784b83/ my-app-tools ``` -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. Before committing this directory to a -changeset, because it has a parent image, it is first compared with the -directory tree of the parent snapshot, `f60c56784b83`, looking for files and -directories that have been added, modified, or removed. The following changeset -is found: +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. +Before committing this directory to a changeset, because it has a parent image, it is first compared with the directory tree of the parent snapshot, `f60c56784b83`, looking for files and directories that have been added, modified, or removed. +The following changeset is found: ``` Added: /etc/my-app.d/default.cfg @@ -472,11 +404,10 @@ Deleted: /etc/my-app-config A Tar Archive is then created which contains *only* this changeset: The added and modified files and directories in their entirety, and for each deleted item an entry for an empty file at the same location but with the basename of the -deleted file or directory prefixed with `.wh.`. The filenames prefixed with -`.wh.` are known as "whiteout" files. NOTE: For this reason, it is not possible -to create an image root filesystem which contains a file or directory with a -name beginning with `.wh.`. The resulting Tar archive for `f60c56784b83` has -the following entries: +deleted file or directory prefixed with `.wh.`. +The filenames prefixed with `.wh.` are known as "whiteout" files. +NOTE: For this reason, it is not possible to create an image root filesystem which contains a file or directory with a name beginning with `.wh.`. +The resulting Tar archive for `f60c56784b83` has the following entries: ``` /etc/my-app.d/default.cfg @@ -484,20 +415,17 @@ the following entries: /etc/.wh.my-app-config ``` -Any given image is likely to be composed of several of these Image Filesystem -Changeset tar archives. +Any given image is likely to be composed of several of these Image Filesystem Changeset tar archives. ## Combined Image JSON + Filesystem Changeset Format -There is also a format for a single archive which contains complete information -about an image, including: +There is also a format for a single archive which contains complete information about an image, including: - repository names/tags - all image layer JSON files - all tar archives of each layer filesystem changesets -For example, here's what the full archive of `library/busybox` is (displayed in -`tree` format): +For example, here's what the full archive of `library/busybox` is (displayed in `tree` format): ``` . @@ -520,13 +448,12 @@ For example, here's what the full archive of `library/busybox` is (displayed in └── repositories ``` -There are one or more directories named with the ID for each layer in a full -image. Each of these directories contains 3 files: +There are one or more directories named with the ID for each layer in a full image. +Each of these directories contains 3 files: * `VERSION` - The schema version of the `json` file * `json` - The JSON metadata for an image layer - * `layer.tar` - The Tar archive of the filesystem changeset for an image - layer. + * `layer.tar` - The Tar archive of the filesystem changeset for an image layer. The content of the `VERSION` files is simply the semantic version of the JSON metadata schema: @@ -545,29 +472,14 @@ And the `repositories` file is another JSON file which describes names/tags: } ``` -Every key in this object is the name of a repository, and maps to a collection -of tag suffixes. Each tag maps to the ID of the image represented by that tag. +Every key in this object is the name of a repository, and maps to a collection of tag suffixes. Each tag maps to the ID of the image represented by that tag. ## Loading an Image Filesystem Changeset -Unpacking a bundle of image layer JSON files and their corresponding filesystem -changesets can be done using a series of steps: +Unpacking a bundle of image layer JSON files and their corresponding filesystem changesets can be done using a series of steps: -1. Follow the parent IDs of image layers to find the root ancestor (an image -with no parent ID specified). -2. For every image layer, in order from root ancestor and descending down, -extract the contents of that layer's filesystem changeset archive into a -directory which will be used as the root of a container filesystem. +1. Follow the parent IDs of image layers to find the root ancestor (an image with no parent ID specified). +2. For every image layer, in order from root ancestor and descending down, extract the contents of that layer's filesystem changeset archive into a directory which will be used as the root of a container filesystem. - Extract all contents of each archive. - - Walk the directory tree once more, removing any files with the prefix - `.wh.` and the corresponding file or directory named without this prefix. - - -## Implementations - -This specification is an admittedly imperfect description of an -imperfectly-understood problem. The Docker project is, in turn, an attempt to -implement this specification. Our goal and our execution toward it will evolve -over time, but our primary concern in this specification and in our -implementation is compatibility and interoperability. + - Walk the directory tree once more, removing any files with the prefix `.wh.` and the corresponding file or directory named without this prefix. From a92d50dcaa1019fa9297c399bbe696310ad8f054 Mon Sep 17 00:00:00 2001 From: Timothy Hobbs Date: Fri, 15 Apr 2016 01:15:48 +0200 Subject: [PATCH 019/245] Include whiteout file in example of how to use white out files Signed-off-by: Timothy Hobbs --- serialization.md | 1 + 1 file changed, 1 insertion(+) diff --git a/serialization.md b/serialization.md index d32029d..9e4b5f4 100644 --- a/serialization.md +++ b/serialization.md @@ -383,6 +383,7 @@ The `f60c56784b83` directory then looks like this: ``` f60c56784b83/ etc/ + .wh.my-app.cfg my-app.d/ default.cfg bin/ From 5a5144f1ab009b1727427d3b3e19a572bf876cad Mon Sep 17 00:00:00 2001 From: Jonathan Boulle Date: Fri, 15 Apr 2016 12:10:55 +0200 Subject: [PATCH 020/245] README: correct minor grammar typo Signed-off-by: Jonathan Boulle --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 794996a..2a8f749 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ The current roadmap can be found in the [GitHub milestones](https://github.com/o * April v0.0.0 * Import Docker v2.2 format * April v0.1.0 - * Spec factored for top to bottom reading with three audiences in-mind: + * Spec factored for top to bottom reading with three audiences in mind: * Build system creators * Image registry creators * Container engine creators From 949ea01d9d0f0a42d3a5f864ed6adacdeac0b826 Mon Sep 17 00:00:00 2001 From: Timothy Hobbs Date: Fri, 15 Apr 2016 01:05:31 +0200 Subject: [PATCH 021/245] Don't merge image contents and data volumes Signed-off-by: Timothy Hobbs --- serialization.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/serialization.md b/serialization.md index d32029d..1d2b15c 100644 --- a/serialization.md +++ b/serialization.md @@ -307,6 +307,9 @@ Here is an example image JSON file:
    A set of directories which should be created as data volumes in a container running this image. +

    + 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:
    {
    
    From 93330ae583524ddb745ca1053c0500bb65405586 Mon Sep 17 00:00:00 2001
    From: Vincent Batts 
    Date: Fri, 15 Apr 2016 20:47:05 +0000
    Subject: [PATCH 022/245] schema: switch Object to Descriptor
    
    Signed-off-by: Vincent Batts 
    ---
     schema/defs-image.json            | 8 ++++----
     schema/image-manifest-schema.json | 4 ++--
     2 files changed, 6 insertions(+), 6 deletions(-)
    
    diff --git a/schema/defs-image.json b/schema/defs-image.json
    index cf4ad3c..e2e221d 100644
    --- a/schema/defs-image.json
    +++ b/schema/defs-image.json
    @@ -11,8 +11,8 @@
           "type": "string",
           "pattern": "^[a-z0-9]+:[a-fA-F0-9]+$"
         },
    -    "blobObject": {
    -      "id": "https://opencontainers.org/schema/image/blobObject",
    +    "descriptor": {
    +      "id": "https://opencontainers.org/schema/image/descriptor",
           "type": "object",
           "required": [
             "mediaType",
    @@ -33,8 +33,8 @@
             }
           }
         },
    -    "manifestObject": {
    -      "id": "https://opencontainers.org/schema/image/manifestObject",
    +    "manifestDescriptor": {
    +      "id": "https://opencontainers.org/schema/image/manifestDescriptor",
           "type": "object",
           "required": [
             "mediaType",
    diff --git a/schema/image-manifest-schema.json b/schema/image-manifest-schema.json
    index 1434725..411fa2d 100644
    --- a/schema/image-manifest-schema.json
    +++ b/schema/image-manifest-schema.json
    @@ -14,12 +14,12 @@
           "$ref": "defs-image.json#/definitions/mediaType"
         },
         "config": {
    -      "$ref": "defs-image.json#/definitions/blobObject"
    +      "$ref": "defs-image.json#/definitions/descriptor"
         },
         "layers": {
           "type": "array",
           "items": {
    -        "$ref": "defs-image.json#/definitions/blobObject"
    +        "$ref": "defs-image.json#/definitions/descriptor"
           }
         },
         "annotations": {
    
    From 0947a33e43009fb80afd3d44dda068e0f1e6c67d Mon Sep 17 00:00:00 2001
    From: Vincent Batts 
    Date: Fri, 15 Apr 2016 16:51:23 -0400
    Subject: [PATCH 023/245] schema: actually add the schema file
    
    I managed to forget adding the file in
    https://github.com/opencontainers/image-spec/pull/18
    
    Signed-off-by: Vincent Batts 
    ---
     schema/manifest-list-schema.json | 28 ++++++++++++++++++++++++++++
     1 file changed, 28 insertions(+)
     create mode 100644 schema/manifest-list-schema.json
    
    diff --git a/schema/manifest-list-schema.json b/schema/manifest-list-schema.json
    new file mode 100644
    index 0000000..43033da
    --- /dev/null
    +++ b/schema/manifest-list-schema.json
    @@ -0,0 +1,28 @@
    +{
    +  "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/manifestObject"
    +      }
    +    }
    +  },
    +  "required": [
    +    "schemaVersion",
    +    "mediaType",
    +    "manifests"
    +  ]
    +}
    
    From 4b14700ad1e3441433472d9077a3da0fcefa23a0 Mon Sep 17 00:00:00 2001
    From: Vincent Batts 
    Date: Fri, 15 Apr 2016 18:02:25 -0400
    Subject: [PATCH 024/245] schema: remove annotations field
    
    This was yank-and-paste from the runtime-spec that I was mulling on and
    forgot to remove before the PR.
    
    While this might can be added back, it will likely have its own
    discussion.
    
    Signed-off-by: Vincent Batts 
    ---
     schema/image-manifest-schema.json | 11 -----------
     1 file changed, 11 deletions(-)
    
    diff --git a/schema/image-manifest-schema.json b/schema/image-manifest-schema.json
    index 1434725..dd859b1 100644
    --- a/schema/image-manifest-schema.json
    +++ b/schema/image-manifest-schema.json
    @@ -21,17 +21,6 @@
           "items": {
             "$ref": "defs-image.json#/definitions/blobObject"
           }
    -    },
    -    "annotations": {
    -      "id": "https://opencontainers.org/schema/image/manifest/annotations",
    -      "oneOf": [
    -        {
    -          "$ref": "defs.json#/definitions/mapStringString"
    -        },
    -        {
    -          "type": "null"
    -        }
    -      ]
         }
       },
       "required": [
    
    From 1f429153473ed75f6685e63af519a8769fb0dc92 Mon Sep 17 00:00:00 2001
    From: Stephen J Day 
    Date: Thu, 21 Apr 2016 17:09:58 -0700
    Subject: [PATCH 025/245] schema: use correct reference for manifestDescriptor
    
    Signed-off-by: Stephen J Day 
    ---
     schema/manifest-list-schema.json | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/schema/manifest-list-schema.json b/schema/manifest-list-schema.json
    index 43033da..f9ddd62 100644
    --- a/schema/manifest-list-schema.json
    +++ b/schema/manifest-list-schema.json
    @@ -16,7 +16,7 @@
         "manifests": {
           "type": "array",
           "items": {
    -        "$ref": "defs-image.json#/definitions/manifestObject"
    +        "$ref": "defs-image.json#/definitions/manifestDescriptor"
           }
         }
       },
    
    From aab1201ced002bdf80a936f4c2c65fc5dedae70e Mon Sep 17 00:00:00 2001
    From: Vincent Batts 
    Date: Thu, 28 Apr 2016 15:35:32 -0400
    Subject: [PATCH 026/245] mediatype: center the types on their own
    
    Signed-off-by: Vincent Batts 
    ---
     manifest.md    | 12 ++----------
     media-types.md |  8 ++++++++
     2 files changed, 10 insertions(+), 10 deletions(-)
     create mode 100644 media-types.md
    
    diff --git a/manifest.md b/manifest.md
    index 0fec0e1..0a1a028 100644
    --- a/manifest.md
    +++ b/manifest.md
    @@ -4,23 +4,15 @@ draft = true
     +++
     
     
    -# Image Manifest
    +# 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)
     
    -# Media Types
    -
    -The following media types are used by the manifest formats described here, and the resources they reference:
    -
    -- `application/vnd.oci.image.manifest.list.v1+json`: Manifest list, aka "fat manifest"
    -- `application/vnd.oci.image.manifest.v1+json`: Image manifest format
    -- `application/vnd.oci.image.rootfs.tar.gzip`: "Layer", as a gzipped tar archive
    -- `application/vnd.oci.image.serialization.v1+json`: Container config JSON
     
    -## Manifest List
    +# 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.
    diff --git a/media-types.md b/media-types.md
    new file mode 100644
    index 0000000..29e4f17
    --- /dev/null
    +++ b/media-types.md
    @@ -0,0 +1,8 @@
    +# Media Types
    +
    +The following `mediaType` MIME types are used by the formats described here, and the resources they reference:
    +
    +- `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.rootfs.tar.gzip`: ["Layer", as a gzipped tar archive](serialization.md#creating-an-image-filesystem-changeset)
    +- `application/vnd.oci.image.serialization.v1+json`: [Container config JSON](serialization.md#image-json-description)
    
    From ff48948fc5d16b455f42b40f8c9d79da27173c1c Mon Sep 17 00:00:00 2001
    From: Vincent Batts 
    Date: Thu, 28 Apr 2016 15:47:54 -0400
    Subject: [PATCH 027/245] media-types: type for combined layout
    
    This gives a distinguished type for the combined JSON + filesystem
    changeset layout.
    
    Signed-off-by: Vincent Batts 
    ---
     media-types.md | 5 +++--
     1 file changed, 3 insertions(+), 2 deletions(-)
    
    diff --git a/media-types.md b/media-types.md
    index 29e4f17..27587b3 100644
    --- a/media-types.md
    +++ b/media-types.md
    @@ -4,5 +4,6 @@ The following `mediaType` MIME types are used by the formats described here, and
     
     - `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.rootfs.tar.gzip`: ["Layer", as a gzipped tar archive](serialization.md#creating-an-image-filesystem-changeset)
    -- `application/vnd.oci.image.serialization.v1+json`: [Container config JSON](serialization.md#image-json-description)
    +- `application/vnd.oci.image.serialization.rootfs.tar.gzip`: ["Layer", as a gzipped tar archive](serialization.md#creating-an-image-filesystem-changeset)
    +- `application/vnd.oci.image.serialization.config.v1+json`: [Container config JSON](serialization.md#image-json-description)
    +- `application/vnd.oci.image.serialization.combined.v1+json`: [Combined image JSON and filesystem changesets](serialization.md#combined-image-json--filesystem-changeset-format)
    
    From 197a8a8ec816bd88238ec0e64789d50f4736291c Mon Sep 17 00:00:00 2001
    From: Stephen J Day 
    Date: Thu, 21 Apr 2016 17:29:00 -0700
    Subject: [PATCH 028/245] examples: validate markdown examples
    
    To ensure that examples in the specification match up with the json
    schemas, we propose a validation approach that will extract the fenced
    code blocks from the markdown document and validate them. Examples are
    marked with specification mediatypes which are then validated against
    the respective json shema doc.
    
    From here, we can propose that all examples be part of the
    specification. Each act of clarification would be represented within the
    specification and validated against the schema.
    
    The tool can be run from the root repository directory with the
    following command:
    
    ```console
    $ go run examples.go < manifest.md
    ok	 application/vnd.oci.image.manifest.list.v1+json
    ok	 application/vnd.oci.image.manifest.v1+json
    ```
    
    This changeset should be followeb by the following tasks:
    
    1. Integrate with top-level Makefile.
    2. Move tools (validate.go, examples.go) to `cmd/` directory.
    3. Run validation as part of travis CI testsuite.
    
    Note that there seems to be a bug in the schema validator when using
    recursive references. We can investigate this after this PR is merged.
    
    Signed-off-by: Stephen J Day 
    ---
     examples.go | 208 ++++++++++++++++++++++++++++++++++++++++++++++++++++
     manifest.md |   4 +-
     2 files changed, 210 insertions(+), 2 deletions(-)
     create mode 100644 examples.go
    
    diff --git a/examples.go b/examples.go
    new file mode 100644
    index 0000000..5a2d7b7
    --- /dev/null
    +++ b/examples.go
    @@ -0,0 +1,208 @@
    +package main
    +
    +import (
    +	"bytes"
    +	"errors"
    +	"flag"
    +	"fmt"
    +	"io"
    +	"io/ioutil"
    +	"log"
    +	"net/url"
    +	"os"
    +	"path/filepath"
    +	"strings"
    +
    +	"github.com/russross/blackfriday"
    +	"github.com/xeipuuv/gojsonschema"
    +)
    +
    +var (
    +	verbose    bool
    +	skipped    bool
    +	schemaPath string
    +)
    +
    +func init() {
    +	flag.BoolVar(&verbose, "verbose", false, "display examples no matter what")
    +	flag.BoolVar(&skipped, "skipped", false, "show skipped examples")
    +	flag.StringVar(&schemaPath, "schema", "./schema", "specify location of schema directory")
    +}
    +
    +func main() {
    +	flag.Parse()
    +
    +	examples, err := extractExamples(os.Stdin)
    +	if err != nil {
    +		log.Fatalln(err)
    +	}
    +	var fail bool
    +	for _, example := range examples {
    +		if example.Err != nil {
    +			printFields("error", example.Mediatype, example.Title, example.Err)
    +			fail = true
    +			continue
    +		}
    +
    +		schema, err := schemaByMediatype(schemaPath, example.Mediatype)
    +		if err != nil {
    +			if err == errSchemaNotFound {
    +				if skipped {
    +					printFields("skip", example.Mediatype, example.Title)
    +
    +					if verbose {
    +						fmt.Println(example.Body, "---")
    +					}
    +				}
    +				continue
    +			}
    +		}
    +
    +		// BUG(stevvooe): Recursive validation is not working. Need to
    +		// investigate. Will use this code as information for bug.
    +		document := gojsonschema.NewStringLoader(example.Body)
    +		result, err := gojsonschema.Validate(schema, document)
    +
    +		if err != nil {
    +			printFields("error", example.Mediatype, example.Title, err)
    +			fmt.Println(example.Body, "---")
    +			fail = true
    +			continue
    +		}
    +
    +		if !result.Valid() {
    +			// TOOD(stevvooe): This is nearly useless without file, line no.
    +			printFields("invalid", example.Mediatype, example.Title)
    +			for _, desc := range result.Errors() {
    +				printFields("reason", example.Mediatype, example.Title, desc)
    +			}
    +			fmt.Println(example.Body, "---")
    +			fail = true
    +			continue
    +		}
    +
    +		printFields("ok", example.Mediatype, example.Title)
    +		if verbose {
    +			fmt.Println(example.Body, "---")
    +		}
    +	}
    +
    +	if fail {
    +		os.Exit(1)
    +	}
    +}
    +
    +var (
    +	specsByMediaType = map[string]string{
    +		"application/vnd.oci.image.manifest.v1+json":      "image-manifest-schema.json",
    +		"application/vnd.oci.image.manifest.list.v1+json": "manifest-list-schema.json",
    +	}
    +
    +	errSchemaNotFound = errors.New("schema: not found")
    +	errFormatInvalid  = errors.New("format: invalid")
    +)
    +
    +func schemaByMediatype(root, mediatype string) (gojsonschema.JSONLoader, error) {
    +	name, ok := specsByMediaType[mediatype]
    +	if !ok {
    +		return nil, errSchemaNotFound
    +	}
    +
    +	if !filepath.IsAbs(root) {
    +		wd, err := os.Getwd()
    +		if err != nil {
    +			return nil, err
    +		}
    +		root = filepath.Join(wd, root)
    +	}
    +
    +	// lookup path
    +	path := filepath.Join(root, name)
    +	return gojsonschema.NewReferenceLoader("file://" + path), nil
    +}
    +
    +// 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(vs ...interface{}) {
    +	var ss []string
    +	for _, f := range vs {
    +		ss = append(ss, fmt.Sprint(f))
    +	}
    +	fmt.Println(strings.Join(ss, "\t"))
    +}
    diff --git a/manifest.md b/manifest.md
    index 0fec0e1..7a19536 100644
    --- a/manifest.md
    +++ b/manifest.md
    @@ -92,7 +92,7 @@ A client will distinguish a manifest list from an image manifest based on the Co
     ## Example Manifest List
     
     *Example showing a simple manifest list pointing to image manifests for two platforms:*
    -```json
    +```json,title=Manifest%20List&mediatype=application/vnd.oci.image.manifest.list.v1%2Bjson
     {
       "schemaVersion": 2,
       "mediaType": "application/vnd.oci.image.manifest.list.v1+json",
    @@ -185,7 +185,7 @@ The image manifest provides a configuration and a set of layers for a container
     ## Example Image Manifest
     
     *Example showing an image manifest:*
    -```json
    +```json,title=Manifest&mediatype=application/vnd.oci.image.manifest.v1%2Bjson
     {
       "schemaVersion": 2,
       "mediaType": "application/vnd.oci.image.manifest.v1+json",
    
    From 48c6221cfff395e68d5da705a16ba3bc0aac8dc0 Mon Sep 17 00:00:00 2001
    From: Stephen J Day 
    Date: Thu, 28 Apr 2016 14:22:43 -0700
    Subject: [PATCH 029/245] cmd: move validation into cmd directories
    
    We can work out the hierarchy more carefully and likely merge these, but
    these tools have been moved into cmd/, per Go best practices.
    
    Signed-off-by: Stephen J Day 
    ---
     Makefile                                      | 22 +++++++++++++++++++
     .../oci-validate-examples/main.go             |  0
     .../oci-validate-json/main.go                 |  0
     schema/Makefile                               | 15 -------------
     4 files changed, 22 insertions(+), 15 deletions(-)
     create mode 100644 Makefile
     rename examples.go => cmd/oci-validate-examples/main.go (100%)
     rename schema/validate.go => cmd/oci-validate-json/main.go (100%)
     delete mode 100644 schema/Makefile
    
    diff --git a/Makefile b/Makefile
    new file mode 100644
    index 0000000..36c126c
    --- /dev/null
    +++ b/Makefile
    @@ -0,0 +1,22 @@
    +
    +default: help
    +
    +help:
    +	@echo "Usage: make "
    +	@echo
    +	@echo " * 'fmt' - format the json with indentation"
    +	@echo " * 'validate' - build the validation tool"
    +
    +fmt:
    +	for i in *.json ; do jq --indent 2 -M . "$${i}" > xx && cat xx > "$${i}" && rm xx ; done
    +
    +.PHONY: validate-examples
    +validate-examples: oci-validate-examples
    +	./oci-validate-examples < manifest.md
    +
    +oci-validate-json: validate.go
    +	go build ./cmd/oci-validate-json
    +
    +oci-validate-examples: cmd/oci-validate-examples/main.go
    +	go build ./cmd/oci-validate-examples
    +
    diff --git a/examples.go b/cmd/oci-validate-examples/main.go
    similarity index 100%
    rename from examples.go
    rename to cmd/oci-validate-examples/main.go
    diff --git a/schema/validate.go b/cmd/oci-validate-json/main.go
    similarity index 100%
    rename from schema/validate.go
    rename to cmd/oci-validate-json/main.go
    diff --git a/schema/Makefile b/schema/Makefile
    deleted file mode 100644
    index ae94071..0000000
    --- a/schema/Makefile
    +++ /dev/null
    @@ -1,15 +0,0 @@
    -
    -default: help
    -
    -help:
    -	@echo "Usage: make "
    -	@echo
    -	@echo " * 'fmt' - format the json with indentation"
    -	@echo " * 'validate' - build the validation tool"
    -
    -fmt:
    -	for i in *.json ; do jq --indent 2 -M . "$${i}" > xx && cat xx > "$${i}" && rm xx ; done
    -
    -validate: validate.go
    -	go build ./validate.go
    -
    
    From 119483e35044963e18ab58dedf7fc3a71a999c42 Mon Sep 17 00:00:00 2001
    From: "Rob Dolin (MSFT)" 
    Date: Thu, 28 Apr 2016 14:23:14 -0700
    Subject: [PATCH 030/245] [ Image Format Spec ] Remove Code of Conduct file
    
    Since the ReadMe now points to the shared Code of Conduct in the OCI TOB repo, we can remove this file.
    
    Signed-off-by: Rob Dolin 
    ---
     code-of-conduct.md | 37 -------------------------------------
     1 file changed, 37 deletions(-)
     delete mode 100644 code-of-conduct.md
    
    diff --git a/code-of-conduct.md b/code-of-conduct.md
    deleted file mode 100644
    index 06cb2b8..0000000
    --- a/code-of-conduct.md
    +++ /dev/null
    @@ -1,37 +0,0 @@
    -# OpenContainers Code of Conduct
    -
    -Behave as a community member, follow the code of conduct.
    -
    -## Code of Conduct
    -
    -The OpenContainers community is made up of a mixture of professionals and volunteers from all over the world.
    -
    -When we disagree, we try to understand why.
    -Disagreements, both social and technical, happen all the time and OpenContainers is no exception.
    -It is important that we resolve disagreements and differing views constructively.
    -
    -This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
    -Participants should be aware of these concerns.
    -
    -We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
    -
    -Examples of unacceptable behavior by participants include:
    -
    -* The use of sexualized language or imagery
    -* Personal attacks
    -* Trolling or insulting/derogatory comments
    -* Public or private harassment
    -* Publishing other's private information, such as physical or electronic addresses, without explicit permission
    -* Other unethical or unprofessional conduct
    -
    -The OpenContainers team does not condone any statements by speakers contrary to these standards.
    -The OpenContainers team reserves the right to deny participation any individual found to be engaging in discriminatory or harassing actions.
    -
    -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct.
    -By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project.
    -
    -## Thanks 
    -
    -Thanks to the [Fedora Code of Conduct](https://getfedora.org/code-of-conduct) and [Contributor Covenant](http://contributor-covenant.org) for inspiration and ideas.
    -
    -Portions of this Code of Conduct are adapted from the Contributor Covenant, version 1.2.0, available at http://contributor-covenant.org/version/1/2/0/
    
    From 11a7f29f6a5dcaaf3a1a0ed67b8cd007923d538b Mon Sep 17 00:00:00 2001
    From: Vincent Batts 
    Date: Thu, 28 Apr 2016 22:17:43 -0400
    Subject: [PATCH 031/245] travis: validate examples
    
    ensure the JSON examples in the documentation validates.
    
    Signed-off-by: Vincent Batts 
    ---
     .travis.yml | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/.travis.yml b/.travis.yml
    index bbee276..fdf4327 100644
    --- a/.travis.yml
    +++ b/.travis.yml
    @@ -6,9 +6,11 @@ sudo: false
     
     before_install:
       - go get github.com/vbatts/git-validation
    +  - go get -d ./cmd/...
     
     install: true
     
     script:
       - $HOME/gopath/bin/git-validation -run DCO,short-subject -v -range ${TRAVIS_COMMIT_RANGE}
    +  - make validate-examples
       
    
    From e3d8984157fe736543867900eba8ce96568e8c3c Mon Sep 17 00:00:00 2001
    From: Vincent Batts 
    Date: Wed, 27 Apr 2016 14:09:22 -0400
    Subject: [PATCH 032/245] serialization: rebase on docker draft
    
    from https://github.com/docker/docker/pull/22264
    specifically commit a0237314277148093987380edc10222e63ba19e2
    
    This does remove the one-line-per-sentence formatting, but we can
    address that later.
    
    Signed-off-by: Vincent Batts 
    ---
     serialization.md | 457 +++++++++++++++++++++++++++++++----------------
     1 file changed, 305 insertions(+), 152 deletions(-)
    
    diff --git a/serialization.md b/serialization.md
    index 1d2b15c..deaff6f 100644
    --- a/serialization.md
    +++ b/serialization.md
    @@ -1,7 +1,12 @@
    -# OpenContainers Image Serialization Specification
    +# Docker Image Specification v1.1.0
     
    -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 format of these filesystem changes and corresponding parameters and describes how to create and use them for use with a container runtime and execution tool.
    +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 format of these filesystem changes and corresponding
    +parameters and describes how to create and use them for use with a container
    +runtime and execution tool.
    +
    +This version of the image specification was adopted starting in Docker 1.10.
     
     ## Terminology
     
    @@ -12,70 +17,94 @@ This specification uses the following terms:
             Layer
         
         
    - Images are composed of layers. - Image layer is a general term which may be used to refer to one or both of the following: - -
      -
    1. The metadata for the layer, described in the JSON format.
    2. -
    3. The filesystem changes described by a layer.
    4. -
    - - To refer to the former you may use the term Layer JSON or Layer Metadata. - To refer to the latter you may use the term Image Filesystem Changeset or Image Diff. + Images are composed of layers. Each layer is a set of filesystem + changes. 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.
    Image JSON
    - Each layer 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 entry point, default arguments, CPU/memory shares, networking, and volumes. + 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 entry point, 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.
    Image Filesystem Changeset
    - Each layer has an archive of the files which have been added, changed, or deleted relative to its parent 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. + Each layer has an archive of the files which have been added, changed, + or deleted relative to its parent 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 ID + Layer DiffID
    - Each layer is given an ID upon its creation. - It is represented as a hexadecimal encoding of 256 bits, e.g., a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9. - Image IDs should be sufficiently random so as to be globally unique. - 32 bytes read from /dev/urandom is sufficient for all practical purposes. - Alternatively, an image ID may be derived as a cryptographic hash of image contents as the result is considered indistinguishable from random. - The choice is left up to implementors. + Layers are referenced by cryptographic hashes of their serialized + representation. This is a SHA256 digest over the tar archive used to + transport the layer, represented as a hexadecimal encoding of 256 bits, 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 + that the digest used as the layer ID is taken over an uncompressed + version of the tar.
    - Image Parent + Layer ChainID
    - Most layer metadata structs contain a parent field which refers to the Image from which another directly descends. - An image contains a separate JSON metadata file and set of changes relative to the filesystem of its parent image. - Image Ancestor and Image Descendant are also common terms. + 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)).
    - Image Checksum + ImageID
    - The checksum is a cryptographic digest of the artifact. + 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.
    Tag
    - A tag serves to map a descriptive, user-given name to any single image ID. - An image name suffix (the name component after :) is often referred to as a tag as well, though it strictly refers to the full name of an image. - Acceptable values for a tag suffix are implementation specific, but they SHOULD be limited to the set of alphanumeric characters [a-zA-z0-9], punctuation characters [._-], and MUST NOT contain a : character. + A tag serves to map a descriptive, user-given name to any single image + ID. Tag values are limited to the set of characters + [a-zA-Z_0-9].
    Repository
    - A collection of tags grouped under a common prefix (the name component before :). - For example, in an image tagged with the name my-app:3.1.4, my-app is the Repository component of the name. - Acceptable values for repository name are implementation specific, but they SHOULD be limited to the set of alphanumeric characters [a-zA-z0-9], and punctuation characters [._-], however it MAY contain additional / and : characters for organizational purposes, with the last : character being interpreted dividing the repository component of the name from the tag suffix component. + A collection of tags grouped under a common prefix (the name component + before :). For example, in an image tagged with the name + my-app:3.1.4, my-app is the Repository + component of the name. A repository name is made up of slash-separated + name components, optionally prefixed by a DNS hostname. The hostname + must follow comply with standard DNS rules, but may not contain + _ characters. If a hostname is present, it may optionally + be followed by a port number in the format :8080. + Name components may contain lowercase characters, digits, and + separators. A separator is defined as a period, one or two underscores, + or one or more dashes. A name component may not start or end with + a separator.
    @@ -85,14 +114,10 @@ Here is an example image JSON file: ``` { - "id": "a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9", - "parent": "c6e3cedcda2e3982a1a6760e178355e8e65f7b80e4e5248743fa3549d284e024", - "checksum": "tarsum.v1+sha256:e58fcf7418d2390dec8e8fb69d88c06ec07039d651fedc3aa72af9972e7d046b", - "created": "2014-10-13T21:19:18.674353812Z", + "created": "2015-10-31T22:22:56.015925234Z", "author": "Alyssa P. Hacker <alyspdev@example.com>", "architecture": "amd64", "os": "linux", - "Size": 271828, "config": { "User": "alice", "Memory": 2048, @@ -119,35 +144,40 @@ Here is an example image JSON file: "/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 that image JSON files produced by Docker don't contain formatting +whitespace. It has been added to this example for clarity. + ### Image JSON Field Descriptions
    -
    - id string -
    -
    - Randomly generated, 256-bit, hexadecimal encoded. - Uniquely identifies the image. -
    -
    - parent string -
    -
    - ID of the parent image. - If there is no parent image then this field should be omitted. - A collection of images may share many of the same ancestor layers. - This organizational structure is strictly a tree with any one layer having either no parent or a single parent and zero or more descendant layers. - Cycles are not allowed and implementations should be careful to avoid creating them or iterating through a cycle indefinitely. -
    created string
    - ISO-8601 formatted combined date and time at which the image was created. + ISO-8601 formatted combined date and time at which the image was + created.
    author string @@ -160,14 +190,15 @@ Here is an example image JSON file: architecture string
    - The CPU architecture which the binaries in this image are built to run on. - Possible values include: + 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. + More values may be supported in the future and any of these may or may + not be supported by a given container runtime implementation.
    os string @@ -183,24 +214,14 @@ Here is an example image JSON file: More values may be supported in the future and any of these may or may not be supported by a given container runtime implementation.
    -
    - checksum string -
    -
    - Image Checksum of the filesystem changeset associated with the image layer. -
    -
    - Size integer -
    -
    - The size in bytes of the filesystem changeset associated with the image layer. -
    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. + 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

    @@ -209,10 +230,9 @@ Here is an example image JSON file: 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. -

    +

    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:

    @@ -225,38 +245,44 @@ Here is an example image JSON file:
  • 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. -

    +

    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. + 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. + 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. + 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: + 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": {},
    @@ -276,42 +302,52 @@ Here is an example image JSON file:
                             "port"
                         
                     
    -                with the default protocol being "tcp" if not specified.
    +                with the default protocol being "tcp" if not
    +                specified.
     
    -                These values act as defaults and are merged with any specified when creating a container.
    +                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. + 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. + 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.
    Cmd array of strings
    - Default arguments to the entry point 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. + Default arguments to the entry point 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.
    Volumes struct
    - A set of directories which should be created as data volumes in a container running this image. + A set of directories which should be created as data volumes in + a container running this image.

    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: + 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/": {},
    @@ -321,22 +357,103 @@ Here is an example image JSON file:
                     WorkingDir string
                 
                 
    - Sets the current working directory of the entry point process in the container. - This value acts as a default and is replaced by a working directory specified when creating a container. + Sets the current working directory of the entry point 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 is usually set to layers. There is + also a Windows-specific value layers+base that allows + a base layer to be specified in a field of rootfs + called base_layer. +
    • +
    • + 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. +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. ## Creating an Image Filesystem Changeset An example of creating an Image Filesystem Changeset follows. -An image root filesystem is first created as an empty directory named with the ID of the image being created. -Here is the initial empty directory structure for the changeset for an image with ID `c3167915dc9d` ([real IDs are much longer](#id_desc), but this example use a truncated one here for brevity. -Implementations need not name the rootfs directory in this way but it may be convenient for keeping record of a large number of image layers.): +An image root filesystem is first created as an empty directory. Here is the +initial empty directory structure for the a changeset using the +randomly-generated directory name `c3167915dc9d` ([actual layer DiffIDs are +generated based on the content](#id_desc)). ``` c3167915dc9d/ @@ -362,13 +479,11 @@ bin/my-app-binary bin/my-app-tools ``` -The digest checksum for the archive file is then computed and placed in the JSON metadata along with the execution parameters. - To make changes to the filesystem of this container image, create a new -directory named with a new ID, such as `f60c56784b83`, and initialize it with -a snapshot of the parent image's root filesystem, so that the directory is -identical to that of `c3167915dc9d`. -NOTE: a copy-on-write or union filesystem can make this very efficient: +directory, such as `f60c56784b83`, and initialize it with a snapshot of the +parent image's root filesystem, so that the directory is identical to that +of `c3167915dc9d`. NOTE: a copy-on-write or union filesystem can make this very +efficient: ``` f60c56784b83/ @@ -379,9 +494,10 @@ f60c56784b83/ my-app-tools ``` -This example change is going add a configuration directory at `/etc/my-app.d` which contains a default config file. -There's also a change to the `my-app-tools` binary to handle the config layout change. -The `f60c56784b83` directory then looks like this: +This example change is going add a configuration directory at `/etc/my-app.d` +which contains a default config file. There's also a change to the +`my-app-tools` binary to handle the config layout change. The `f60c56784b83` +directory then looks like this: ``` f60c56784b83/ @@ -393,10 +509,13 @@ f60c56784b83/ my-app-tools ``` -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. -Before committing this directory to a changeset, because it has a parent image, it is first compared with the directory tree of the parent snapshot, `f60c56784b83`, looking for files and directories that have been added, modified, or removed. -The following changeset is found: +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. Before committing this directory to a +changeset, because it has a parent image, it is first compared with the +directory tree of the parent snapshot, `f60c56784b83`, looking for files and +directories that have been added, modified, or removed. The following changeset +is found: ``` Added: /etc/my-app.d/default.cfg @@ -407,10 +526,11 @@ Deleted: /etc/my-app-config A Tar Archive is then created which contains *only* this changeset: The added and modified files and directories in their entirety, and for each deleted item an entry for an empty file at the same location but with the basename of the -deleted file or directory prefixed with `.wh.`. -The filenames prefixed with `.wh.` are known as "whiteout" files. -NOTE: For this reason, it is not possible to create an image root filesystem which contains a file or directory with a name beginning with `.wh.`. -The resulting Tar archive for `f60c56784b83` has the following entries: +deleted file or directory prefixed with `.wh.`. The filenames prefixed with +`.wh.` are known as "whiteout" files. NOTE: For this reason, it is not possible +to create an image root filesystem which contains a file or directory with a +name beginning with `.wh.`. The resulting Tar archive for `f60c56784b83` has +the following entries: ``` /etc/my-app.d/default.cfg @@ -418,45 +538,49 @@ The resulting Tar archive for `f60c56784b83` has the following entries: /etc/.wh.my-app-config ``` -Any given image is likely to be composed of several of these Image Filesystem Changeset tar archives. +Any given image is likely to be composed of several of these Image Filesystem +Changeset tar archives. ## Combined Image JSON + Filesystem Changeset Format -There is also a format for a single archive which contains complete information about an image, including: +There is also a format for a single archive which contains complete information +about an image, including: - repository names/tags - - all image layer JSON files + - image configuration JSON file - all tar archives of each layer filesystem changesets -For example, here's what the full archive of `library/busybox` is (displayed in `tree` format): +For example, here's what the full archive of `library/busybox` is (displayed in +`tree` format): ``` . -├── 5785b62b697b99a5af6cd5d0aabc804d5748abbb6d3d07da5d1d3795f2dcc83e +├── 47bcc53f74dc94b1920f0b34f6036096526296767650f223433fe65c35f149eb.json +├── 5f29f704785248ddb9d06b90a11b5ea36c534865e9035e4022bb2e71d4ecbb9a │   ├── VERSION │   ├── json │   └── layer.tar -├── a7b8b41220991bfc754d7ad445ad27b7f272ab8b4a2c175b9512b97471d02a8a -│   ├── VERSION -│   ├── json -│   └── layer.tar -├── a936027c5ca8bf8f517923169a233e391cbb38469a75de8383b5228dc2d26ceb -│   ├── VERSION -│   ├── json -│   └── layer.tar -├── f60c56784b832dd990022afc120b8136ab3da9528094752ae13fe63a2d28dc8c +├── a65da33792c5187473faa80fa3e1b975acba06712852d1dea860692ccddf3198 │   ├── VERSION │   ├── json │   └── layer.tar +├── manifest.json └── repositories ``` -There are one or more directories named with the ID for each layer in a full image. -Each of these directories contains 3 files: +There are one or more directories named with the ChainID for each layer in a +full image. Each of these directories contains 3 files: * `VERSION` - The schema version of the `json` file - * `json` - The JSON metadata for an image layer - * `layer.tar` - The Tar archive of the filesystem changeset for an image layer. + * `json` - The legacy JSON metadata for an image layer. In this version of + the image specification, layers don't have JSON metadata, but in + [version 1](v1.md), they did. A file is created for each layer in the + v1 format for backward compatiblity. + * `layer.tar` - The Tar archive of the filesystem changeset for an image + layer. + +Note that this directory layout is only important for backward compatibility. +Current implementations use the paths specified in `manifest.json`. The content of the `VERSION` files is simply the semantic version of the JSON metadata schema: @@ -465,24 +589,53 @@ metadata schema: 1.0 ``` -And the `repositories` file is another JSON file which describes names/tags: +The `repositories` file is another JSON file which describes names/tags: ``` { "busybox":{ - "latest":"5785b62b697b99a5af6cd5d0aabc804d5748abbb6d3d07da5d1d3795f2dcc83e" + "latest":"5f29f704785248ddb9d06b90a11b5ea36c534865e9035e4022bb2e71d4ecbb9a" } } ``` -Every key in this object is the name of a repository, and maps to a collection of tag suffixes. Each tag maps to the ID of the image represented by that tag. +Every key in this object is the name of a repository, and maps to a collection +of tag suffixes. Each tag maps to the ID of the image represented by that tag. +This file is only used for backwards compatibility. Current implementations use +the `manifest.json` file instead. + +The `manifest.json` file provides the image JSON for the top-level image, and +optionally for parent images that this image was derived from. It consists of +an array of metadata entries: + +``` +[ + { + "Config": "47bcc53f74dc94b1920f0b34f6036096526296767650f223433fe65c35f149eb.json", + "RepoTags": ["busybox:latest"], + "Layers": [ + "a65da33792c5187473faa80fa3e1b975acba06712852d1dea860692ccddf3198/layer.tar", + "5f29f704785248ddb9d06b90a11b5ea36c534865e9035e4022bb2e71d4ecbb9a/layer.tar" + ] + } +] +``` + +There is an entry in the array for each image. + +The `Config` field references another file in the tar which includes the image +JSON for this image. + +The `RepoTags` field lists references pointing to this image. -## Loading an Image Filesystem Changeset +The `Layers` field points to the filesystem changeset tars. -Unpacking a bundle of image layer JSON files and their corresponding filesystem changesets can be done using a series of steps: +An optional `Parent` field references the imageID of the parent image. This +parent must be part of the same `manifest.json` file. -1. Follow the parent IDs of image layers to find the root ancestor (an image with no parent ID specified). -2. For every image layer, in order from root ancestor and descending down, extract the contents of that layer's filesystem changeset archive into a directory which will be used as the root of a container filesystem. +This file shouldn't be confused with the distribution manifest, used to push +and pull images. - - Extract all contents of each archive. - - Walk the directory tree once more, removing any files with the prefix `.wh.` and the corresponding file or directory named without this prefix. +Generally, implementations that support this version of the spec will use +the `manifest.json` file if available, and older implementations will use the +legacy `*/json` files and `repositories`. From 314a439b1ec31092fa6a52f1474a093c7bb86360 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Thu, 28 Apr 2016 13:42:42 -0400 Subject: [PATCH 033/245] serialization: one line per sentence Signed-off-by: Vincent Batts --- serialization.md | 309 ++++++++++++++++++----------------------------- 1 file changed, 116 insertions(+), 193 deletions(-) diff --git a/serialization.md b/serialization.md index deaff6f..dd5151a 100644 --- a/serialization.md +++ b/serialization.md @@ -1,10 +1,7 @@ # Docker Image Specification v1.1.0 -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 format of these filesystem changes and corresponding -parameters and describes how to create and use them for use with a container -runtime and execution tool. +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 format of these filesystem changes and corresponding parameters and describes how to create and use them for use with a container runtime and execution tool. This version of the image specification was adopted starting in Docker 1.10. @@ -17,54 +14,42 @@ This specification uses the following terms: Layer
    - Images are composed of layers. Each layer is a set of filesystem - changes. 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. + Images are composed of layers. + Each layer is a set of filesystem changes. + 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.
    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 entry point, 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. + 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 entry point, 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.
    Image Filesystem Changeset
    - Each layer has an archive of the files which have been added, changed, - or deleted relative to its parent 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. + Each layer has an archive of the files which have been added, changed, or deleted relative to its parent 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.
    Layer DiffID
    - Layers are referenced by cryptographic hashes of their serialized - representation. This is a SHA256 digest over the tar archive used to - transport the layer, represented as a hexadecimal encoding of 256 bits, 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 - that the digest used as the layer ID is taken over an uncompressed - version of the tar. + Layers are referenced by cryptographic hashes of their serialized representation. + This is a SHA256 digest over the tar archive used to transport the layer, represented as a hexadecimal encoding of 256 bits, 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 that the digest used as the layer ID is taken over an uncompressed version of the tar.
    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 + 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: @@ -74,37 +59,29 @@ This specification uses the following terms: 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. + 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.
    Tag
    - A tag serves to map a descriptive, user-given name to any single image - ID. Tag values are limited to the set of characters - [a-zA-Z_0-9]. + A tag serves to map a descriptive, user-given name to any single image ID. + Tag values are limited to the set of characters [a-zA-Z_0-9].
    Repository
    - A collection of tags grouped under a common prefix (the name component - before :). For example, in an image tagged with the name - my-app:3.1.4, my-app is the Repository - component of the name. A repository name is made up of slash-separated - name components, optionally prefixed by a DNS hostname. The hostname - must follow comply with standard DNS rules, but may not contain - _ characters. If a hostname is present, it may optionally - be followed by a port number in the format :8080. - Name components may contain lowercase characters, digits, and - separators. A separator is defined as a period, one or two underscores, - or one or more dashes. A name component may not start or end with - a separator. + A collection of tags grouped under a common prefix (the name component before :). + For example, in an image tagged with the name my-app:3.1.4, my-app is the Repository component of the name. + A repository name is made up of slash-separated name components, optionally prefixed by a DNS hostname. + The hostname must follow comply with standard DNS rules, but may not contain _ characters. + If a hostname is present, it may optionally be followed by a port number in the format :8080. + Name components may contain lowercase characters, digits, and separators. + A separator is defined as a period, one or two underscores, or one or more dashes. + A name component may not start or end with a separator.
    @@ -166,8 +143,8 @@ Here is an example image JSON file: } ``` -Note that image JSON files produced by Docker don't contain formatting -whitespace. It has been added to this example for clarity. +Note that image JSON files produced by Docker don't contain formatting whitespace. +It has been added to this example for clarity. ### Image JSON Field Descriptions @@ -176,29 +153,26 @@ whitespace. It has been added to this example for clarity. created string
    - ISO-8601 formatted combined date and time at which the image was - created. + 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. + 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: + 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. + More values may be supported in the future and any of these may or may not be supported by a given container runtime implementation.
    os string @@ -211,17 +185,14 @@ whitespace. It has been added to this example for clarity.
  • 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. + More values may be supported in the future and any of these may or may not be supported by a given container runtime implementation.
    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. + 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

    @@ -230,9 +201,10 @@ whitespace. It has been added to this example for clarity. 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.

    +

    + 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:

    @@ -245,44 +217,38 @@ whitespace. It has been added to this example for clarity.
  • 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.

    +

    + 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. + 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. + 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. + 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: + 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": {},
    @@ -302,52 +268,42 @@ whitespace. It has been added to this example for clarity.
                             "port"
                         
                     
    -                with the default protocol being "tcp" if not
    -                specified.
    +                with the default protocol being "tcp" if not specified.
     
    -                These values act as defaults and are merged with any specified
    -                when creating a container.
    +                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. + 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. + 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.
    Cmd array of strings
    - Default arguments to the entry point 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. + Default arguments to the entry point 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.
    Volumes struct
    - A set of directories which should be created as data volumes in - a container running this image. + A set of directories which should be created as data volumes in a container running this image.

    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: + 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/": {},
    @@ -357,9 +313,8 @@ whitespace. It has been added to this example for clarity.
                     WorkingDir string
                 
                 
    - Sets the current working directory of the entry point process - in the container. This value acts as a default and is replaced - by a working directory specified when creating a container. + Sets the current working directory of the entry point process in the container. + This value acts as a default and is replaced by a working directory specified when creating a container.
    @@ -367,16 +322,14 @@ whitespace. It has been added to this example for clarity. 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. + 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 is usually set to layers. There is - also a Windows-specific value layers+base that allows - a base layer to be specified in a field of rootfs - called base_layer. + type is usually set to layers. + There is also a Windows-specific value layers+base that allows a base layer to be specified in a field of rootfs called base_layer.
    • diff_ids is an array of layer content hashes (DiffIDs), in order from bottom-most to top-most. @@ -399,9 +352,9 @@ whitespace. It has been added to this example for clarity. 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. + 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.
      • @@ -418,11 +371,8 @@ whitespace. It has been added to this example for clarity. 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). + 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).
      @@ -442,18 +392,14 @@ Here is an example history section:
      -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. +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. ## Creating an Image Filesystem Changeset An example of creating an Image Filesystem Changeset follows. -An image root filesystem is first created as an empty directory. Here is the -initial empty directory structure for the a changeset using the -randomly-generated directory name `c3167915dc9d` ([actual layer DiffIDs are -generated based on the content](#id_desc)). +An image root filesystem is first created as an empty directory. +Here is the initial empty directory structure for the a changeset using the randomly-generated directory name `c3167915dc9d` ([actual layer DiffIDs are generated based on the content](#id_desc)). ``` c3167915dc9d/ @@ -470,8 +416,7 @@ c3167915dc9d/ my-app-tools ``` -The `c3167915dc9d` directory is then committed as a plain Tar archive with -entries for the following files: +The `c3167915dc9d` directory is then committed as a plain Tar archive with entries for the following files: ``` etc/my-app-config @@ -479,11 +424,8 @@ bin/my-app-binary bin/my-app-tools ``` -To make changes to the filesystem of this container image, create a new -directory, such as `f60c56784b83`, and initialize it with a snapshot of the -parent image's root filesystem, so that the directory is identical to that -of `c3167915dc9d`. NOTE: a copy-on-write or union filesystem can make this very -efficient: +To make changes to the filesystem of this container image, create a new directory, such as `f60c56784b83`, and initialize it with a snapshot of the parent image's root filesystem, so that the directory is identical to that of `c3167915dc9d`. +NOTE: a copy-on-write or union filesystem can make this very efficient: ``` f60c56784b83/ @@ -494,10 +436,9 @@ f60c56784b83/ my-app-tools ``` -This example change is going add a configuration directory at `/etc/my-app.d` -which contains a default config file. There's also a change to the -`my-app-tools` binary to handle the config layout change. The `f60c56784b83` -directory then looks like this: +This example change is going add a configuration directory at `/etc/my-app.d` which contains a default config file. +There's also a change to the `my-app-tools` binary to handle the config layout change. +The `f60c56784b83` directory then looks like this: ``` f60c56784b83/ @@ -509,13 +450,10 @@ f60c56784b83/ my-app-tools ``` -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. Before committing this directory to a -changeset, because it has a parent image, it is first compared with the -directory tree of the parent snapshot, `f60c56784b83`, looking for files and -directories that have been added, modified, or removed. The following changeset -is found: +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. +Before committing this directory to a changeset, because it has a parent image, it is first compared with the directory tree of the parent snapshot, `f60c56784b83`, looking for files and directories that have been added, modified, or removed. +The following changeset is found: ``` Added: /etc/my-app.d/default.cfg @@ -523,14 +461,10 @@ Modified: /bin/my-app-tools Deleted: /etc/my-app-config ``` -A Tar Archive is then created which contains *only* this changeset: The added -and modified files and directories in their entirety, and for each deleted item -an entry for an empty file at the same location but with the basename of the -deleted file or directory prefixed with `.wh.`. The filenames prefixed with -`.wh.` are known as "whiteout" files. NOTE: For this reason, it is not possible -to create an image root filesystem which contains a file or directory with a -name beginning with `.wh.`. The resulting Tar archive for `f60c56784b83` has -the following entries: +A Tar Archive is then created which contains *only* this changeset: The added and modified files and directories in their entirety, and for each deleted item an entry for an empty file at the same location but with the basename of the deleted file or directory prefixed with `.wh.`. +The filenames prefixed with `.wh.` are known as "whiteout" files. +NOTE: For this reason, it is not possible to create an image root filesystem which contains a file or directory with a name beginning with `.wh.`. +The resulting Tar archive for `f60c56784b83` has the following entries: ``` /etc/my-app.d/default.cfg @@ -538,20 +472,17 @@ the following entries: /etc/.wh.my-app-config ``` -Any given image is likely to be composed of several of these Image Filesystem -Changeset tar archives. +Any given image is likely to be composed of several of these Image Filesystem Changeset tar archives. ## Combined Image JSON + Filesystem Changeset Format -There is also a format for a single archive which contains complete information -about an image, including: +There is also a format for a single archive which contains complete information about an image, including: - repository names/tags - image configuration JSON file - all tar archives of each layer filesystem changesets -For example, here's what the full archive of `library/busybox` is (displayed in -`tree` format): +For example, here's what the full archive of `library/busybox` is (displayed in `tree` format): ``` . @@ -568,22 +499,19 @@ For example, here's what the full archive of `library/busybox` is (displayed in └── repositories ``` -There are one or more directories named with the ChainID for each layer in a -full image. Each of these directories contains 3 files: +There are one or more directories named with the ChainID for each layer in a full image. +Each of these directories contains 3 files: * `VERSION` - The schema version of the `json` file - * `json` - The legacy JSON metadata for an image layer. In this version of - the image specification, layers don't have JSON metadata, but in - [version 1](v1.md), they did. A file is created for each layer in the - v1 format for backward compatiblity. - * `layer.tar` - The Tar archive of the filesystem changeset for an image - layer. + * `json` - The legacy JSON metadata for an image layer. + In this version of the image specification, layers don't have JSON metadata, but in [version 1](v1.md), they did. + A file is created for each layer in the v1 format for backward compatiblity. + * `layer.tar` - The Tar archive of the filesystem changeset for an image layer. Note that this directory layout is only important for backward compatibility. Current implementations use the paths specified in `manifest.json`. -The content of the `VERSION` files is simply the semantic version of the JSON -metadata schema: +The content of the `VERSION` files is simply the semantic version of the JSON metadata schema: ``` 1.0 @@ -599,14 +527,13 @@ The `repositories` file is another JSON file which describes names/tags: } ``` -Every key in this object is the name of a repository, and maps to a collection -of tag suffixes. Each tag maps to the ID of the image represented by that tag. -This file is only used for backwards compatibility. Current implementations use -the `manifest.json` file instead. +Every key in this object is the name of a repository, and maps to a collection of tag suffixes. +Each tag maps to the ID of the image represented by that tag. +This file is only used for backwards compatibility. +Current implementations use the `manifest.json` file instead. -The `manifest.json` file provides the image JSON for the top-level image, and -optionally for parent images that this image was derived from. It consists of -an array of metadata entries: +The `manifest.json` file provides the image JSON for the top-level image, and optionally for parent images that this image was derived from. +It consists of an array of metadata entries: ``` [ @@ -623,19 +550,15 @@ an array of metadata entries: There is an entry in the array for each image. -The `Config` field references another file in the tar which includes the image -JSON for this image. +The `Config` field references another file in the tar which includes the image JSON for this image. The `RepoTags` field lists references pointing to this image. The `Layers` field points to the filesystem changeset tars. -An optional `Parent` field references the imageID of the parent image. This -parent must be part of the same `manifest.json` file. +An optional `Parent` field references the imageID of the parent image. +This parent must be part of the same `manifest.json` file. -This file shouldn't be confused with the distribution manifest, used to push -and pull images. +This file shouldn't be confused with the distribution manifest, used to push and pull images. -Generally, implementations that support this version of the spec will use -the `manifest.json` file if available, and older implementations will use the -legacy `*/json` files and `repositories`. +Generally, implementations that support this version of the spec will use the `manifest.json` file if available, and older implementations will use the legacy `*/json` files and `repositories`. From 78c7ff7fde7d75dd9b6b2ef059899a92f8bb7e17 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 27 Apr 2016 11:44:55 -0400 Subject: [PATCH 034/245] manifest: add annotations This introduces OPTIONAL annotations, which are consistent with the annotations in the runtime-spec. While the spec does not exclude use of arbitrary fields, this OPTIONAL property gives a guided place for manifest authors to isolate their arbitrary metadata. Signed-off-by: Vincent Batts --- manifest.md | 36 +++++++++++++++++++++++++++++-- schema/defs-image.json | 11 ++++++++++ schema/image-manifest-schema.json | 4 ++++ schema/manifest-list-schema.json | 4 ++++ 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/manifest.md b/manifest.md index 04f9094..b0c94f3 100644 --- a/manifest.md +++ b/manifest.md @@ -81,6 +81,18 @@ A client will distinguish a manifest list from an image manifest based on the Co This OPTIONAL property specifies an array of strings, each specifying a mandatory CPU feature (for example `sse4` or `aes`). +- **`annotations`** *string-string hashmap* + + This OPTIONAL property contains arbitrary metadata for the manifest list. + Annotations is a key-value, unordered hashmap. + Keys are unique, and best practice is to namespace the keys. + Common annotation keys include: + * **created** date on which the image was built (string, timestamps type) + * **authors** contact details of the people or organization responsible for the image (freeform string) + * **homepage** URL to find more information on the image (string, must be a URL with scheme HTTP or HTTPS) + * **documentation** URL to get documentation on the image (string, must be a URL with scheme HTTP or HTTPS) + + ## Example Manifest List *Example showing a simple manifest list pointing to image manifests for two platforms:* @@ -110,7 +122,11 @@ A client will distinguish a manifest list from an image manifest based on the Co ] } } - ] + ], + "annotations": { + "key1": "value1", + "key2": "value2", + } } ``` @@ -174,6 +190,18 @@ The image manifest provides a configuration and a set of layers for a container The digest of the content, as defined by the [Registry V2 HTTP API Specificiation](https://docs.docker.com/registry/spec/api/#digest-parameter). +- **`annotations`** *hashmap* + + This OPTIONAL property contains arbitrary metadata for the manifest list. + Annotations is a key-value, unordered hashmap. + Keys are unique, and best practice is to namespace the keys. + Common annotation keys include: + * **created** date on which the image was built (string, timestamps type) + * **authors** contact details of the people or organization responsible for the image (freeform string) + * **homepage** URL to find more information on the image (string, must be a URL with scheme HTTP or HTTPS) + * **documentation** URL to get documentation on the image (string, must be a URL with scheme HTTP or HTTPS) + + ## Example Image Manifest *Example showing an image manifest:* @@ -202,7 +230,11 @@ The image manifest provides a configuration and a set of layers for a container "size": 73109, "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736" } - ] + ], + "annotations": { + "key1": "value1", + "key2": "value2", + } } ``` diff --git a/schema/defs-image.json b/schema/defs-image.json index e2e221d..a26d1c9 100644 --- a/schema/defs-image.json +++ b/schema/defs-image.json @@ -94,6 +94,17 @@ } } } + }, + "annotations": { + "id": "https://opencontainers.org/schema/image/annotations", + "oneOf": [ + { + "$ref": "defs.json#/definitions/mapStringString" + }, + { + "type": "null" + } + ] } } } diff --git a/schema/image-manifest-schema.json b/schema/image-manifest-schema.json index 7659d53..197eeb1 100644 --- a/schema/image-manifest-schema.json +++ b/schema/image-manifest-schema.json @@ -21,6 +21,10 @@ "items": { "$ref": "defs-image.json#/definitions/descriptor" } + }, + "annotations": { + "id": "https://opencontainers.org/schema/image/manifest-list/annotations", + "$ref": "defs-image.json#/definitions/annotations" } }, "required": [ diff --git a/schema/manifest-list-schema.json b/schema/manifest-list-schema.json index f9ddd62..261db7d 100644 --- a/schema/manifest-list-schema.json +++ b/schema/manifest-list-schema.json @@ -18,6 +18,10 @@ "items": { "$ref": "defs-image.json#/definitions/manifestDescriptor" } + }, + "annotations": { + "id": "https://opencontainers.org/schema/image/manifest-list/annotations", + "$ref": "defs-image.json#/definitions/annotations" } }, "required": [ From facff522bf0d0b1fb2bcbf5696ddffe506fd0424 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 29 Apr 2016 16:34:23 -0700 Subject: [PATCH 035/245] manifest: Remove trailing comma from annotations.key2 Typos from 78c7ff7f (manifest: add annotations, 2016-04-27, #44). Signed-off-by: W. Trevor King --- manifest.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.md b/manifest.md index b0c94f3..2563a8a 100644 --- a/manifest.md +++ b/manifest.md @@ -125,7 +125,7 @@ A client will distinguish a manifest list from an image manifest based on the Co ], "annotations": { "key1": "value1", - "key2": "value2", + "key2": "value2" } } ``` @@ -233,7 +233,7 @@ The image manifest provides a configuration and a set of layers for a container ], "annotations": { "key1": "value1", - "key2": "value2", + "key2": "value2" } } ``` From 19011ba4cc38e1d2bb5820d1284a385081e34060 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Fri, 29 Apr 2016 13:55:19 -0400 Subject: [PATCH 036/245] docs: make target for PDF and HTML This adds a `docs` make target for producing a PDF and HTML. The output of `tree` in `./serialization.md` does not jive with pandoc. Not sure what better visual layout of a directory tree we can use. Fixes #16 Signed-off-by: Vincent Batts --- .gitignore | 2 ++ Makefile | 38 ++++++++++++++++++++++++++++++++++++++ README.md | 2 +- serialization.md | 28 +++++++++++++++++----------- 4 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9179330 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +oci-validate-examples +code-of-conduct.md diff --git a/Makefile b/Makefile index 36c126c..fe21ecf 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,14 @@ +DOCKER ?= $(shell which docker) +# 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 \ + manifest.md \ + serialization.md + default: help help: @@ -10,6 +20,34 @@ help: fmt: for i in *.json ; do jq --indent 2 -M . "$${i}" > xx && cat xx > "$${i}" && rm xx ; done +docs: output/docs.pdf output/docs.html +.PHONY: docs + +output/docs.pdf: $(DOC_FILES) + @mkdir -p output/ && \ + $(DOCKER) run \ + -it \ + --rm \ + -v $(shell pwd)/:/input/:ro \ + -v $(shell pwd)/output/:/output/ \ + -u $(shell id -u) \ + vbatts/pandoc -f markdown_github -t latex -o /output/docs.pdf $(patsubst %,/input/%,$(DOC_FILES)) && \ + ls -sh $(shell readlink -f $@) + +output/docs.html: $(DOC_FILES) + @mkdir -p output/ && \ + $(DOCKER) run \ + -it \ + --rm \ + -v $(shell pwd)/:/input/:ro \ + -v $(shell pwd)/output/:/output/ \ + -u $(shell id -u) \ + vbatts/pandoc -f markdown_github -t html5 -o /output/docs.html $(patsubst %,/input/%,$(DOC_FILES)) && \ + ls -sh $(shell readlink -f $@) + +code-of-conduct.md: + curl -o $@ https://raw.githubusercontent.com/opencontainers/tob/d2f9d68c1332870e40693fe077d311e0742bc73d/code-of-conduct.md + .PHONY: validate-examples validate-examples: oci-validate-examples ./oci-validate-examples < manifest.md diff --git a/README.md b/README.md index 2a8f749..3a7a4ed 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ The specification and code is licensed under the Apache 2.0 license found in the ## Code of Conduct -Participation in the OpenContainers community is governed by [OpenContainer's Code of Conduct](https://github.com/opencontainers/tob/blob/master/code-of-conduct.md). +Participation in the OpenContainers community is governed by [OpenContainer's Code of Conduct](https://github.com/opencontainers/tob/blob/d2f9d68c1332870e40693fe077d311e0742bc73d/code-of-conduct.md). ## Discuss your design diff --git a/serialization.md b/serialization.md index dd5151a..6c98763 100644 --- a/serialization.md +++ b/serialization.md @@ -486,17 +486,23 @@ For example, here's what the full archive of `library/busybox` is (displayed in ``` . -├── 47bcc53f74dc94b1920f0b34f6036096526296767650f223433fe65c35f149eb.json -├── 5f29f704785248ddb9d06b90a11b5ea36c534865e9035e4022bb2e71d4ecbb9a -│   ├── VERSION -│   ├── json -│   └── layer.tar -├── a65da33792c5187473faa80fa3e1b975acba06712852d1dea860692ccddf3198 -│   ├── VERSION -│   ├── json -│   └── layer.tar -├── manifest.json -└── repositories +|-- 5785b62b697b99a5af6cd5d0aabc804d5748abbb6d3d07da5d1d3795f2dcc83e +| |-- VERSION +| |-- json +| |-- layer.tar +|-- a7b8b41220991bfc754d7ad445ad27b7f272ab8b4a2c175b9512b97471d02a8a +| |-- VERSION +| |-- json +| |-- layer.tar +|-- a936027c5ca8bf8f517923169a233e391cbb38469a75de8383b5228dc2d26ceb +| |-- VERSION +| |-- json +| |-- layer.tar +|-- f60c56784b832dd990022afc120b8136ab3da9528094752ae13fe63a2d28dc8c +| |-- VERSION +| |-- json +| |-- layer.tar +|-- repositories ``` There are one or more directories named with the ChainID for each layer in a full image. From 8391154e7e1ed4f9dc0578cc9030cb1fe7043a80 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Fri, 29 Apr 2016 14:49:52 -0700 Subject: [PATCH 037/245] manifest: try to clear up the langauge on layers - The meaning of "base" isn't terribly clear. - In OCI "version 1" isn't useful. Signed-off-by: Brandon Philips --- manifest.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manifest.md b/manifest.md index 2563a8a..75c2755 100644 --- a/manifest.md +++ b/manifest.md @@ -171,7 +171,8 @@ The image manifest provides a configuration and a set of layers for a container - **`layers`** *array* - The layer list is ordered starting from the base image (opposite order of schema1). + The layer list has the base image at index 0. + The algorithm to create the final unpacked filesystem layout is to first unpack the layer at index 0, then index 1, and so on. Fields of an item in the layers list are: From 14449dcf2f329748b5495c82031889b169840423 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 2 May 2016 11:57:34 -0400 Subject: [PATCH 038/245] serialization: fixing layout regression Caused by https://github.com/opencontainers/image-spec/pull/49 Signed-off-by: Vincent Batts --- serialization.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/serialization.md b/serialization.md index 6c98763..5a7226a 100644 --- a/serialization.md +++ b/serialization.md @@ -486,6 +486,7 @@ For example, here's what the full archive of `library/busybox` is (displayed in ``` . +|-- 88ecdbb5a908bd1bdbb921110a6134d6916f962680da0c4628102ff0691b38b3.json |-- 5785b62b697b99a5af6cd5d0aabc804d5748abbb6d3d07da5d1d3795f2dcc83e | |-- VERSION | |-- json @@ -502,6 +503,7 @@ For example, here's what the full archive of `library/busybox` is (displayed in | |-- VERSION | |-- json | |-- layer.tar +|-- manifest.json |-- repositories ``` From ad25882bba9bbf3b75e2190e087a214037178e47 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 2 May 2016 12:58:07 -0400 Subject: [PATCH 039/245] manifest: fixing mediatype reference Fixes https://github.com/opencontainers/image-spec/issues/53 Reported-by: Sergiusz Urbaniak Signed-off-by: Vincent Batts --- manifest.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/manifest.md b/manifest.md index 75c2755..07683ca 100644 --- a/manifest.md +++ b/manifest.md @@ -157,7 +157,7 @@ The image manifest provides a configuration and a set of layers for a container - **`mediaType`** *string* This REQUIRED property contains the MIME type of the referenced object. - (i.e. `application/vnd.oci.image.serialization.v1+json`) + (i.e. `application/vnd.oci.image.serialization.config.v1+json`) - **`size`** *int* @@ -179,7 +179,7 @@ The image manifest provides a configuration and a set of layers for a container - **`mediaType`** *string* This REQUIRED property contains the MIME type of the referenced object. - (i.e. `application/vnd.oci.image.rootfs.tar.gzip`) + (i.e. `application/vnd.oci.image.serialization.rootfs.tar.gzip`) - **`size`** *int* @@ -211,23 +211,23 @@ The image manifest provides a configuration and a set of layers for a container "schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", "config": { - "mediaType": "application/vnd.oci.image.serialization.v1+json", + "mediaType": "application/vnd.oci.image.serialization.config.v1+json", "size": 7023, "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7" }, "layers": [ { - "mediaType": "application/vnd.oci.image.rootfs.tar.gzip", + "mediaType": "application/vnd.oci.image.serialization.rootfs.tar.gzip", "size": 32654, "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f" }, { - "mediaType": "application/vnd.oci.image.rootfs.tar.gzip", + "mediaType": "application/vnd.oci.image.serialization.rootfs.tar.gzip", "size": 16724, "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b" }, { - "mediaType": "application/vnd.oci.image.rootfs.tar.gzip", + "mediaType": "application/vnd.oci.image.serialization.rootfs.tar.gzip", "size": 73109, "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736" } From 0a41b1208816b5471e152a670b7ab0a5225c1564 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Mon, 2 May 2016 00:13:04 -0700 Subject: [PATCH 040/245] media-types: add a compatbility list In an effort to help people implementing registries or systems that consume the OCI Image specification and other image formats start a list of compatible mime-types. Fixes https://github.com/opencontainers/image-spec/issues/14 Take some language from the discussion here: https://github.com/opencontainers/image-spec/issues/42 Signed-off-by: Brandon Philips --- media-types.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/media-types.md b/media-types.md index 27587b3..ef54740 100644 --- a/media-types.md +++ b/media-types.md @@ -7,3 +7,37 @@ The following `mediaType` MIME types are used by the formats described here, and - `application/vnd.oci.image.serialization.rootfs.tar.gzip`: ["Layer", as a gzipped tar archive](serialization.md#creating-an-image-filesystem-changeset) - `application/vnd.oci.image.serialization.config.v1+json`: [Container config JSON](serialization.md#image-json-description) - `application/vnd.oci.image.serialization.combined.v1+json`: [Combined image JSON and filesystem changesets](serialization.md#combined-image-json--filesystem-changeset-format) + +## 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 + +**Interchangable 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.serialization.config.v1+json + +**Similar/related schema** + +- [application/vnd.docker.container.image.v1+json](https://github.com/docker/docker/blob/master/image/spec/v1.md#image-json-description) + +### application/vnd.oci.image.serialization.combined.v1+json + +- [layout compatible with docker save/load format](https://github.com/opencontainers/image-spec/blob/master/serialization.md#combined-image-json--filesystem-changeset-format) From 016d33abf8baf1c770e2e8a2fd51c6af6b1808f3 Mon Sep 17 00:00:00 2001 From: Timothy Hobbs Date: Fri, 15 Apr 2016 01:21:09 +0200 Subject: [PATCH 041/245] Specify process for naming future architectures Signed-off-by: Timothy Hobbs --- serialization.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/serialization.md b/serialization.md index 5a7226a..e2914c4 100644 --- a/serialization.md +++ b/serialization.md @@ -173,6 +173,7 @@ It has been added to this example for clarity.
    • 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 @@ -186,6 +187,7 @@ It has been added to this example for clarity.
  • 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 From 7b39189b95440a24e5f0f60c97d46ed69cc4486e Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Mon, 2 May 2016 13:19:47 +0200 Subject: [PATCH 042/245] media-types: show relations between media types Signed-off-by: Sergiusz Urbaniak --- Makefile | 16 +++++++++++++--- media-types.dot | 14 ++++++++++++++ media-types.md | 8 ++++++++ media-types.png | Bin 0 -> 34295 bytes 4 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 media-types.dot create mode 100644 media-types.png diff --git a/Makefile b/Makefile index fe21ecf..1fc2222 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,9 @@ DOC_FILES := \ manifest.md \ serialization.md +FIGURE_FILES := \ + media-types.png + default: help help: @@ -23,8 +26,9 @@ fmt: docs: output/docs.pdf output/docs.html .PHONY: docs -output/docs.pdf: $(DOC_FILES) +output/docs.pdf: $(DOC_FILES) $(FIGURE_FILES) @mkdir -p output/ && \ + cp *.png $(shell pwd)/output && \ $(DOCKER) run \ -it \ --rm \ @@ -34,8 +38,9 @@ output/docs.pdf: $(DOC_FILES) vbatts/pandoc -f markdown_github -t latex -o /output/docs.pdf $(patsubst %,/input/%,$(DOC_FILES)) && \ ls -sh $(shell readlink -f $@) -output/docs.html: $(DOC_FILES) +output/docs.html: $(DOC_FILES) $(FIGURE_FILES) @mkdir -p output/ && \ + cp *.png $(shell pwd)/output && \ $(DOCKER) run \ -it \ --rm \ @@ -48,7 +53,6 @@ output/docs.html: $(DOC_FILES) code-of-conduct.md: curl -o $@ https://raw.githubusercontent.com/opencontainers/tob/d2f9d68c1332870e40693fe077d311e0742bc73d/code-of-conduct.md -.PHONY: validate-examples validate-examples: oci-validate-examples ./oci-validate-examples < manifest.md @@ -58,3 +62,9 @@ oci-validate-json: validate.go oci-validate-examples: cmd/oci-validate-examples/main.go go build ./cmd/oci-validate-examples +media-types.png: media-types.dot + +%.png: %.dot + dot -Tpng $^ > $@ + +.PHONY: validate-examples diff --git a/media-types.dot b/media-types.dot new file mode 100644 index 0000000..ee0dd84 --- /dev/null +++ b/media-types.dot @@ -0,0 +1,14 @@ +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.serialization.config.v1+json"] + layer [shape=note, label="Layer tar+gzip\napplication/vnd.oci.image.serialization.rootfs.tar.gzip"] + combined [shape=note, label="Combined image JSON and filesystem changesets\n<>\napplication/vnd.oci.image.serialization.combined.v1+json"] + } + + manifestList -> manifest [label="1..*"] + manifest -> config [label="1..1"] + manifest -> layer [label="1..*"] + combined +} diff --git a/media-types.md b/media-types.md index ef54740..3bf7221 100644 --- a/media-types.md +++ b/media-types.md @@ -41,3 +41,11 @@ This section shows where the OCI Image Specification is compatible with formats ### application/vnd.oci.image.serialization.combined.v1+json - [layout compatible with docker save/load format](https://github.com/opencontainers/image-spec/blob/master/serialization.md#combined-image-json--filesystem-changeset-format) + +## Relations + +The following figure shows how the above media types reference each other: + +![Media Types](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/media-types.png b/media-types.png new file mode 100644 index 0000000000000000000000000000000000000000..4bc20cf10962671f4c2833efc6a7224c4a88c44b GIT binary patch literal 34295 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 z y Date: Tue, 3 May 2016 11:42:36 -0700 Subject: [PATCH 043/245] Initial commit --- LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. From e3fbd396c7ac5f1b4db591e8fb21ac0980ba1aa1 Mon Sep 17 00:00:00 2001 From: Chris Aniszczyk Date: Tue, 3 May 2016 11:43:30 -0700 Subject: [PATCH 044/245] Create README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..2a80b08 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# OCI Project Template + +All OCI projects should have: +* LICENSE +* CONTRIBUTING.md +* MAINTAINERS.md +* MAINTAINERS_GUIDE.md +* README From fcc7f4212db4a8861ff1647c2c8a8a1c004093b3 Mon Sep 17 00:00:00 2001 From: Chris Aniszczyk Date: Tue, 3 May 2016 11:45:27 -0700 Subject: [PATCH 045/245] Add contributing and maintainer guidelines. Signed-off-by: Chris Aniszczyk --- CONTRIBUTING.md | 116 +++++++++++++++++++++++++++++++++++++++++ MAINTAINERS_GUIDE.md | 121 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 MAINTAINERS_GUIDE.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..385fba5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,116 @@ +## Contribution Guidelines + +### Pull requests are always welcome + +We are always thrilled to receive pull requests, and do our best to +process them as fast as possible. Not sure if that typo is worth a pull +request? Do it! We will appreciate it. + +If your pull request is not accepted on the first try, don't be +discouraged! If there's a problem with the implementation, hopefully you +received feedback on what to improve. + +We're trying very hard to keep the project lean and focused. We don't want it +to do everything for everybody. This means that we might decide against +incorporating a new feature. + + +### Conventions + +Fork the repo and make changes on your fork in a feature branch: + +- If it's a bugfix branch, name it XXX-something where XXX is the number of the + issue +- If it's a feature branch, create an enhancement issue to announce your + intentions, and name it XXX-something where XXX is the number of the issue. + +Submit unit tests for your changes. Go has a great test framework built in; use +it! Take a look at existing tests for inspiration. Run the full test suite on +your branch before submitting a pull request. + +Update the documentation when creating or modifying features. Test +your documentation changes for clarity, concision, and correctness, as +well as a clean documentation build. See ``docs/README.md`` for more +information on building the docs and how docs get released. + +Write clean code. Universally formatted code promotes ease of writing, reading, +and maintenance. Always run `gofmt -s -w file.go` on each changed file before +committing your changes. Most editors have plugins that do this automatically. + +Pull requests descriptions should be as clear as possible and include a +reference to all the issues that they address. + +Pull requests must not contain commits from other users or branches. + +Commit messages must start with a capitalized and short summary (max. 50 +chars) written in the imperative, followed by an optional, more detailed +explanatory text which is separated from the summary by an empty line. + +Code review comments may be added to your pull request. Discuss, then make the +suggested modifications and push additional commits to your feature branch. Be +sure to post a comment after pushing. The new commits will show up in the pull +request automatically, but the reviewers will not be notified unless you +comment. + +Before the pull request is merged, make sure that you squash your commits into +logical units of work using `git rebase -i` and `git push -f`. After every +commit the test suite should be passing. Include documentation changes in the +same commit so that a revert would remove all traces of the feature or fix. + +Commits that fix or close an issue should include a reference like `Closes #XXX` +or `Fixes #XXX`, which will automatically close the issue when merged. + +### Sign your work + +The sign-off is a simple line at the end of the explanation for the +patch, which certifies that you wrote it or otherwise have the right to +pass it on as an open-source patch. The rules are pretty simple: if you +can certify the below (from +[developercertificate.org](http://developercertificate.org/)): + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +then you just add a line to every git commit message: + + Signed-off-by: Joe Smith + +using your real name (sorry, no pseudonyms or anonymous contributions.) + +You can add the sign off when creating the git commit via `git commit -s`. diff --git a/MAINTAINERS_GUIDE.md b/MAINTAINERS_GUIDE.md new file mode 100644 index 0000000..16a6367 --- /dev/null +++ b/MAINTAINERS_GUIDE.md @@ -0,0 +1,121 @@ +## Introduction + +Dear maintainer. Thank you for investing the time and energy to help +make this project as useful as possible. Maintaining a project is difficult, +sometimes unrewarding work. Sure, you will get to contribute cool +features to the project. But most of your time will be spent reviewing, +cleaning up, documenting, answering questions, justifying design +decisions - while everyone has all the fun! But remember - the quality +of the maintainers work is what distinguishes the good projects from the +great. So please be proud of your work, even the unglamourous parts, +and encourage a culture of appreciation and respect for *every* aspect +of improving the project - not just the hot new features. + +This document is a manual for maintainers old and new. It explains what +is expected of maintainers, how they should work, and what tools are +available to them. + +This is a living document - if you see something out of date or missing, +speak up! + +## What are a maintainer's responsibility? + +It is every maintainer's responsibility to: + +* 1) Expose a clear roadmap for improving their component. +* 2) Deliver prompt feedback and decisions on pull requests. +* 3) Be available to anyone with questions, bug reports, criticism etc. + on their component. This includes IRC and GitHub issues and pull requests. +* 4) Make sure their component respects the philosophy, design and + roadmap of the project. + +## How are decisions made? + +Short answer: with pull requests to the project repository. + +This project is an open-source project with an open design philosophy. This +means that the repository is the source of truth for EVERY aspect of the +project, including its philosophy, design, roadmap and APIs. *If it's +part of the project, it's in the repo. It's in the repo, it's part of +the project.* + +As a result, all decisions can be expressed as changes to the +repository. An implementation change is a change to the source code. An +API change is a change to the API specification. A philosophy change is +a change to the philosophy manifesto. And so on. + +All decisions affecting this project, big and small, follow the same 3 steps: + +* Step 1: Open a pull request. Anyone can do this. + +* Step 2: Discuss the pull request. Anyone can do this. + +* Step 3: Accept (`LGTM`) or refuse a pull request. The relevant maintainers do +this (see below "Who decides what?") + +### I'm a maintainer, should I make pull requests too? + +Yes. Nobody should ever push to master directly. All changes should be +made through a pull request. + +## Who decides what? + +All decisions are pull requests, and the relevant maintainers make +decisions by accepting or refusing the pull request. Review and acceptance +by anyone is denoted by adding a comment in the pull request: `LGTM`. +However, only currently listed `MAINTAINERS` are counted towards the required +two LGTMs. + +Overall the maintainer system works because of mutual respect across the +maintainers of the project. The maintainers trust one another to make decisions +in the best interests of the project. Sometimes maintainers can disagree and +this is part of a healthy project to represent the point of views of various people. +In the case where maintainers cannot find agreement on a specific change the +role of a Chief Maintainer comes into play. + +The Chief Maintainer for the project is responsible for overall architecture +of the project to maintain conceptual integrity. Large decisions and +architecture changes should be reviewed by the chief maintainer. +The current chief maintainer for the project is the first person listed +in the MAINTAINERS file. + +Even though the maintainer system is built on trust, if there is a conflict +with the chief maintainer on a decision, their decision can be challenged +and brought to the technical oversight board if two-thirds of the +maintainers vote for an appeal. It is expected that this would be a +very exceptional event. + + +### How are maintainers added? + +The best maintainers have a vested interest in the project. Maintainers +are first and foremost contributors that have shown they are committed to +the long term success of the project. Contributors wanting to become +maintainers are expected to be deeply involved in contributing code, +pull request review, and triage of issues in the project for more than two months. + +Just contributing does not make you a maintainer, it is about building trust +with the current maintainers of the project and being a person that they can +depend on and trust to make decisions in the best interest of the project. The +final vote to add a new maintainer should be approved by over 66% of the current +maintainers with the chief maintainer having veto power. In case of a veto, +conflict resolution rules expressed above apply. The voting period is +five business days on the Pull Request to add the new maintainer. + + +### What is expected of maintainers? + +Part of a healthy project is to have active maintainers to support the community +in contributions and perform tasks to keep the project running. Maintainers are +expected to be able to respond in a timely manner if their help is required on specific +issues where they are pinged. Being a maintainer is a time consuming commitment and should +not be taken lightly. + +When a maintainer is unable to perform the required duties they can be removed with +a vote by 66% of the current maintainers with the chief maintainer having veto power. +The voting period is ten business days. Issues related to a maintainer's performance should +be discussed with them among the other maintainers so that they are not surprised by +a pull request removing them. + + + From 5112dab81458326e2a6ff1e58252f5266efd345e Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 3 May 2016 14:12:31 -0700 Subject: [PATCH 046/245] serialization: remove references to docker This spec started from the Docker v1.10 serialization notes but we need to give it its own identity. So, remove mentions of Docker that aren't material to the spec. Signed-off-by: Brandon Philips --- serialization.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/serialization.md b/serialization.md index b951934..bc877c2 100644 --- a/serialization.md +++ b/serialization.md @@ -1,10 +1,8 @@ -# Docker Image Specification v1.1.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 format of these filesystem changes and corresponding parameters and describes how to create and use them for use with a container runtime and execution tool. -This version of the image specification was adopted starting in Docker 1.10. - ## Terminology This specification uses the following terms: @@ -105,8 +103,8 @@ Here is an example image JSON file: }, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "FOO=docker_is_a_really", - "BAR=great_tool_you_know" + "FOO=oci_is_a", + "BAR=well_written_spec" ], "Entrypoint": [ "/bin/my-app-binary" @@ -143,8 +141,7 @@ Here is an example image JSON file: } ``` -Note that image JSON files produced by Docker don't contain formatting whitespace. -It has been added to this example for clarity. +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 From f4454f7829f6161f1651975fe2fa52b77bc7d32c Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Wed, 4 May 2016 11:58:14 +0200 Subject: [PATCH 047/245] serialization: format the config example as valid JSON Signed-off-by: Sergiusz Urbaniak --- serialization.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/serialization.md b/serialization.md index b951934..7046bb3 100644 --- a/serialization.md +++ b/serialization.md @@ -118,9 +118,9 @@ Here is an example image JSON file: ], "Volumes": { "/var/job-result-data": {}, - "/var/log/my-app-logs": {}, + "/var/log/my-app-logs": {} }, - "WorkingDir": "/home/alice", + "WorkingDir": "/home/alice" }, "rootfs": { "diff_ids": [ From 6f29dd4fe44c0ed478f28e0775deadc0fec0b25e Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Fri, 6 May 2016 12:14:15 -0400 Subject: [PATCH 048/245] README: add initial pointer for the spec(s) Signed-off-by: Doug Davis --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 3a7a4ed..63444b6 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,11 @@ This new OCI project intends to start with the Docker v2.2 specification, improv * 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 Specification + +The Distributable Image Specification is made up of several different components and documents. +It is recommended to begin with the [Media Types](media-types.md) document as a starting point to understanding the overall structure of the specification. + ## Cooperation with OCI Runtime Project The [OCI Runtime Spec project](https://github.com/opencontainers/runtime-spec) is developing a specification for the lifecycle of a running container. The OCI Image Format Spec project should work with the OCI Runtime Spec project so that the image can support 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: From fa4c6e7cb04d5cb7e0464a3e4640d5b59cf348a7 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Fri, 6 May 2016 08:27:54 -0700 Subject: [PATCH 049/245] Minor tweaks to manifest for clarity Signed-off-by: Doug Davis --- manifest.md | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/manifest.md b/manifest.md index 07683ca..d74e1cf 100644 --- a/manifest.md +++ b/manifest.md @@ -18,7 +18,7 @@ The manifest list is the "fat manifest" which points to specific image manifests 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* Field Descriptions +## *Manifest List* Property Descriptions - **`schemaVersion`** *int* @@ -35,7 +35,7 @@ A client will distinguish a manifest list from an image manifest based on the Co 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. - Fields of each object in the manifests list are: + Properties of each object in the manifests list are: - **`mediaType`** *string* @@ -45,7 +45,7 @@ A client will distinguish a manifest list from an image manifest based on the Co - **`size`** *int* This REQUIRED property specifies the size in bytes of the object. - This field exists so that a client will have an expected size for the content before validating. + This property exists so that a client will have an expected size for the content before validating. If the length of the retrieved content does not match the specified length, the content should not be trusted. - **`digest`** *string* @@ -67,7 +67,7 @@ A client will distinguish a manifest list from an image manifest based on the Co - **`os.version`** *string* - This optional property specifies the operating system version, for example `10.0.10586`. + This OPTIONAL property specifies the operating system version, for example `10.0.10586`. - **`os.features`** *array* @@ -132,14 +132,14 @@ A client will distinguish a manifest list from an image manifest based on the Co # Image Manifest -The image manifest provides a configuration and a set of layers for a container image. +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* Field Descriptions +## *Image Manifest* Property Descriptions - **`schemaVersion`** *int* This REQUIRED property specifies the image manifest schema version. - This schema uses version `2`. + For this version of the specification, this MUST be `2`. - **`mediaType`** *string* @@ -148,11 +148,10 @@ The image manifest provides a configuration and a set of layers for a container - **`config`** *object* - The config field references a configuration object for a container, by digest. - This configuration item is a JSON blob that the runtime uses to set up the container. - This new schema uses a tweaked version of this configuration to allow image content-addressability on the daemon side. + The `config` 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](serialization.md#image-json-description). - Fields of a config object are: + Properties of `config` are: - **`mediaType`** *string* @@ -162,7 +161,7 @@ The image manifest provides a configuration and a set of layers for a container - **`size`** *int* This REQUIRED property specifies the size in bytes of the object. - This field exists so that a client will have an expected size for the content before validating. + This property exists so that a client will have an expected size for the content before validating. If the length of the retrieved content does not match the specified length, the content should not be trusted. - **`digest`** *string* @@ -171,10 +170,11 @@ The image manifest provides a configuration and a set of layers for a container - **`layers`** *array* - The layer list has the base image at index 0. - The algorithm to create the final unpacked filesystem layout is to first unpack the layer at index 0, then index 1, and so on. + The layer list 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. - Fields of an item in the layers list are: + Properties of an item in the layers list are: - **`mediaType`** *string* @@ -184,7 +184,7 @@ The image manifest provides a configuration and a set of layers for a container - **`size`** *int* This REQUIRED property specifies the size in bytes of the object. - This field exists so that a client will have an expected size for the content before validating. + This property exists so that a client will have an expected size for the content before validating. If the length of the retrieved content does not match the specified length, the content should not be trusted. - **`digest`** *string* @@ -195,7 +195,7 @@ The image manifest provides a configuration and a set of layers for a container This OPTIONAL property contains arbitrary metadata for the manifest list. Annotations is a key-value, unordered hashmap. - Keys are unique, and best practice is to namespace the keys. + Keys MUST be unique within the hashmap, and best practice is to namespace the keys. Common annotation keys include: * **created** date on which the image was built (string, timestamps type) * **authors** contact details of the people or organization responsible for the image (freeform string) @@ -243,20 +243,20 @@ The image manifest provides a configuration and a set of layers for a container The registry will continue to accept uploads of manifests in both the old and new formats. -When pushing images, clients which support the new manifest format should first construct a manifest in the new format. -If uploading this manifest fails, presumably because the registry only supports the old format, the client may fall back to uploading a manifest in the old format. +When pushing images, clients which support the new manifest format SHOULD first construct a manifest in the new format. +If uploading this manifest fails, presumably because the registry only supports the old format, the client MAY fall back to uploading a manifest in the old format. When pulling images, clients indicate support for this new version of the manifest format by sending the `application/vnd.oci.image.manifest.v1+json` and `application/vnd.oci.image.manifest.list.v1+json` media types in an `Accept` header when making a request to the `manifests` endpoint. -Updated clients should check the `Content-Type` header to see whether the manifest returned from the endpoint is in the old format, or is an image manifest or manifest list in the new format. +Updated clients SHOULD check the `Content-Type` header to see whether the manifest returned from the endpoint is in the old format, or is an image manifest or manifest list in the new format. -If the manifest being requested uses the new format, and the appropriate media type is not present in an `Accept` header, the registry will assume that the client cannot handle the manifest as-is, and rewrite it on the fly into the old format. -If the object that would otherwise be returned is a manifest list, the registry will look up the appropriate manifest for the amd64 platform and linux OS, rewrite that manifest into the old format if necessary, and return the result to the client. +If the manifest being requested uses the new format, and the appropriate media type is not present in an `Accept` header, the registry MUST assume that the client cannot handle the manifest as-is, and MUST rewrite it on the fly into the old format. +If the object that would otherwise be returned is a manifest list, the registry MUST look up the appropriate manifest for the amd64 platform and linux OS, rewrite that manifest into the old format if necessary, and return the result to the client. If no suitable manifest is found in the manifest list, the registry will return a 404 error. One of the challenges in rewriting manifests to the old format is that the old format involves an image configuration for each layer in the manifest, but the new format only provides one image configuration. -To work around this, the registry will create synthetic image configurations for all layers except the top layer. +To work around this, the registry MUST create synthetic image configurations for all layers except the top layer. These image configurations will not result in runnable images on their own, but only serve to fill in the parent chain in a compatible way. The IDs in these synthetic configurations will be derived from hashes of their respective blobs. -The registry will create these configurations and their IDs using the same scheme as Docker 1.10 when it creates a legacy manifest to push to a registry which doesn't support the new format. +The registry MUST create these configurations and their IDs using the same scheme as Docker 1.10 when it creates a legacy manifest to push to a registry which doesn't support the new format. From 88ef945196cc5d6b1e5249470fef752a06fa96e8 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Sat, 7 May 2016 07:56:48 -0400 Subject: [PATCH 050/245] image manifest: add source annotation For a number of images, source code needs to be provided due to the license of the files in the image, so provide an annotation to make it easy to determine where that source can be found. Signed-off-by: Greg Kroah-Hartman --- manifest.md | 1 + 1 file changed, 1 insertion(+) diff --git a/manifest.md b/manifest.md index d74e1cf..0b81f1d 100644 --- a/manifest.md +++ b/manifest.md @@ -201,6 +201,7 @@ Unlike the [Manifest List](#manifest-list), which contains information about a s * **authors** contact details of the people or organization responsible for the image (freeform string) * **homepage** URL to find more information on the image (string, must be a URL with scheme HTTP or HTTPS) * **documentation** URL to get documentation on the image (string, must be a URL with scheme HTTP or HTTPS) + * **source** URL to get the source code for the binary files in the image (string, must be a URL with scheme HTTP or HTTPS) ## Example Image Manifest From 4263cf529b30d46b99a761ca764ede70b667cdc9 Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Mon, 2 May 2016 19:15:36 +0200 Subject: [PATCH 051/245] oci-image-tool: initial commit This adds the oci-image-tool with the following capabilities: - validate manifest - validate manifest list Signed-off-by: Sergiusz Urbaniak --- .gitignore | 1 + .travis.yml | 14 +- Makefile | 17 +- cmd/oci-image-tool/main.go | 24 + cmd/oci-image-tool/validate.go | 201 +++++++ cmd/oci-validate-json/main.go | 45 -- lint | 22 + schema/doc.go | 2 + schema/fs.go | 538 ++++++++++++++++++ schema/gen.go | 6 + schema/schema.go | 30 + .../main.go => schema/spec_test.go | 122 ++-- schema/validator.go | 58 ++ 13 files changed, 940 insertions(+), 140 deletions(-) create mode 100644 cmd/oci-image-tool/main.go create mode 100644 cmd/oci-image-tool/validate.go delete mode 100644 cmd/oci-validate-json/main.go create mode 100755 lint create mode 100644 schema/doc.go create mode 100644 schema/fs.go create mode 100644 schema/gen.go create mode 100644 schema/schema.go rename cmd/oci-validate-examples/main.go => schema/spec_test.go (51%) create mode 100644 schema/validator.go diff --git a/.gitignore b/.gitignore index 9179330..a8b3002 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ oci-validate-examples code-of-conduct.md +oci-image-tool diff --git a/.travis.yml b/.travis.yml index fdf4327..08fd81e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,13 +4,19 @@ go: sudo: false +before_script: + - export PATH=$HOME/gopath/bin:$PATH + before_install: - go get github.com/vbatts/git-validation - - go get -d ./cmd/... + - go get github.com/alecthomas/gometalinter + - gometalinter --install --update + - go get -t -d ./... install: true script: - - $HOME/gopath/bin/git-validation -run DCO,short-subject -v -range ${TRAVIS_COMMIT_RANGE} - - make validate-examples - + - git-validation -run DCO,short-subject -v -range ${TRAVIS_COMMIT_RANGE} + - make lint + - make test + - make oci-image-tool diff --git a/Makefile b/Makefile index 1fc2222..522fc18 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,6 @@ fmt: for i in *.json ; do jq --indent 2 -M . "$${i}" > xx && cat xx > "$${i}" && rm xx ; done docs: output/docs.pdf output/docs.html -.PHONY: docs output/docs.pdf: $(DOC_FILES) $(FIGURE_FILES) @mkdir -p output/ && \ @@ -62,9 +61,23 @@ oci-validate-json: validate.go oci-validate-examples: cmd/oci-validate-examples/main.go go build ./cmd/oci-validate-examples +oci-image-tool: + go build ./cmd/oci-image-tool + +lint: + for d in $(shell find . -type d -not -iwholename '*.git*'); do echo "$${d}" && ./lint "$${d}"; done + +test: + go test -race ./... + media-types.png: media-types.dot %.png: %.dot dot -Tpng $^ > $@ -.PHONY: validate-examples +.PHONY: \ + validate-examples \ + oci-image-tool \ + lint \ + docs \ + test diff --git a/cmd/oci-image-tool/main.go b/cmd/oci-image-tool/main.go new file mode 100644 index 0000000..93fabd7 --- /dev/null +++ b/cmd/oci-image-tool/main.go @@ -0,0 +1,24 @@ +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)) + if err := cmd.Execute(); err != nil { + stderr.Println(err) + os.Exit(1) + } +} diff --git a/cmd/oci-image-tool/validate.go b/cmd/oci-image-tool/validate.go new file mode 100644 index 0000000..5f42483 --- /dev/null +++ b/cmd/oci-image-tool/validate.go @@ -0,0 +1,201 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "strings" + + "github.com/opencontainers/image-spec/schema" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// supported validation types +const ( + typeImageLayout = "imageLayout" + typeImage = "image" + typeManifest = "manifest" + typeManifestList = "manifestList" + typeConfig = "config" +) + +var validateTypes = []string{ + typeImageLayout, + typeImage, + typeManifest, + typeManifestList, + typeConfig, +} + +type validateCmd struct { + stdout *log.Logger + stderr *log.Logger + typ string // the type to validate, can be empty string +} + +func newValidateCmd(stdout, stderr *log.Logger) *cobra.Command { + v := &validateCmd{ + stdout: stdout, + stderr: stderr, + } + + cmd := &cobra.Command{ + Use: "validate FILE...", + Short: "Validate one or more image files", + Run: v.Run, + } + + cmd.Flags().StringVar( + &v.typ, "type", "", + fmt.Sprintf( + `Type of the file to validate. If unset, oci-image-tool will try to auto-detect the type. One of "%s"`, + strings.Join(validateTypes, ","), + ), + ) + + return cmd +} + +func (v *validateCmd) Run(cmd *cobra.Command, args []string) { + if len(args) < 1 { + v.stderr.Printf("no files specified") + if err := cmd.Usage(); err != nil { + v.stderr.Println(err) + } + os.Exit(1) + } + + var exitcode int + for _, arg := range args { + err := v.validatePath(arg) + + if err == nil { + v.stdout.Printf("file %s: OK", arg) + continue + } + + var errs []error + if verr, ok := errors.Cause(err).(schema.ValidationError); ok { + errs = verr.Errs + } else { + v.stderr.Printf("file %s: validation failed: %v", arg, err) + exitcode = 1 + continue + } + + for _, err := range errs { + v.stderr.Printf("file %s: validation failed: %v", arg, err) + } + + exitcode = 1 + } + + os.Exit(exitcode) +} + +func (v *validateCmd) validatePath(name string) error { + var err error + typ := v.typ + + if typ == "" { + if typ, err = autodetect(name); err != nil { + return errors.Wrap(err, "unable to determine type") + } + } + + f, err := os.Open(name) + if err != nil { + return errors.Wrap(err, "unable to open file") + } + defer f.Close() + + switch typ { + case typeManifest: + if err := schema.MediaTypeManifest.Validate(f); err != nil { + return err + } + + return nil + case typeManifestList: + if err := schema.MediaTypeManifestList.Validate(f); err != nil { + return err + } + + return nil + } + + return fmt.Errorf("type %q unimplemented", typ) +} + +// 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) { + 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 + } + + f, err := os.Open(path) + if err != nil { + return "", errors.Wrap(err, "unable to open file") // os.Open includes the filename + } + defer f.Close() + + buf, err := ioutil.ReadAll(io.LimitReader(f, 512)) // read some initial bytes to detect content + if err != nil { + return "", errors.Wrap(err, "unable to read") + } + + mimeType := http.DetectContentType(buf) + + switch mimeType { + case "application/x-gzip": + return typeImage, nil + + case "application/octet-stream": + return typeImage, nil + + case "text/plain; charset=utf-8": + // might be a JSON file, will be handled below + + default: + return "", errors.New("unknown file type") + } + + if _, err := f.Seek(0, os.SEEK_SET); err != nil { + return "", errors.Wrap(err, "unable to seek") + } + + header := struct { + SchemaVersion int `json:"schemaVersion"` + MediaType string `json:"mediaType"` + Config interface{} `json:"config"` + }{} + + if err := json.NewDecoder(f).Decode(&header); err != nil { + return "", errors.Wrap(err, "unable to parse JSON") + } + + switch { + case header.MediaType == string(schema.MediaTypeManifest): + return typeManifest, nil + + case header.MediaType == string(schema.MediaTypeManifestList): + return typeManifestList, nil + + case header.MediaType == "" && header.SchemaVersion == 0 && header.Config != nil: + // config files don't have mediaType/schemaVersion header + return typeConfig, nil + } + + return "", errors.New("unknown media type") +} diff --git a/cmd/oci-validate-json/main.go b/cmd/oci-validate-json/main.go deleted file mode 100644 index a4d60ec..0000000 --- a/cmd/oci-validate-json/main.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/xeipuuv/gojsonschema" -) - -func main() { - if len(os.Args[1:]) != 2 { - fmt.Printf("ERROR: usage is: %s ", os.Args[0]) - os.Exit(1) - } - - schemaPath, err := filepath.Abs(os.Args[1]) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - documentPath, err := filepath.Abs(os.Args[2]) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - schemaLoader := gojsonschema.NewReferenceLoader("file://" + schemaPath) - documentLoader := gojsonschema.NewReferenceLoader("file://" + documentPath) - - result, err := gojsonschema.Validate(schemaLoader, documentLoader) - if err != nil { - panic(err.Error()) - } - - if result.Valid() { - fmt.Printf("The document is valid\n") - } else { - fmt.Printf("The document is not valid. see errors :\n") - for _, desc := range result.Errors() { - fmt.Printf("- %s\n", desc) - } - os.Exit(1) - } -} diff --git a/lint b/lint new file mode 100755 index 0000000..3ac5d2a --- /dev/null +++ b/lint @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +if [ ! $(command -v gometalinter) ] +then + go get github.com/alecthomas/gometalinter + gometalinter --update --install +fi + +gometalinter \ + --exclude='error return value not checked.*(Close|Log|Print).*\(errcheck\)$' \ + --exclude='.*_test\.go:.*error return value not checked.*\(errcheck\)$' \ + --exclude='duplicate of.*_test.go.*\(dupl\)$' \ + --exclude='schema/fs.go' \ + --disable=aligncheck \ + --disable=gotype \ + --cyclo-over=20 \ + --tests \ + --deadline=10s "${1}" diff --git a/schema/doc.go b/schema/doc.go new file mode 100644 index 0000000..dd9a732 --- /dev/null +++ b/schema/doc.go @@ -0,0 +1,2 @@ +// Package schema defines the OCI image media types, schema definitions and validation functions. +package schema diff --git a/schema/fs.go b/schema/fs.go new file mode 100644 index 0000000..c0a57bc --- /dev/null +++ b/schema/fs.go @@ -0,0 +1,538 @@ +package schema + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "io/ioutil" + "net/http" + "os" + "path" + "sync" + "time" +) + +type _escLocalFS struct{} + +var _escLocal _escLocalFS + +type _escStaticFS struct{} + +var _escStatic _escStaticFS + +type _escDirectory struct { + fs http.FileSystem + name string +} + +type _escFile struct { + compressed string + size int64 + modtime int64 + local string + isDir bool + + once sync.Once + data []byte + name string +} + +func (_escLocalFS) Open(name string) (http.File, error) { + f, present := _escData[path.Clean(name)] + if !present { + return nil, os.ErrNotExist + } + return os.Open(f.local) +} + +func (_escStaticFS) prepare(name string) (*_escFile, error) { + f, present := _escData[path.Clean(name)] + if !present { + return nil, os.ErrNotExist + } + var err error + f.once.Do(func() { + f.name = path.Base(name) + if f.size == 0 { + return + } + var gr *gzip.Reader + b64 := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(f.compressed)) + gr, err = gzip.NewReader(b64) + if err != nil { + return + } + f.data, err = ioutil.ReadAll(gr) + }) + if err != nil { + return nil, err + } + return f, nil +} + +func (fs _escStaticFS) Open(name string) (http.File, error) { + f, err := fs.prepare(name) + if err != nil { + return nil, err + } + return f.File() +} + +func (dir _escDirectory) Open(name string) (http.File, error) { + return dir.fs.Open(dir.name + name) +} + +func (f *_escFile) File() (http.File, error) { + type httpFile struct { + *bytes.Reader + *_escFile + } + return &httpFile{ + Reader: bytes.NewReader(f.data), + _escFile: f, + }, nil +} + +func (f *_escFile) Close() error { + return nil +} + +func (f *_escFile) Readdir(count int) ([]os.FileInfo, error) { + return nil, nil +} + +func (f *_escFile) Stat() (os.FileInfo, error) { + return f, nil +} + +func (f *_escFile) Name() string { + return f.name +} + +func (f *_escFile) Size() int64 { + return f.size +} + +func (f *_escFile) Mode() os.FileMode { + return 0 +} + +func (f *_escFile) ModTime() time.Time { + return time.Unix(f.modtime, 0) +} + +func (f *_escFile) IsDir() bool { + return f.isDir +} + +func (f *_escFile) Sys() interface{} { + return f +} + +// _escFS returns a http.Filesystem for the embedded assets. If useLocal is true, +// the filesystem's contents are instead used. +func _escFS(useLocal bool) http.FileSystem { + if useLocal { + return _escLocal + } + return _escStatic +} + +// _escDir returns a http.Filesystem for the embedded assets on a given prefix dir. +// If useLocal is true, the filesystem's contents are instead used. +func _escDir(useLocal bool, name string) http.FileSystem { + if useLocal { + return _escDirectory{fs: _escLocal, name: name} + } + return _escDirectory{fs: _escStatic, name: name} +} + +// _escFSByte returns the named file from the embedded assets. If useLocal is +// true, the filesystem's contents are instead used. +func _escFSByte(useLocal bool, name string) ([]byte, error) { + if useLocal { + f, err := _escLocal.Open(name) + if err != nil { + return nil, err + } + return ioutil.ReadAll(f) + } + f, err := _escStatic.prepare(name) + if err != nil { + return nil, err + } + return f.data, nil +} + +// _escFSMustByte is the same as _escFSByte, but panics if name is not present. +func _escFSMustByte(useLocal bool, name string) []byte { + b, err := _escFSByte(useLocal, name) + if err != nil { + panic(err) + } + return b +} + +// _escFSString is the string version of _escFSByte. +func _escFSString(useLocal bool, name string) (string, error) { + b, err := _escFSByte(useLocal, name) + return string(b), err +} + +// _escFSMustString is the string version of _escFSMustByte. +func _escFSMustString(useLocal bool, name string) string { + return string(_escFSMustByte(useLocal, name)) +} + +var _escData = map[string]*_escFile{ + + "/defs-image.json": { + local: "defs-image.json", + size: 3100, + modtime: 1462965360, + compressed: ` +H4sIAAAJbogA/+xWy27bMBC86ysIJUAOfqjXGkGAoEGBnlIgPdVQgY20kphKpLqkgzqB/r3U+x3Xidte +erK55A5nhmPSzxZjto/KI55qLoW9YfYNBlzwfKRYCqS5t4uBmJbsNkXxQQoNXCCxTwmEyO5S9HjAPSja +lyVeA2Dw8i1MMUGfw5d9ik3JFLmfbxhpnaqN40gD79Xwai0pdJQXYQIOz7dyWohlDaBLQFtp4iJs6ylo +jVTI+baF1ZO7cLbvVu/Nt+vV1/XCXZzbxdKs7LB9HqLSXWoDU3SEzKN9qmVIkEbcY4aZ913tElb2Mhmw +fJG8f0BPLxkXxbAiwi4uI1DR1eYywp/gG8sSiKvOq4vj9Rgt7mJjvgXXq4/FYCiooi/p9X53MEYES5lt +nfDHjhPm+Nuq1jv0ZVtU/Kk3rryvCm6rmQxBEz9UHQkzSZo7smJtzrk+HsIAychGnw0kFBDnZj7vPetk +uJO7Zmk21HOYSr4sT8X9XqP6TTq121xoDJGm9x9ld15J32oDY3U/6+wkIHhg1t2cIEMTWH8hS51KGoMO +JCX/8/Uv8jV1EAOg4/LUoEzqmNI4maZiBsiLuDYNO8Jej5mTyu4U3B7iTHDGmMPZV6t1XqA6fjV609lY +2OloGbA3klk/mh3KFJ+OVAP6VnIBQu74aS1rUWfpARHsx9MmAckUlwPC2nt+WugjEAcx/IUf7ddLZv0x +YSMo8P3iMoL4c/dnGkCs0JrzJDvwIoIQUkP/H+3REeiCNI+QFHgb9K6myVvWXLJq/aCkOHN6Lwekd4Uv +dwN3Or48T12UYhfH478BrlWPMiuzfgUAAP//VjUNyBwMAAA= +`, + }, + + "/defs.json": { + local: "defs.json", + size: 3044, + modtime: 1462965360, + compressed: ` +H4sIAAAJbogA/6xWT3PaPhC98yk8/H5H2tiybENvnaZ/csiEmUxPnR5cs4BakFRZ7jTN8N0rGWMse3Ew +5QBYu9r3dt+usJ5HnjdeQJ4pJjUTfPzGG9/CknFmV7lX5LDw9FqJYrUWhTaP4D1I4O8E1ynjoLxHCRlb +siwtwyd7vBrA4FkKY2RcT+uVWesnCZbN2GEFqowsHVsTuy22xvcqINOjOf1dmQOSlMbdpEYO4qHQIUli +DNzaO/AhGQpPAprQaRhTjKN2dohiOpRoRkgYJsQP42lEkyT2fR9hRHY51MUF3cF4SBR1cAf3BgOOoyjs +Qg/uCwZNyYzO4oTMuviD24HhB1NK44RSPwkTfxZFBBM/iOfC4qomoeDwsDSGL5XBq12l+38F1jv+76Zx +4G4qyeuNuwkefaiGF5tNY3f19BXR4poZGmWvmmGuFeOr4RkeOPbx181pm8rHEnb/jY2S+PYdMn2cJJlq +kz+fKyFBaQZ5I8i4Xz8Hk51j6ith1Pw9JPX57raZyOkOmbPlBH68NPCtUunTw9LE55gEqXUfFWAatq2q +cSqbD1phxbcX/UJKXFOX5wPbwDzVa4yhGXfY/57/elnAVvIOwCchfjQR5IkpkW5SPWx1CdjcG5lW+Xk4 +WNZtNHDK7wGzOr0wxBWfFeSqM1UqjDLe3d6nUrZO8akGrEWundPSQ9k8MW3JssMl6xpgOfsDF6KgityL +gutz1MhFobIzqfsH0txTNeNpdU/9Zzgh3StqL9Q5I16N37B/53pKFfwsmIKF87Jyap50RG2Tu++hkf3s +Rn8DAAD//2VgiEzkCwAA +`, + }, + + "/doc.go": { + local: "doc.go", + size: 113, + modtime: 1462979016, + compressed: ` +H4sIAAAJbogA/9LXVwhITM5OTE9VKE7OSM1NVEhJTcvMSy1WKMlIVfB39lTIzAVJ5qamZCYqlFQWpBbr +oKjMLMnMzytWSMxLUShLzMlMSQTxFdJK85LBEnpcBSjGcwECAAD//4+fgLBxAAAA +`, + }, + + "/fs.go": { + local: "fs.go", + size: 20244, + modtime: 1462980156, + compressed: ` +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 +`, + }, + + "/gen.go": { + local: "gen.go", + size: 187, + modtime: 1462980095, + compressed: ` +H4sIAAAJbogA/zSOQa6DMAwF95zCYvV/JZI9Etv2AD1BCA8nhcQIh0q9fYlQl/ZoNG9zfnEMUh+QXNNY +Sw9k7K6ASgCFUjZzjyueHy1IhDRimmJmcut6WTSfWKt5aAVQT3/V095ajiUco/GSbHrFUSXbk/+bWmLp ++deqUrft8V2PTmhWw3J+Fh6uadRFzrJjaM2NpSXTfAMAAP//3UYmUrsAAAA= +`, + }, + + "/image-manifest-schema.json": { + local: "image-manifest-schema.json", + size: 1064, + modtime: 1462965360, + compressed: ` +H4sIAAAJbogA/6RTvVLjMBDu/RQ7TspzdMVVaa+64oaCDA1DIeyVvZlYMlrBTCaTd0c/UZAJBSSlV/v9 +Sj5UAHWH3FqaHBldr6G+m1D/NdpJ0mjh3yh7hP9Sk0J2cD9hS4paGbd/BfiS2wFHGaCDc9NaiC0b3aTp +ythedFYq1/z+I9JskXDUZQh7jPGqbVblCEvbgoIDMZ4cJKzbTxjQ5nmL7Wk2Wc9hHSH7kxDMzxLFg2dM +4dL4MvNmIAZFuOuAU0JkcANCFIcsDokP3hIhSAapgbTDHm10EcmvSybmZs9sOWuWifNjOq5H7Ehu0sbh +Rv0PrrP20qIKXB0qbuL6KlzuQvgBaQr1cYGbWfOaivrS17fY8s2YT0l3cu/tl3S5GGmt3BftOxzLvWuF +vfTMgNTauPju+faymx35xkvKn3VeIqvsNTqtLb68ksVg6/Grv+Di5czva163/3iqjtV7AAAA///++ypf +KAQAAA== +`, + }, + + "/manifest-list-schema.json": { + local: "manifest-list-schema.json", + size: 1010, + modtime: 1462965360, + compressed: ` +H4sIAAAJbogA/6ySMU/7MBDF93yKU9rxn/o/MHWFBQnEQMWCGExyaa5q7OAzSFXV747tS0qiMIDoUqkv +fu9+7+xjBpBXyKWjzpM1+Rryhw7NtTVek0EHt63eItxrQzWyhzsKP48dllRTqZPlX8xYctlgq6O/8b5b +K7VjawpRV9ZtVeV07Yv/V0q0hfioGiwcPDaMLofRnGxyWlHEUG2PUewDhgT4Q4cxwr7usOy1zoUg5wk5 +fIkVgyY5TyFWaoo8b79piKEm3FfAUhMZfIOQCGBCABIKH5IKmkEbIONxiy6hpAl/6Kim2OfIofUwK+kn ++Zy3WJHeyInjJSC+As8AS4d1DKyw5iJ5VvHCFyoIZChuk0e+KV8fzmO+oZF2Th9Gu/PYjs/9eHQ/46a/ +XdvvKFBMWLQx1qd3zJfa1jjyd/saO7OBNZHmDt/eyWHEev7uQc+ufrbr8P8lO2WfAQAA//+46c2u8gMA +AA== +`, + }, + + "/media-types.go": { + local: "media-types.go", + size: 2348, + modtime: 1462980480, + compressed: ` +H4sIAAAJbogA/6RVTY/bNhA9W79iKiCA1ChSezXgQ7FogABJC8RBewgWG1oayexKpEBSajfF/vfOkLIs +rZ0PdPewNsmZ994M39C9KO9Fg2DLI3YiimTXa+MgiTZx3bmYPqQO/wupByfbOKJVI91xOOSl7or+vinQ +GG1svD74B2U/DGPR6L+sVgE/jtIoKgp4h5UU4B56tFBrA+6I8PvNG5Ada6GdTjibw4cjWoSSsp1QztJx +32KHyvmEP0QrK+EoXSqHphYl5pEPZvme4gMxvBNK1kibl387YGmngOST6PtWlsJJrYpRVbkuZe4l5d0U +k48/v+ScT+kVhrfyguV/MLTyCzRvOG6PRlLdnz3EgmZQc3Ow+hqPXQLkRmtX29wJkzefZf8Nvhutatk8 +h6/0CN9Z3o3uDlJh9Ry6gLAgJAOOwrBDakvAd2jL1/ukFq1FOtrYHkve70T/cXl1t9YZqZp/o83lvW/9 +HcRewKvTPb4Kjs8ZJM6upbFdthDPCXzxT7Mep4HZ++3XskUw6AajrB+BEA017SvR4TxLjRxRQTdPGUPQ +EXa9e4BQCcgalFY8XkNbwYGTB2plVA+qXPAl3Un3lJieAKgXQQv4pq3adc5Kb6NHX8JiXIP4eWppJRyM +4RzDIRnFIc+8rhcVGRQVmjzimq7NP2uatjGxpgSp8/c+JwX/SK21kEV+5V3PJiQ1VbStpwsvWhB2JLch +27AafN3jnLwWMqNRf4bSsRZaW/h4O1P73iYXGSn4j+RKa+kRzvc9bbo6iV+McQaYM2rKcGzku0UfdpD8 +uLyFNFGyJQN5lcuDieckaFwdpvDVFrK4w1BnvILtDsLPgo/5pW05haaazMXHP+yAFHDGqZ7Q2PxPI/qE +vmcQD0ocyNZO+9uF0zjEKbufBrJlkuWPSP4b/v0eazSoSnyrWRobdf9gHXZJzMOwLYoifhlsOd5mUFO/ +Nt1VpL3vRIBJQlsSKi/lx8CgHVo3V7pKPfeozaBrv7Pmeip6GtwXdgvn+k8TcO5BBuPUBgIPagIxWWVB +QHwhCtlvW3697jGZfJfBTxnQ+5BM+d5qNkmpwg2/F3cZVPQIcpoRqkF4EueJPPAO/CRUXIPNvDV9EDvT +xgEmnfROyp4YnaHYvVvwEBz6BRuvHvyVj1cnF0Ze533byYsxO9eyXcOEa3iM/gsAAP//HbnjLywJAAA= +`, + }, + + "/": { + isDir: true, + local: "/", + }, +} diff --git a/schema/gen.go b/schema/gen.go new file mode 100644 index 0000000..f19a012 --- /dev/null +++ b/schema/gen.go @@ -0,0 +1,6 @@ +package schema + +// Generates an embbedded http.FileSystem for all schema files +// using esc (https://github.com/mjibson/esc). + +//go:generate esc -private -o fs.go -pkg=schema -ignore=".*go" . diff --git a/schema/schema.go b/schema/schema.go new file mode 100644 index 0000000..9647f71 --- /dev/null +++ b/schema/schema.go @@ -0,0 +1,30 @@ +package schema + +import "net/http" + +// Media types for the OCI image formats +const ( + MediaTypeManifest Validator = `application/vnd.oci.image.manifest.v1+json` + MediaTypeManifestList Validator = `application/vnd.oci.image.manifest.list.v1+json` + MediaTypeImageSerialization unimplemented = `application/vnd.oci.image.serialization.rootfs.tar.gzip` + MediaTypeImageSerializationConfig unimplemented = `application/vnd.oci.image.serialization.config.v1+json` + MediaTypeImageSerializationCombined unimplemented = `application/vnd.oci.image.serialization.combined.v1+json` +) + +var ( + // fs stores the embedded http.FileSystem + // having the OCI JSON schema files in root "/". + fs = _escFS(false) + + // specs maps OCI schema media types to schema files. + specs = map[Validator]string{ + MediaTypeManifest: "image-manifest-schema.json", + MediaTypeManifestList: "manifest-list-schema.json", + } +) + +// FileSystem returns an in-memory file system including the schema files. +// The schema files are located at the root directory. +func FileSystem() http.FileSystem { + return fs +} diff --git a/cmd/oci-validate-examples/main.go b/schema/spec_test.go similarity index 51% rename from cmd/oci-validate-examples/main.go rename to schema/spec_test.go index 5a2d7b7..f97c41f 100644 --- a/cmd/oci-validate-examples/main.go +++ b/schema/spec_test.go @@ -1,126 +1,70 @@ -package main +package schema_test import ( "bytes" - "errors" - "flag" "fmt" "io" "io/ioutil" - "log" "net/url" "os" - "path/filepath" "strings" + "testing" + "github.com/opencontainers/image-spec/schema" + "github.com/pkg/errors" "github.com/russross/blackfriday" - "github.com/xeipuuv/gojsonschema" ) var ( - verbose bool - skipped bool - schemaPath string + errFormatInvalid = errors.New("format: invalid") ) -func init() { - flag.BoolVar(&verbose, "verbose", false, "display examples no matter what") - flag.BoolVar(&skipped, "skipped", false, "show skipped examples") - flag.StringVar(&schemaPath, "schema", "./schema", "specify location of schema directory") -} - -func main() { - flag.Parse() +// TODO(sur): include examples from all specification files +func TestSpecExamples(t *testing.T) { + m, err := os.Open("../manifest.md") + if err != nil { + t.Fatal(err) + } - examples, err := extractExamples(os.Stdin) + examples, err := extractExamples(m) if err != nil { - log.Fatalln(err) + t.Fatal(err) } - var fail bool + for _, example := range examples { if example.Err != nil { - printFields("error", example.Mediatype, example.Title, example.Err) - fail = true + printFields(t, "error", example.Mediatype, example.Title, example.Err) + t.Error(err) continue } - schema, err := schemaByMediatype(schemaPath, example.Mediatype) - if err != nil { - if err == errSchemaNotFound { - if skipped { - printFields("skip", example.Mediatype, example.Title) - - if verbose { - fmt.Println(example.Body, "---") - } - } - 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 } - // BUG(stevvooe): Recursive validation is not working. Need to - // investigate. Will use this code as information for bug. - document := gojsonschema.NewStringLoader(example.Body) - result, err := gojsonschema.Validate(schema, document) - - if err != nil { - printFields("error", example.Mediatype, example.Title, err) - fmt.Println(example.Body, "---") - fail = true + 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 } - if !result.Valid() { + for _, err := range errs { // TOOD(stevvooe): This is nearly useless without file, line no. - printFields("invalid", example.Mediatype, example.Title) - for _, desc := range result.Errors() { - printFields("reason", example.Mediatype, example.Title, desc) - } + printFields(t, "invalid", example.Mediatype, example.Title) + t.Error(err) fmt.Println(example.Body, "---") - fail = true continue } - - printFields("ok", example.Mediatype, example.Title) - if verbose { - fmt.Println(example.Body, "---") - } - } - - if fail { - os.Exit(1) } } -var ( - specsByMediaType = map[string]string{ - "application/vnd.oci.image.manifest.v1+json": "image-manifest-schema.json", - "application/vnd.oci.image.manifest.list.v1+json": "manifest-list-schema.json", - } - - errSchemaNotFound = errors.New("schema: not found") - errFormatInvalid = errors.New("format: invalid") -) - -func schemaByMediatype(root, mediatype string) (gojsonschema.JSONLoader, error) { - name, ok := specsByMediaType[mediatype] - if !ok { - return nil, errSchemaNotFound - } - - if !filepath.IsAbs(root) { - wd, err := os.Getwd() - if err != nil { - return nil, err - } - root = filepath.Join(wd, root) - } - - // lookup path - path := filepath.Join(root, name) - return gojsonschema.NewReferenceLoader("file://" + path), nil -} - // renderer allows one to incercept fenced blocks in markdown documents. type renderer struct { blackfriday.Renderer @@ -199,10 +143,10 @@ func extractExamples(rd io.Reader) ([]example, error) { } // printFields prints each value tab separated. -func printFields(vs ...interface{}) { +func printFields(t *testing.T, vs ...interface{}) { var ss []string for _, f := range vs { ss = append(ss, fmt.Sprint(f)) } - fmt.Println(strings.Join(ss, "\t")) + t.Log(strings.Join(ss, "\t")) } diff --git a/schema/validator.go b/schema/validator.go new file mode 100644 index 0000000..b0f13e0 --- /dev/null +++ b/schema/validator.go @@ -0,0 +1,58 @@ +package schema + +import ( + "fmt" + "io" + "io/ioutil" + + "github.com/pkg/errors" + "github.com/xeipuuv/gojsonschema" +) + +// Validator wraps a media type string identifier +// and implements validation against a JSON schema. +type Validator string + +// ValidationError contains all the errors that happened during validation. +type ValidationError struct { + Errs []error +} + +func (e ValidationError) Error() string { + return fmt.Sprintf("%v", e.Errs) +} + +// Validate validates the given reader against the schema of the wrapped media type. +func (v Validator) Validate(src io.Reader) error { + buf, err := ioutil.ReadAll(src) + if err != nil { + return errors.Wrap(err, "unable to read manifest") + } + + sl := gojsonschema.NewReferenceLoaderFileSystem("file:///"+specs[v], fs) + ml := gojsonschema.NewStringLoader(string(buf)) + + result, err := gojsonschema.Validate(sl, ml) + if err != nil { + return errors.Wrapf(err, "schema %s: unable to validate manifest", v) + } + + if result.Valid() { + return nil + } + + errs := make([]error, 0, len(result.Errors())) + for _, desc := range result.Errors() { + errs = append(errs, fmt.Errorf("%s", desc)) + } + + return ValidationError{ + Errs: errs, + } +} + +type unimplemented string + +func (v unimplemented) Validate(src io.Reader) error { + return fmt.Errorf("%s: unimplemented", v) +} From ec271e880f3eec688344ccb479fab6c49a00cf52 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Thu, 19 May 2016 11:31:38 -0400 Subject: [PATCH 052/245] inline images: media-types.png is inline due to pandoc pdf and html wanting a uri to reference an image, we'll need images to be inlined fo rthe document. So with this, the document will get an base64 blob of images that match the target 'inline-png-%.md' Signed-off-by: Vincent Batts --- Makefile | 6 ++++-- media-types.md | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 522fc18..867c4f3 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ DOC_FILES := \ code-of-conduct.md \ project.md \ media-types.md \ + inline-png-media-types.md \ manifest.md \ serialization.md @@ -70,11 +71,12 @@ lint: test: go test -race ./... -media-types.png: media-types.dot - %.png: %.dot dot -Tpng $^ > $@ +inline-png-%.md: %.png + @printf '$*\n' "$(shell base64 $^)" > $@ + .PHONY: \ validate-examples \ oci-image-tool \ diff --git a/media-types.md b/media-types.md index 3bf7221..9551d74 100644 --- a/media-types.md +++ b/media-types.md @@ -46,6 +46,4 @@ This section shows where the OCI Image Specification is compatible with formats The following figure shows how the above media types reference each other: -![Media Types](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. From 10e6ff7101c2b0ff76e41a4a213838953e2082d8 Mon Sep 17 00:00:00 2001 From: Jonathan Boulle Date: Thu, 19 May 2016 18:58:59 +0200 Subject: [PATCH 053/245] *: add license header checking and tweak Makefile - move file-finding logic into lint check script - move lint check script into .tool/ subdirectory (similar to opencontainers/runtime-spec) - add a simple `check-license` script to .tool/ to ensure all Go source files have an Apache license header, and wire up to `check-license` make target - invoke this from travis - add `schema-fs` target to generate schema/fs.go (including header) - add header everywhere it's missing so far - add `output/` directory to .gitignore Signed-off-by: Jonathan Boulle --- .gitignore | 3 +- .header | 14 ++ .tool/check-license | 13 ++ .tool/lint | 23 ++ .travis.yml | 1 + Makefile | 12 +- cmd/oci-image-tool/main.go | 14 ++ cmd/oci-image-tool/validate.go | 14 ++ lint | 22 -- schema/doc.go | 14 ++ schema/fs.go | 386 +++++++++++---------------------- schema/gen.go | 17 +- schema/schema.go | 14 ++ schema/spec_test.go | 14 ++ schema/validator.go | 14 ++ 15 files changed, 285 insertions(+), 290 deletions(-) create mode 100644 .header create mode 100755 .tool/check-license create mode 100755 .tool/lint delete mode 100755 lint diff --git a/.gitignore b/.gitignore index a8b3002..0248ce3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -oci-validate-examples code-of-conduct.md oci-image-tool +oci-validate-examples +output diff --git a/.header b/.header new file mode 100644 index 0000000..868470b --- /dev/null +++ b/.header @@ -0,0 +1,14 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + diff --git a/.tool/check-license b/.tool/check-license new file mode 100755 index 0000000..122a7c2 --- /dev/null +++ b/.tool/check-license @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +ret=0 + +for file in $(find . -type f -iname '*.go' ! -path './vendor/*'); do + (head -n3 "${file}" | grep -Eq "(Copyright|generated|GENERATED)") || (echo -e "${file}:missing license header" && ret=1) +done + +exit $ret diff --git a/.tool/lint b/.tool/lint new file mode 100755 index 0000000..062eb9b --- /dev/null +++ b/.tool/lint @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +if [ ! $(command -v gometalinter) ]; then + go get github.com/alecthomas/gometalinter + gometalinter --update --install +fi + +for d in $(find . -type d -not -iwholename '*.git*' -a -not -iname '.tool'); do + gometalinter \ + --exclude='error return value not checked.*(Close|Log|Print).*\(errcheck\)$' \ + --exclude='.*_test\.go:.*error return value not checked.*\(errcheck\)$' \ + --exclude='duplicate of.*_test.go.*\(dupl\)$' \ + --exclude='schema/fs.go' \ + --disable=aligncheck \ + --disable=gotype \ + --cyclo-over=20 \ + --tests \ + --deadline=10s "${d}" +done diff --git a/.travis.yml b/.travis.yml index 08fd81e..c9e05a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,5 +18,6 @@ install: true script: - git-validation -run DCO,short-subject -v -range ${TRAVIS_COMMIT_RANGE} - make lint + - make check-license - make test - make oci-image-tool diff --git a/Makefile b/Makefile index 522fc18..b7f6117 100644 --- a/Makefile +++ b/Makefile @@ -64,8 +64,17 @@ oci-validate-examples: cmd/oci-validate-examples/main.go oci-image-tool: go build ./cmd/oci-image-tool +schema-fs: + @echo "generating schema fs" + @cd schema && echo -e "$$(cat ../.header)\n\n$$(go generate)" > fs.go + +check-license: + @echo "checking license headers" + @./.tool/check-license + lint: - for d in $(shell find . -type d -not -iwholename '*.git*'); do echo "$${d}" && ./lint "$${d}"; done + @echo "checking lint" + @./.tool/lint test: go test -race ./... @@ -78,6 +87,7 @@ media-types.png: media-types.dot .PHONY: \ validate-examples \ oci-image-tool \ + check-license \ lint \ docs \ test diff --git a/cmd/oci-image-tool/main.go b/cmd/oci-image-tool/main.go index 93fabd7..9206f28 100644 --- a/cmd/oci-image-tool/main.go +++ b/cmd/oci-image-tool/main.go @@ -1,3 +1,17 @@ +// 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 ( diff --git a/cmd/oci-image-tool/validate.go b/cmd/oci-image-tool/validate.go index 5f42483..76891bb 100644 --- a/cmd/oci-image-tool/validate.go +++ b/cmd/oci-image-tool/validate.go @@ -1,3 +1,17 @@ +// 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 ( diff --git a/lint b/lint deleted file mode 100755 index 3ac5d2a..0000000 --- a/lint +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o nounset -set -o pipefail - -if [ ! $(command -v gometalinter) ] -then - go get github.com/alecthomas/gometalinter - gometalinter --update --install -fi - -gometalinter \ - --exclude='error return value not checked.*(Close|Log|Print).*\(errcheck\)$' \ - --exclude='.*_test\.go:.*error return value not checked.*\(errcheck\)$' \ - --exclude='duplicate of.*_test.go.*\(dupl\)$' \ - --exclude='schema/fs.go' \ - --disable=aligncheck \ - --disable=gotype \ - --cyclo-over=20 \ - --tests \ - --deadline=10s "${1}" diff --git a/schema/doc.go b/schema/doc.go index dd9a732..5ea5914 100644 --- a/schema/doc.go +++ b/schema/doc.go @@ -1,2 +1,16 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + // Package schema defines the OCI image media types, schema definitions and validation functions. package schema diff --git a/schema/fs.go b/schema/fs.go index c0a57bc..d8ce6c0 100644 --- a/schema/fs.go +++ b/schema/fs.go @@ -1,3 +1,17 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package schema import ( @@ -189,7 +203,7 @@ var _escData = map[string]*_escFile{ "/defs-image.json": { local: "defs-image.json", size: 3100, - modtime: 1462965360, + modtime: 1462375266, compressed: ` H4sIAAAJbogA/+xWy27bMBC86ysIJUAOfqjXGkGAoEGBnlIgPdVQgY20kphKpLqkgzqB/r3U+x3Xidte erK55A5nhmPSzxZjto/KI55qLoW9YfYNBlzwfKRYCqS5t4uBmJbsNkXxQQoNXCCxTwmEyO5S9HjAPSja @@ -208,7 +222,7 @@ dwN3Or48T12UYhfH478BrlWPMiuzfgUAAP//VjUNyBwMAAA= "/defs.json": { local: "defs.json", size: 3044, - modtime: 1462965360, + modtime: 1460709579, compressed: ` H4sIAAAJbogA/6xWT3PaPhC98yk8/H5H2tiybENvnaZ/csiEmUxPnR5cs4BakFRZ7jTN8N0rGWMse3Ew 5QBYu9r3dt+usJ5HnjdeQJ4pJjUTfPzGG9/CknFmV7lX5LDw9FqJYrUWhTaP4D1I4O8E1ynjoLxHCRlb @@ -226,262 +240,41 @@ Rn8DAAD//2VgiEzkCwAA "/doc.go": { local: "doc.go", - size: 113, - modtime: 1462979016, + size: 711, + modtime: 1463675171, compressed: ` -H4sIAAAJbogA/9LXVwhITM5OTE9VKE7OSM1NVEhJTcvMSy1WKMlIVfB39lTIzAVJ5qamZCYqlFQWpBbr -oKjMLMnMzytWSMxLUShLzMlMSQTxFdJK85LBEnpcBSjGcwECAAD//4+fgLBxAAAA -`, - }, - - "/fs.go": { - local: "fs.go", - size: 20244, - modtime: 1462980156, - compressed: ` -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 ++fwK0Y5Gz4Vs4a7sF/8CAAD//2ICNX/HAgAA `, }, "/gen.go": { local: "gen.go", - size: 187, - modtime: 1462980095, + size: 839, + modtime: 1463676428, compressed: ` -H4sIAAAJbogA/zSOQa6DMAwF95zCYvV/JZI9Etv2AD1BCA8nhcQIh0q9fYlQl/ZoNG9zfnEMUh+QXNNY -Sw9k7K6ASgCFUjZzjyueHy1IhDRimmJmcut6WTSfWKt5aAVQT3/V095ajiUco/GSbHrFUSXbk/+bWmLp -+deqUrft8V2PTmhWw3J+Fh6uadRFzrJjaM2NpSXTfAMAAP//3UYmUrsAAAA= +H4sIAAAJbogA/2SSwW7bPBCE736KhU7JD1vKn0MPKXJQk7gVGsiApTTILZS0ollTJEtSlvX2XTIKkKCG +LxR3Zr8dbpbBnTazFfzg4frq/y9QHxAehRrPsNWj6pgXWq2yjP70uUXlsAP6jhY8VeaGtVEQb9bwC60j +AVynV3ARCpLlKrn8GixmPcLAZlDaw+iQPISDXkgEPLdoPAgFrR6MFEy1CJPwh9hncUmDx8vioRvPqJyR +wNCp/1gIzC/Q4Xfw3txk2TRNKYvAqbY8k2+lLnss7h7K6mFD0IvoSUl0Diz+GYWlgZsZmCGoljWEKtkE +2gLjFunO6wA9WeGF4mtwuvcTsxhsOuG8Fc3oP2X2jkiTfyyg1JiCJK+gqBL4lldFtQ4mz0X9Y/dUw3O+ +3+dlXTxUsNvD3a68L+piV9JpC3n5Aj+L8n4NSIlRHzwbGyYgTBHSxC5GVyF+Quj1G5Iz2IpetDSa4iPj +CFyf0CqaCAzaQbjwqo4Au2AjxSB83Az371zpakUZH4OJo6gHtgqS76jQMo/BA3BoGuw6Gjq8TLql969m +53GIQEzKRRk3wwX56AIKuhYugsTRa3KadGxS2pZs+C0ap1VG95dpbFeHvXIHPcoOeGwt5QwNha5O+kiN +42a9Duz4Trnp3Sspub7hC2pstzFWnMJhY478dsHaCK60xdsk/Y/rBNLV3wAAAP//Xhj9JUcDAAA= `, }, "/image-manifest-schema.json": { local: "image-manifest-schema.json", size: 1064, - modtime: 1462965360, + modtime: 1462375266, compressed: ` H4sIAAAJbogA/6RTvVLjMBDu/RQ7TspzdMVVaa+64oaCDA1DIeyVvZlYMlrBTCaTd0c/UZAJBSSlV/v9 Sj5UAHWH3FqaHBldr6G+m1D/NdpJ0mjh3yh7hP9Sk0J2cD9hS4paGbd/BfiS2wFHGaCDc9NaiC0b3aTp @@ -496,7 +289,7 @@ KAQAAA== "/manifest-list-schema.json": { local: "manifest-list-schema.json", size: 1010, - modtime: 1462965360, + modtime: 1462375266, compressed: ` H4sIAAAJbogA/6ySMU/7MBDF93yKU9rxn/o/MHWFBQnEQMWCGExyaa5q7OAzSFXV747tS0qiMIDoUqkv fu9+7+xjBpBXyKWjzpM1+Rryhw7NtTVek0EHt63eItxrQzWyhzsKP48dllRTqZPlX8xYctlgq6O/8b5b @@ -508,26 +301,89 @@ AA== `, }, - "/media-types.go": { - local: "media-types.go", - size: 2348, - modtime: 1462980480, + "/schema.go": { + local: "schema.go", + size: 1719, + modtime: 1463675171, + compressed: ` +H4sIAAAJbogA/6RUUW/bNhB+jn7FQU8JZlNtH/aQIQ+eG2PaUhuI3BbFMKy0dJK5SaRGUnbUof99d5Ts +OnFQDK3gF5N333333cdLEpibtreq2np49eLlj7DeItwp3T3AwnS6kF4ZHSUJ/eg4R+2wADpHC54iZ63M +Q0K4mcA7tI4S4JV4AZccEI9X8dVPDNGbDhrZgzYeOoeEoRyUqkbAhxxbD0pDbpq2VlLnCHvlt6HOiCIY +48OIYTZeUrikhJb+laeBIP1Imr+t9+11kuz3eyEDYWFsldRDqEvu0vntMrudEukx6a2u0Tmw+E+nLDW8 +6UG2RCqXG6Jayz0YC7KySHfeMOm9VV7pagLOlH4vLTJMoZy3atP5R5odKFLnpwGkmtQQzzJIsxh+nmVp +NmGQ9+n6l9XbNbyf3d/Pluv0NoPVPcxXy9fpOl0t6d8CZssP8Fu6fD0BJMWoDj60ljsgmorVxCJIlyE+ +olCagZJrMVelyqk1XXWyQqjMDq2mjqBF2yjHU3VEsGCYWjXKB2e4875EFJHGfzOII6kbGUVEwVgPsUaf +8CziiFHeYKEk+L5FdySymqdEmHPppJHeRTkV8XAZXYTwNUW/kVqVSIfn3ztZK7IsgfF3Ax/HqTHVZKcL +YXIlAr5oRhSxe/nDX87oj89UuFNnVb6lQq2eL5NyWIZWEeangHAs02keGzao2RpfLeNOAYQ1xpdOeGlF +9Um1Xy83N7pU1feUywPC/2tubpqN0uEpfHO1AeFLvaso2knL9iBDlQ4cTYbsxFbCZoNFQQXYcWJBOybr +nccmhG7ljr19sNyv2Wo5ujVsI8dPmqWEOIlFdEHIN/AnunyRXZaydkh1GYbfjaNl1LoAMyI0J8am7XCK +S1hDzg1n/X500x+8B3T1b3Rx7sLr4Ig4CDI9mGo6oAqWIZ48l8bmvYb4mMAufJr1mQWkPr6oQzvPdzY8 +ddJg2mBjbD9saDcEKJ3XXXFQ73FzBLV+cgi0C6E2NF0ahfQhKShb0GbNqfVeRGWn8xMOl1dPZwYkzECM +hhx9jv4LAAD//6aSebu3BgAA +`, + }, + + "/spec_test.go": { + local: "spec_test.go", + size: 4209, + modtime: 1463675171, + compressed: ` +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 +ppaZyd9DMuLC/K8AAAD//0k/ScFxEAAA +`, + }, + + "/validator.go": { + local: "validator.go", + size: 1894, + modtime: 1463675171, compressed: ` -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 `, }, diff --git a/schema/gen.go b/schema/gen.go index f19a012..e2f8e74 100644 --- a/schema/gen.go +++ b/schema/gen.go @@ -1,6 +1,21 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package schema // Generates an embbedded http.FileSystem for all schema files // using esc (https://github.com/mjibson/esc). -//go:generate esc -private -o fs.go -pkg=schema -ignore=".*go" . +// This should generally be invoked with `make schema-fs` +//go:generate esc -private -pkg=schema -ignore=".*go" . diff --git a/schema/schema.go b/schema/schema.go index 9647f71..e6f87fe 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -1,3 +1,17 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package schema import "net/http" diff --git a/schema/spec_test.go b/schema/spec_test.go index f97c41f..afae60d 100644 --- a/schema/spec_test.go +++ b/schema/spec_test.go @@ -1,3 +1,17 @@ +// 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 ( diff --git a/schema/validator.go b/schema/validator.go index b0f13e0..900edcd 100644 --- a/schema/validator.go +++ b/schema/validator.go @@ -1,3 +1,17 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package schema import ( From ac1797ef47bc66b949984dd27d20442a353649f2 Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Thu, 19 May 2016 11:06:18 -0700 Subject: [PATCH 054/245] schema: remove *.go files from embedded fs Currently *.go files are encoded in the embedded schema file system. This fixes the ignore pattern for esc for go generate. Fixes #79 Signed-off-by: Sergiusz Urbaniak --- schema/fs.go | 127 ++------------------------------------------------ schema/gen.go | 2 +- 2 files changed, 5 insertions(+), 124 deletions(-) diff --git a/schema/fs.go b/schema/fs.go index d8ce6c0..4672497 100644 --- a/schema/fs.go +++ b/schema/fs.go @@ -203,7 +203,7 @@ var _escData = map[string]*_escFile{ "/defs-image.json": { local: "defs-image.json", size: 3100, - modtime: 1462375266, + modtime: 1462965360, compressed: ` H4sIAAAJbogA/+xWy27bMBC86ysIJUAOfqjXGkGAoEGBnlIgPdVQgY20kphKpLqkgzqB/r3U+x3Xidte erK55A5nhmPSzxZjto/KI55qLoW9YfYNBlzwfKRYCqS5t4uBmJbsNkXxQQoNXCCxTwmEyO5S9HjAPSja @@ -222,7 +222,7 @@ dwN3Or48T12UYhfH478BrlWPMiuzfgUAAP//VjUNyBwMAAA= "/defs.json": { local: "defs.json", size: 3044, - modtime: 1460709579, + modtime: 1463695223, compressed: ` H4sIAAAJbogA/6xWT3PaPhC98yk8/H5H2tiybENvnaZ/csiEmUxPnR5cs4BakFRZ7jTN8N0rGWMse3Ew 5QBYu9r3dt+usJ5HnjdeQJ4pJjUTfPzGG9/CknFmV7lX5LDw9FqJYrUWhTaP4D1I4O8E1ynjoLxHCRlb @@ -238,43 +238,10 @@ Rn8DAAD//2VgiEzkCwAA `, }, - "/doc.go": { - local: "doc.go", - size: 711, - modtime: 1463675171, - compressed: ` -H4sIAAAJbogA/2SSMW/bMBCFd/+KB08t4Epphg7tpDoJKjSQC8tpkJGmTvKhEsmSVGT/+x5lB4hRwwt5 -795998Q8x9q6k+fuEHF78/kLdgfCI5vxiAc7mkZFtmaR5/KXa00mUAO5J48oysIpPTfMlRV+kw/SgNvs -Bh+SYHkpLT9+SxYnO2JQJxgbMQYSDw5ouSfQUZOLYANtB9ezMpowcTzMcy4uWfJ4uXjYfVQiV9Lg5NS+ -F0LFC3T6HWJ0X/N8mqZMzcCZ9V3en6UhfyzX91V9/0mgL01PpqcQ4OnvyF4W3p+gnEBptRfUXk2wHqrz -JLVoE/TkObLpVgi2jZPylGwaDtHzfoxXmb0hyubvBZKaMlgWNcp6ie9FXdarZPJc7n5snnZ4LrbbotqV -9zU2W6w31V25KzeVnB5QVC/4WVZ3K5AkJnPo6HzaQDA5pUnNHF1NdIXQ2jNScKS5ZS2rmW5UHaGzr+SN -bARHfuCQvmoQwCbZ9DxwnF9G+H+vbJEkv5T+k3yCpD0oNNSyoTDrNutSoFJxoIYV4slRWF0pOb6Nw6vq -+fwK0Y5Gz4Vs4a7sF/8CAAD//2ICNX/HAgAA -`, - }, - - "/gen.go": { - local: "gen.go", - size: 839, - modtime: 1463676428, - compressed: ` -H4sIAAAJbogA/2SSwW7bPBCE736KhU7JD1vKn0MPKXJQk7gVGsiApTTILZS0ollTJEtSlvX2XTIKkKCG -LxR3Zr8dbpbBnTazFfzg4frq/y9QHxAehRrPsNWj6pgXWq2yjP70uUXlsAP6jhY8VeaGtVEQb9bwC60j -AVynV3ARCpLlKrn8GixmPcLAZlDaw+iQPISDXkgEPLdoPAgFrR6MFEy1CJPwh9hncUmDx8vioRvPqJyR -wNCp/1gIzC/Q4Xfw3txk2TRNKYvAqbY8k2+lLnss7h7K6mFD0IvoSUl0Diz+GYWlgZsZmCGoljWEKtkE -2gLjFunO6wA9WeGF4mtwuvcTsxhsOuG8Fc3oP2X2jkiTfyyg1JiCJK+gqBL4lldFtQ4mz0X9Y/dUw3O+ -3+dlXTxUsNvD3a68L+piV9JpC3n5Aj+L8n4NSIlRHzwbGyYgTBHSxC5GVyF+Quj1G5Iz2IpetDSa4iPj -CFyf0CqaCAzaQbjwqo4Au2AjxSB83Az371zpakUZH4OJo6gHtgqS76jQMo/BA3BoGuw6Gjq8TLql969m -53GIQEzKRRk3wwX56AIKuhYugsTRa3KadGxS2pZs+C0ap1VG95dpbFeHvXIHPcoOeGwt5QwNha5O+kiN -42a9Duz4Trnp3Sspub7hC2pstzFWnMJhY478dsHaCK60xdsk/Y/rBNLV3wAAAP//Xhj9JUcDAAA= -`, - }, - "/image-manifest-schema.json": { local: "image-manifest-schema.json", size: 1064, - modtime: 1462375266, + modtime: 1462965360, compressed: ` H4sIAAAJbogA/6RTvVLjMBDu/RQ7TspzdMVVaa+64oaCDA1DIeyVvZlYMlrBTCaTd0c/UZAJBSSlV/v9 Sj5UAHWH3FqaHBldr6G+m1D/NdpJ0mjh3yh7hP9Sk0J2cD9hS4paGbd/BfiS2wFHGaCDc9NaiC0b3aTp @@ -289,7 +256,7 @@ KAQAAA== "/manifest-list-schema.json": { local: "manifest-list-schema.json", size: 1010, - modtime: 1462375266, + modtime: 1462965360, compressed: ` H4sIAAAJbogA/6ySMU/7MBDF93yKU9rxn/o/MHWFBQnEQMWCGExyaa5q7OAzSFXV747tS0qiMIDoUqkv fu9+7+xjBpBXyKWjzpM1+Rryhw7NtTVek0EHt63eItxrQzWyhzsKP48dllRTqZPlX8xYctlgq6O/8b5b @@ -301,92 +268,6 @@ AA== `, }, - "/schema.go": { - local: "schema.go", - size: 1719, - modtime: 1463675171, - compressed: ` -H4sIAAAJbogA/6RUUW/bNhB+jn7FQU8JZlNtH/aQIQ+eG2PaUhuI3BbFMKy0dJK5SaRGUnbUof99d5Ts -OnFQDK3gF5N333333cdLEpibtreq2np49eLlj7DeItwp3T3AwnS6kF4ZHSUJ/eg4R+2wADpHC54iZ63M -Q0K4mcA7tI4S4JV4AZccEI9X8dVPDNGbDhrZgzYeOoeEoRyUqkbAhxxbD0pDbpq2VlLnCHvlt6HOiCIY -48OIYTZeUrikhJb+laeBIP1Imr+t9+11kuz3eyEDYWFsldRDqEvu0vntMrudEukx6a2u0Tmw+E+nLDW8 -6UG2RCqXG6Jayz0YC7KySHfeMOm9VV7pagLOlH4vLTJMoZy3atP5R5odKFLnpwGkmtQQzzJIsxh+nmVp -NmGQ9+n6l9XbNbyf3d/Pluv0NoPVPcxXy9fpOl0t6d8CZssP8Fu6fD0BJMWoDj60ljsgmorVxCJIlyE+ -olCagZJrMVelyqk1XXWyQqjMDq2mjqBF2yjHU3VEsGCYWjXKB2e4875EFJHGfzOII6kbGUVEwVgPsUaf -8CziiFHeYKEk+L5FdySymqdEmHPppJHeRTkV8XAZXYTwNUW/kVqVSIfn3ztZK7IsgfF3Ax/HqTHVZKcL -YXIlAr5oRhSxe/nDX87oj89UuFNnVb6lQq2eL5NyWIZWEeangHAs02keGzao2RpfLeNOAYQ1xpdOeGlF -9Um1Xy83N7pU1feUywPC/2tubpqN0uEpfHO1AeFLvaso2knL9iBDlQ4cTYbsxFbCZoNFQQXYcWJBOybr -nccmhG7ljr19sNyv2Wo5ujVsI8dPmqWEOIlFdEHIN/AnunyRXZaydkh1GYbfjaNl1LoAMyI0J8am7XCK -S1hDzg1n/X500x+8B3T1b3Rx7sLr4Ig4CDI9mGo6oAqWIZ48l8bmvYb4mMAufJr1mQWkPr6oQzvPdzY8 -ddJg2mBjbD9saDcEKJ3XXXFQ73FzBLV+cgi0C6E2NF0ahfQhKShb0GbNqfVeRGWn8xMOl1dPZwYkzECM -hhx9jv4LAAD//6aSebu3BgAA -`, - }, - - "/spec_test.go": { - local: "spec_test.go", - size: 4209, - modtime: 1463675171, - compressed: ` -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 -ppaZyd9DMuLC/K8AAAD//0k/ScFxEAAA -`, - }, - - "/validator.go": { - local: "validator.go", - size: 1894, - modtime: 1463675171, - compressed: ` -H4sIAAAJbogA/4xU0W7bNhR9tr7iTkABCdOkrA97yJAHL00wb4EDRGmDoigGWr6SuUikRlJ2jKH/vkNK -ju12Dw0C2xLvPfecw0MWBV3rfm9ks3H09uLnX+hxw3Qn1fBCt3pQa+GkVlFR4B+vK1aW14T3bMihct6L -KjSElYw+sLFooLf5BSW+IJ6W4vRXD7HXA3ViT0o7GiwDQ1qqZcvELxX3jqSiSnd9K4WqmHbSbcKcCSX3 -GB8nDL1yAuUCDT2e6tNCEm4i7f82zvWXRbHb7XIRCOfaNEU7ltribnF9syxvfgLpqem9atlaMvzPIA0E -r/YkepCqxApUW7EjbUg0hrHmtCe9M9JJ1WRkde12wrCHWUvrjFwN7syzA0UoPy2Aa0JRPC9pUcb027xc -lJkHeVo8/n7//pGe5g8P8+Xj4qak+we6vl++Wzwu7pd4uqX58iP9uVi+y4jhGObwS2+8AtCU3k1eB+tK -5jMKtR4p2Z4rWcsK0lQziIap0Vs2CoqoZ9NJ63fVguDaw7Syky4kw36rK48iePzsQSys7kQUgYI2jpJo -Ftedi/El9fhZSD042cYRnhpQH1Y5tr/on5uCjdHGxucLLyz7YdgWjf7bYnrAj6M08qw+iFYir5C0M6IH -Wep4LQW5fQ8q8Bli5JqVg1I2vgNygj3c4a2l7Qjg8ysaRMs6YPxR3i8nIXkUoI5zRtDT4ei98byRSeXT -CRZtG9wZ5eCncLRBmFhhz9dDYHUcfD7iFQ2DhsrRv9EMz5Y+fQ5o0ZcoqgdVUfJNR0rhK0kPwtFq2A1G -EXYgL3u8dHUSv9nGyEzuUVMPd1TCB1ZsA/9GblnhRAi/3Qd7QnaCN4fz563voexofT5x3B6NS1+HJNZU -JHX+EHDT0SVPdjXUmX+iyysaMxJq5m3rW9JoJuuw/MMVKdn6joO+0ej8CUQS/M4oHlQ4tjionj7uDiVr -ti4GCiTPbOuHnCYqX/LugWs2jEvoTntqt7ijyr113CWxv69wnRTxj/7c2E/bzxnV8G/W/S9SGXZghEnG -7UggL0VqwdkOrXtVetZ69KjNqGu/U3M9iZ625Y29pKP+w5YePchoO9kA8JHNOBjRORmAeWMV+/yBaSee -OZlymNFFRi2rZOoP0bNJCoUzf8P8ldGabeXbDO4Xpq/qwqAAfEXhZKy9BpuFqIYin1QbjzDpxHdi9lXw -PZRP8yUFCF+K4nCoBvV61hHQw9k9hPNs9TsCenKajhQvz2FGd79E/wUAAP//3Fs/YGYHAAA= -`, - }, - "/": { isDir: true, local: "/", diff --git a/schema/gen.go b/schema/gen.go index e2f8e74..c3ff577 100644 --- a/schema/gen.go +++ b/schema/gen.go @@ -18,4 +18,4 @@ package schema // using esc (https://github.com/mjibson/esc). // This should generally be invoked with `make schema-fs` -//go:generate esc -private -pkg=schema -ignore=".*go" . +//go:generate esc -private -pkg=schema -ignore=.*go . From f06beeb796871d5d5fc6030ae9475c36a5887352 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 19 May 2016 15:13:40 -0700 Subject: [PATCH 055/245] MAINTAINERS_GUIDE: Remove trailing whitespace And excessive trailing newlines. These came in with fcc7f421 (Add contributing and maintainer guidelines, 2016-05-03, #1), because we don't have Git validation yet [1]. [1]: https://github.com/opencontainers/project-template/issues/2 Signed-off-by: W. Trevor King --- MAINTAINERS_GUIDE.md | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/MAINTAINERS_GUIDE.md b/MAINTAINERS_GUIDE.md index 16a6367..67a8038 100644 --- a/MAINTAINERS_GUIDE.md +++ b/MAINTAINERS_GUIDE.md @@ -50,7 +50,7 @@ All decisions affecting this project, big and small, follow the same 3 steps: * Step 2: Discuss the pull request. Anyone can do this. -* Step 3: Accept (`LGTM`) or refuse a pull request. The relevant maintainers do +* Step 3: Accept (`LGTM`) or refuse a pull request. The relevant maintainers do this (see below "Who decides what?") ### I'm a maintainer, should I make pull requests too? @@ -62,27 +62,27 @@ made through a pull request. All decisions are pull requests, and the relevant maintainers make decisions by accepting or refusing the pull request. Review and acceptance -by anyone is denoted by adding a comment in the pull request: `LGTM`. +by anyone is denoted by adding a comment in the pull request: `LGTM`. However, only currently listed `MAINTAINERS` are counted towards the required two LGTMs. Overall the maintainer system works because of mutual respect across the maintainers of the project. The maintainers trust one another to make decisions -in the best interests of the project. Sometimes maintainers can disagree and +in the best interests of the project. Sometimes maintainers can disagree and this is part of a healthy project to represent the point of views of various people. -In the case where maintainers cannot find agreement on a specific change the -role of a Chief Maintainer comes into play. +In the case where maintainers cannot find agreement on a specific change the +role of a Chief Maintainer comes into play. -The Chief Maintainer for the project is responsible for overall architecture -of the project to maintain conceptual integrity. Large decisions and -architecture changes should be reviewed by the chief maintainer. -The current chief maintainer for the project is the first person listed -in the MAINTAINERS file. +The Chief Maintainer for the project is responsible for overall architecture +of the project to maintain conceptual integrity. Large decisions and +architecture changes should be reviewed by the chief maintainer. +The current chief maintainer for the project is the first person listed +in the MAINTAINERS file. Even though the maintainer system is built on trust, if there is a conflict -with the chief maintainer on a decision, their decision can be challenged -and brought to the technical oversight board if two-thirds of the -maintainers vote for an appeal. It is expected that this would be a +with the chief maintainer on a decision, their decision can be challenged +and brought to the technical oversight board if two-thirds of the +maintainers vote for an appeal. It is expected that this would be a very exceptional event. @@ -90,15 +90,15 @@ very exceptional event. The best maintainers have a vested interest in the project. Maintainers are first and foremost contributors that have shown they are committed to -the long term success of the project. Contributors wanting to become -maintainers are expected to be deeply involved in contributing code, +the long term success of the project. Contributors wanting to become +maintainers are expected to be deeply involved in contributing code, pull request review, and triage of issues in the project for more than two months. -Just contributing does not make you a maintainer, it is about building trust +Just contributing does not make you a maintainer, it is about building trust with the current maintainers of the project and being a person that they can depend on and trust to make decisions in the best interest of the project. The final vote to add a new maintainer should be approved by over 66% of the current -maintainers with the chief maintainer having veto power. In case of a veto, +maintainers with the chief maintainer having veto power. In case of a veto, conflict resolution rules expressed above apply. The voting period is five business days on the Pull Request to add the new maintainer. @@ -116,6 +116,3 @@ a vote by 66% of the current maintainers with the chief maintainer having veto p The voting period is ten business days. Issues related to a maintainer's performance should be discussed with them among the other maintainers so that they are not surprised by a pull request removing them. - - - From 05483612ef81d0326ae372cb69d6730303177948 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 19 May 2016 15:18:42 -0700 Subject: [PATCH 056/245] CONTRIBUTING: Make leader-issues optional For small projects like ocitools a strict requirement would just be busywork. And the risk here (a submitted PR that would have been rejected or redirected in a leader issue) is a risk assumed by the submitter (who sunk time in the wrong direction) and not much cost to the maintainers. Signed-off-by: W. Trevor King --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 385fba5..c6aa0d5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,6 +24,10 @@ Fork the repo and make changes on your fork in a feature branch: - If it's a feature branch, create an enhancement issue to announce your intentions, and name it XXX-something where XXX is the number of the issue. +Small changes or changes that have been discussed on the project mailing list +may be submitted without a leader issue, in which case you are free to name +your branch however you like. + Submit unit tests for your changes. Go has a great test framework built in; use it! Take a look at existing tests for inspiration. Run the full test suite on your branch before submitting a pull request. From 06827b388768f5357639c749847f2b5a432e860f Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 19 May 2016 15:39:25 -0700 Subject: [PATCH 057/245] CONTRIBUTING: Don't specify a 50-char limit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Folks have been dragging this around from the soft limit in git-commit(1) and git.git's Documentation/SubmittingPatches without believing it. Looking at the git.git history through v2.3.4 (git log --no-merges --format=%s v2.3.4), we have 29853 commits, with 56% ≤ 50 chars and 94% ≤ 70 chars. Projects that want limits should enforce them with CI tests (e.g. runtime-spec uses git-validation, which has a soft limit at 72 and a hard limit at 90 [1]). [1]: https://github.com/vbatts/git-validation/blob/be3aee994370184fd98e455abfe0948d6f45f793/rules/shortsubject/shortsubject.go#L24-L35 Signed-off-by: W. Trevor King --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 385fba5..9b5337b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,8 +42,8 @@ reference to all the issues that they address. Pull requests must not contain commits from other users or branches. -Commit messages must start with a capitalized and short summary (max. 50 -chars) written in the imperative, followed by an optional, more detailed +Commit messages must start with a capitalized and short summary +written in the imperative, followed by an optional, more detailed explanatory text which is separated from the summary by an empty line. Code review comments may be added to your pull request. Discuss, then make the From dbfd5eea1593d360871401b075aaba40978f11c8 Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Thu, 19 May 2016 14:54:27 -0700 Subject: [PATCH 058/245] schema: add schema for config/image JSON Signed-off-by: Sergiusz Urbaniak --- schema/config-schema.json | 33 +++++++++++++++ schema/defs-config.json | 84 +++++++++++++++++++++++++++++++++++++++ schema/defs.json | 8 ++++ 3 files changed, 125 insertions(+) create mode 100644 schema/config-schema.json create mode 100644 schema/defs-config.json diff --git a/schema/config-schema.json b/schema/config-schema.json new file mode 100644 index 0000000..da64dbf --- /dev/null +++ b/schema/config-schema.json @@ -0,0 +1,33 @@ +{ + "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/defs-config.json b/schema/defs-config.json new file mode 100644 index 0000000..2faf3c6 --- /dev/null +++ b/schema/defs-config.json @@ -0,0 +1,84 @@ +{ + "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": { + "type": "array", + "items": { + "type": "string" + } + }, + "Cmd": { + "type": "array", + "items": { + "type": "string" + } + }, + "Volumes": { + "$ref": "defs.json#/definitions/mapStringObject" + }, + "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.json b/schema/defs.json index 85f62da..4eccc5e 100644 --- a/schema/defs.json +++ b/schema/defs.json @@ -79,6 +79,14 @@ } } }, + "mapStringObject": { + "type": "object", + "patternProperties": { + ".{1,}": { + "type": "object" + } + } + }, "UID": { "$ref": "#/definitions/uint32" }, From 8be63a2ada6ec5fc71c22d4f62697a81e339fd14 Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Thu, 19 May 2016 15:09:45 -0700 Subject: [PATCH 059/245] schema: generate embedded fs Signed-off-by: Sergiusz Urbaniak --- schema/fs.go | 54 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/schema/fs.go b/schema/fs.go index 4672497..53ad3e3 100644 --- a/schema/fs.go +++ b/schema/fs.go @@ -200,6 +200,34 @@ func _escFSMustString(useLocal bool, name string) string { var _escData = map[string]*_escFile{ + "/config-schema.json": { + local: "config-schema.json", + size: 707, + modtime: 1463700693, + compressed: ` +H4sIAAAJbogA/5SRPW7DMAyF5/oUhpOxjjp0ytoDdOgJVJmKGcCiQDJDUPju1U/c2kvhLobx+L73JOqr +adtuAHGMUZFCd2679wjhjYJaDMBt+vN4aT8iOPTobHE9Z+woboTJZmRUjWdjrkKhr+qJ+GIGtl77l1dT +tUPlcFgQSQylNre0ScGq2+BkL2Bc6a+k3iNklj6v4LRqkVMCK4KkSb5O0hyDVRh+hBUqyhhqXNE98WQ1 +T4aE9IoTdGU2V0tnbzoS/xG1dbMbUdPhbgx7GZK9zscuVu4jgy+HBy99HZ/yKxxMUjBgfi1ZdrjJYiL1 +8v+sB7fJGlGU+F7CnlZ3sMz2/rvrtJhp3bi7c8l/cHOzfOdmbr4DAAD//1EkCUvDAgAA +`, + }, + + "/defs-config.json": { + local: "defs-config.json", + size: 1755, + modtime: 1463700704, + compressed: ` +H4sIAAAJbogA/7xUzc7TMBA8N09hBY6BXhAHri1HVKQIOFZusm63xF5rvQEi1HfHSauS/qRKKV8PVeOx +Z2bXY/t3olRaQigYvSC59INK52DQYTsKymsWLOpKsxJSCw9uRk40OmAVvwyuVe6hQIOF7vjZXvCoEAVb +jwgW3fLjOCLSeGgNabWFQjpqh3smD9EXQm91xL8E4BOkpxGE0a3T49Qu+8v7BJa4OWe+ZjAtMxYb3m4D +uVfTXt1TdPL+3S29/Kf2/09z5ut8o/ms5YckP/7yFKD8TCz3qlrt825DF/toruu7H0NpaGbdpFl/CgXs +eRk38otWA6bCjafY9vO9Z7Z8vulXqmp797EYFeA34u9xyRzH36qk/3/QSplITHjkZpdozBLLiy5ffnsr +3QAP+o7rf4NBTh+YuzegYNACg8frUEeWTCYRNcRWS5d+JL0RtHA9YF3Lhv7pyTzUs1xdPJuj2GQtDN/Q +W1SwXppll8oQfUVUgXaDqSTtb5f8CQAA//8Cok052wYAAA== +`, + }, + "/defs-image.json": { local: "defs-image.json", size: 3100, @@ -221,20 +249,20 @@ dwN3Or48T12UYhfH478BrlWPMiuzfgUAAP//VjUNyBwMAAA= "/defs.json": { local: "defs.json", - size: 3044, - modtime: 1463695223, + size: 3193, + modtime: 1463700693, compressed: ` -H4sIAAAJbogA/6xWT3PaPhC98yk8/H5H2tiybENvnaZ/csiEmUxPnR5cs4BakFRZ7jTN8N0rGWMse3Ew -5QBYu9r3dt+usJ5HnjdeQJ4pJjUTfPzGG9/CknFmV7lX5LDw9FqJYrUWhTaP4D1I4O8E1ynjoLxHCRlb -siwtwyd7vBrA4FkKY2RcT+uVWesnCZbN2GEFqowsHVsTuy22xvcqINOjOf1dmQOSlMbdpEYO4qHQIUli -DNzaO/AhGQpPAprQaRhTjKN2dohiOpRoRkgYJsQP42lEkyT2fR9hRHY51MUF3cF4SBR1cAf3BgOOoyjs -Qg/uCwZNyYzO4oTMuviD24HhB1NK44RSPwkTfxZFBBM/iOfC4qomoeDwsDSGL5XBq12l+38F1jv+76Zx -4G4qyeuNuwkefaiGF5tNY3f19BXR4poZGmWvmmGuFeOr4RkeOPbx181pm8rHEnb/jY2S+PYdMn2cJJlq -kz+fKyFBaQZ5I8i4Xz8Hk51j6ith1Pw9JPX57raZyOkOmbPlBH68NPCtUunTw9LE55gEqXUfFWAatq2q -cSqbD1phxbcX/UJKXFOX5wPbwDzVa4yhGXfY/57/elnAVvIOwCchfjQR5IkpkW5SPWx1CdjcG5lW+Xk4 -WNZtNHDK7wGzOr0wxBWfFeSqM1UqjDLe3d6nUrZO8akGrEWundPSQ9k8MW3JssMl6xpgOfsDF6KgityL -gutz1MhFobIzqfsH0txTNeNpdU/9Zzgh3StqL9Q5I16N37B/53pKFfwsmIKF87Jyap50RG2Tu++hkf3s -Rn8DAAD//2VgiEzkCwAA +H4sIAAAJbogA/7RWTXPaMBC98ys8tEfa2PIX9NYp/cghAzOZnjo9uGYBtSCpstxpmuG/VzLGWPZiMKWH +JPau9r23T6tYzwPHGS4gSyUVinI2fOMMp7CkjJq3zMkzWDhqLXm+WvNc6UdwZgLYO85UQhlI51FASpc0 +TYry0R6vAtB4hkIHKVPj6k2/qycBhk3HYQWyqCwSW127zbc698oj42M4+V2GPRIXwd2oQvaivtA+iSMM +3MRb8D7pC0+8IA7GfhRgHFWyRRQFfYkmhPh+TFw/GodBHEeu6yKMyCqLOr9idzAeEoYt3N57gwFHYei3 +oXvvCwYdkEkwiWIyaeP33g4M3xsHQRQHgRv7sTsJQ4KZ70VzbnBlnZAzmC114EsZcKpUkX4pwWSHL+5q +B+6utLxauBvh1YduWL7Z1FaXT18RL26pUDt7U4WZkpSt+is8cOzrb6tpm4jHAnb/Gxsl/u07pOo4SSJR +Wj+bSy5AKgpZrUinXz97o50V6mphUP/bEjXbU/9nUSXWGVGf76d1IafHRh94q/DjtYVvpUyeZktdn2EW +JCZ9dIAq2Da6xqmMHrTDkm9v/ZWU+EbbPB/oBuaJWmMM9brD+vfs13kDG+ItgE+c/6gjiBNTImxRHWxV +C9hh1DatsstwMNVNNLDa7wAzPp0Z4pLPGHLTmSocRhnvpw+JEI1/Lac2YM0zZZ2WDsr6iWlalh5ufrcA +y+gfuBIFdeSB50xd4kbGc5leSN09kPryrChLysvzP8NxYd+bO6EuGfFy/Pp9MqoplfAzpxIW1hfU6nnU +MrVJbn8cB+ZnN/gbAAD//0JyEpx5DAAA `, }, From 9e48c45833c413ccae50f7151ae78426da2e82ba Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Thu, 19 May 2016 14:59:21 -0700 Subject: [PATCH 060/245] schema: add config/image JSON validator This implements the validation for the config/image JSON format. Signed-off-by: Sergiusz Urbaniak --- schema/schema.go | 7 ++++--- schema/spec_test.go | 16 ++++++++++++++-- serialization.md | 2 +- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/schema/schema.go b/schema/schema.go index e6f87fe..7f9b9f2 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -21,7 +21,7 @@ const ( MediaTypeManifest Validator = `application/vnd.oci.image.manifest.v1+json` MediaTypeManifestList Validator = `application/vnd.oci.image.manifest.list.v1+json` MediaTypeImageSerialization unimplemented = `application/vnd.oci.image.serialization.rootfs.tar.gzip` - MediaTypeImageSerializationConfig unimplemented = `application/vnd.oci.image.serialization.config.v1+json` + MediaTypeImageSerializationConfig Validator = `application/vnd.oci.image.serialization.config.v1+json` MediaTypeImageSerializationCombined unimplemented = `application/vnd.oci.image.serialization.combined.v1+json` ) @@ -32,8 +32,9 @@ var ( // specs maps OCI schema media types to schema files. specs = map[Validator]string{ - MediaTypeManifest: "image-manifest-schema.json", - MediaTypeManifestList: "manifest-list-schema.json", + MediaTypeManifest: "image-manifest-schema.json", + MediaTypeManifestList: "manifest-list-schema.json", + MediaTypeImageSerializationConfig: "config-schema.json", } ) diff --git a/schema/spec_test.go b/schema/spec_test.go index afae60d..885c048 100644 --- a/schema/spec_test.go +++ b/schema/spec_test.go @@ -33,9 +33,17 @@ var ( errFormatInvalid = errors.New("format: invalid") ) +func TestValidateManifest(t *testing.T) { + validate(t, "../manifest.md") +} + +func TestValidateSerialization(t *testing.T) { + validate(t, "../serialization.md") +} + // TODO(sur): include examples from all specification files -func TestSpecExamples(t *testing.T) { - m, err := os.Open("../manifest.md") +func validate(t *testing.T, name string) { + m, err := os.Open(name) if err != nil { t.Fatal(err) } @@ -46,6 +54,10 @@ func TestSpecExamples(t *testing.T) { } for _, example := range examples { + if example.Err == errFormatInvalid { // ignore + continue + } + if example.Err != nil { printFields(t, "error", example.Mediatype, example.Title, example.Err) t.Error(err) diff --git a/serialization.md b/serialization.md index 32b45fa..1a15117 100644 --- a/serialization.md +++ b/serialization.md @@ -87,7 +87,7 @@ This specification uses the following terms: Here is an example image JSON file: -``` +```json,title=Image%20JSON&mediatype=application/vnd.oci.image.serialization.config.v1%2Bjson { "created": "2015-10-31T22:22:56.015925234Z", "author": "Alyssa P. Hacker <alyspdev@example.com>", From 7884a284e0a83e9db8b7dd7b94c232c660638d5c Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Thu, 19 May 2016 15:18:08 -0700 Subject: [PATCH 061/245] oci-image-tool: add config validation Fixes #74 Signed-off-by: Sergiusz Urbaniak --- cmd/oci-image-tool/validate.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/oci-image-tool/validate.go b/cmd/oci-image-tool/validate.go index 76891bb..c448eeb 100644 --- a/cmd/oci-image-tool/validate.go +++ b/cmd/oci-image-tool/validate.go @@ -140,6 +140,12 @@ func (v *validateCmd) validatePath(name string) error { return err } + return nil + case typeConfig: + if err := schema.MediaTypeImageSerializationConfig.Validate(f); err != nil { + return err + } + return nil } From 114d12a00f47fa45db3ea2b6fe7c7658e9fcca3a Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Mon, 23 May 2016 07:12:32 -0700 Subject: [PATCH 062/245] schema/spec_test: ignore examples having no media type Signed-off-by: Sergiusz Urbaniak --- schema/spec_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema/spec_test.go b/schema/spec_test.go index 885c048..b24f45e 100644 --- a/schema/spec_test.go +++ b/schema/spec_test.go @@ -54,7 +54,7 @@ func validate(t *testing.T, name string) { } for _, example := range examples { - if example.Err == errFormatInvalid { // ignore + if example.Err == errFormatInvalid && example.Mediatype == "" { // ignore continue } From 1b9ba8fe558e373ae57299a3ea88695a984f06eb Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 19 May 2016 15:23:00 -0700 Subject: [PATCH 063/245] CONTRIBUTING: Make the test requirements contingent on an existing suite Small, young projects like ocitools may not have grown a test suite yet. Don't make writing a Go test suite a requirement for submitting a PR. Also allow for other test frameworks, since Go's framework may not be the best fit for all projects, which may not even include Go code. Signed-off-by: W. Trevor King --- CONTRIBUTING.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c6aa0d5..5ee50fa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,9 +28,9 @@ Small changes or changes that have been discussed on the project mailing list may be submitted without a leader issue, in which case you are free to name your branch however you like. -Submit unit tests for your changes. Go has a great test framework built in; use -it! Take a look at existing tests for inspiration. Run the full test suite on -your branch before submitting a pull request. +If the project has a test suite, submit unit tests for your changes. Take a +look at existing tests for inspiration. Run the full test suite on your branch +before submitting a pull request. Update the documentation when creating or modifying features. Test your documentation changes for clarity, concision, and correctness, as @@ -58,8 +58,9 @@ comment. Before the pull request is merged, make sure that you squash your commits into logical units of work using `git rebase -i` and `git push -f`. After every -commit the test suite should be passing. Include documentation changes in the -same commit so that a revert would remove all traces of the feature or fix. +commit the test suite (if any) should be passing. Include documentation changes +in the same commit so that a revert would remove all traces of the feature or +fix. Commits that fix or close an issue should include a reference like `Closes #XXX` or `Fixes #XXX`, which will automatically close the issue when merged. From 84b481298433dfe1ec8dc32143f6c8d66f13b3ec Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 19 May 2016 15:31:51 -0700 Subject: [PATCH 064/245] CONTRIBUTING: Allow collaborative pull requests For runtime-spec, there are often PRs that pickup and reroll another user's commits (e.g. opencontainers/runtime-spec#337). As long as the Signed-off-by entries are there (for the DCO) and the new PR references the earlier work (to avoid maintainer confusion), I see no problem with this sort of collaboration. I thought about replacing the old wording with words like the above paragraph, but it seemed overly prescriptive. Signed-off-by: W. Trevor King --- CONTRIBUTING.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9bdc771..6b77b26 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,8 +44,6 @@ committing your changes. Most editors have plugins that do this automatically. Pull requests descriptions should be as clear as possible and include a reference to all the issues that they address. -Pull requests must not contain commits from other users or branches. - Commit messages must start with a capitalized and short summary written in the imperative, followed by an optional, more detailed explanatory text which is separated from the summary by an empty line. From f0457464eb648b83d6a9f55c092939a0a7d0e63f Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 24 May 2016 13:37:09 -0700 Subject: [PATCH 065/245] README: remove the roadmap We have the roadmap encoded into GitHub issues. Just point there instead. Signed-off-by: Brandon Philips --- README.md | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/README.md b/README.md index 63444b6..00579b1 100644 --- a/README.md +++ b/README.md @@ -47,19 +47,7 @@ A: Existing formats can continue to be a proving ground for technologies, as nee ## Roadmap -The current roadmap can be found in the [GitHub milestones](https://github.com/opencontainers/image-spec/milestones) - -* April v0.0.0 - * Import Docker v2.2 format -* April v0.1.0 - * Spec factored for top to bottom reading with three audiences in mind: - * Build system creators - * Image registry creators - * Container engine creators -* May v0.2.0 - * Release version of spec with improvements from two independent experimental implementations from OCI members e.g. Amazon Container Registry and rkt -* June v1.0.0 - * Release initial version of spec with two independent non-experimental implementations from OCI members +The [GitHub milestones](https://github.com/opencontainers/image-spec/milestones) lays out the path to the OCI v1.0.0 release in June 2016. # Contributing From 74114b73c10272fdecb88940870d03d1d04f939d Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 24 May 2016 14:38:35 -0700 Subject: [PATCH 066/245] README: update to include introductory information Include some diagrams and more introductory text to make the spec more approachable. This is designed to close out: https://github.com/opencontainers/image-spec/issues/69 Please comment on this doc if you feel changes should be made. Once everyone is happy with it I will fix up the PR and merge: https://docs.google.com/document/d/1qvbxydeauQAf9QpO9Vs-PRoN_QlYTrq86Z2OmniC3P0/edit Image documents: https://docs.google.com/drawings/d/1CUx_PsHddk5cgwkEZAj69QxUW0xKzjIPmo5VIY5QMp4/edit https://docs.google.com/drawings/d/1cYRHLBq3b7fTDeuLsXg1htr4B9eeORc3XATzJuK-YiM/edit Signed-off-by: Brandon Philips --- README.md | 31 ++++++++++++++++++------------- img/build-diagram.png | Bin 0 -> 26336 bytes img/run-diagram.png | Bin 0 -> 14368 bytes 3 files changed, 18 insertions(+), 13 deletions(-) create mode 100644 img/build-diagram.png create mode 100644 img/run-diagram.png diff --git a/README.md b/README.md index 63444b6..d635889 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,36 @@ -# Open Container Distributable Image Specification +# Open Container Initiative Image Format Specification -The [Open Container Initiative](http://www.opencontainers.org/) develops specifications for standards on Operating System process and application containers. +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 OCI project is tasked with creating a software shipping container image format spec (OCI Image Format) with security and naming as components. +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 [filesystem serialization](serialization.md), and an [image configuration](serialization.md#image-json-description). 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 serialization 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 serializations is called the "OCI Image". -## Initial Proposal +![](img/build-diagram.png) -This new OCI project intends to start with the Docker v2.2 specification, improve any remaining technical concerns, and standardize and improve the understood properties of a container image format. This new project will have the objectives of: +Once built the OCI Image can then 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). -* A serialized image format (base layer) +![](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 critical filesystem serialization and image manifest described above. + +The high level components of the spec include: + +* An [image manifest](manifest.md) and [filesystem serialization](serialization.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 Specification - -The Distributable Image Specification is made up of several different components and documents. -It is recommended to begin with the [Media Types](media-types.md) document as a starting point to understanding the overall structure of the specification. +## Running an OCI Image -## Cooperation with OCI Runtime Project +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. -The [OCI Runtime Spec project](https://github.com/opencontainers/runtime-spec) is developing a specification for the lifecycle of a running container. The OCI Image Format Spec project should work with the OCI Runtime Spec project so that the image can support 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: +This entire workflow should support 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 -This implies that the OCI Image Format must contain sufficient information to launch the application on the target platform (e.g. command, arguments, environment variables, etc). +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 diff --git a/img/build-diagram.png b/img/build-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..dfd1d602b220f86603de8ee3ef3dc26c133a5ff9 GIT binary patch literal 26336 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 Si+CqiBPDq?xdNHLeE$a?85dyy literal 0 HcmV?d00001 diff --git a/img/run-diagram.png b/img/run-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..df6145bf06ea2997dbe722b2da958e6bf9bf3dd8 GIT binary patch literal 14368 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 Uig1SGgw)TOSez+3ef{452Q_iqDgXcg literal 0 HcmV?d00001 From d82a59647a90633a811158246397a1f7879314fd Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Wed, 25 May 2016 15:58:02 -0700 Subject: [PATCH 067/245] Makefile: remove oci-validate-examples target This binary was removed in a previous commit but the make targets were not. The make targets have now been removed and the `validate-examples` targets run the schema tests on the spec files. Signed-off-by: Stephen J Day --- Makefile | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index bcd998b..2d144ca 100644 --- a/Makefile +++ b/Makefile @@ -53,14 +53,8 @@ output/docs.html: $(DOC_FILES) $(FIGURE_FILES) code-of-conduct.md: curl -o $@ https://raw.githubusercontent.com/opencontainers/tob/d2f9d68c1332870e40693fe077d311e0742bc73d/code-of-conduct.md -validate-examples: oci-validate-examples - ./oci-validate-examples < manifest.md - -oci-validate-json: validate.go - go build ./cmd/oci-validate-json - -oci-validate-examples: cmd/oci-validate-examples/main.go - go build ./cmd/oci-validate-examples +validate-examples: + go test -run TestValidate ./schema oci-image-tool: go build ./cmd/oci-image-tool From 5b3d5d59eab85c9a93044a0877bf73b903fa23ad Mon Sep 17 00:00:00 2001 From: Chris Aniszczyk Date: Thu, 26 May 2016 12:20:04 -0500 Subject: [PATCH 068/245] Test PullApprove --- .pullapprove.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .pullapprove.yml diff --git a/.pullapprove.yml b/.pullapprove.yml new file mode 100644 index 0000000..50609ec --- /dev/null +++ b/.pullapprove.yml @@ -0,0 +1,10 @@ +approve_by_comment: true +approve_regex: ^LGTM +reject_regex: ^Rejected +reset_on_push: false +reviewers: + members: + - tdc-maintainers + - caniszczyk + name: default + required: 2 From 560fdc9f479892138fc8d449b36aded1bbe0f57d Mon Sep 17 00:00:00 2001 From: Chris Aniszczyk Date: Thu, 26 May 2016 12:24:40 -0500 Subject: [PATCH 069/245] Fix to use "teams" in PullApprove --- .pullapprove.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pullapprove.yml b/.pullapprove.yml index 50609ec..041cecc 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -3,8 +3,8 @@ approve_regex: ^LGTM reject_regex: ^Rejected reset_on_push: false reviewers: - members: + teams: - tdc-maintainers - - caniszczyk + - admins name: default required: 2 From 9cb3110ef3274f2234c32b278c5644afa800b77b Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Thu, 26 May 2016 16:02:37 -0700 Subject: [PATCH 070/245] serialization: add language improvement to serialization A small fix from the v1.1 merge that happened inside of the docker repo after we pulled this in. See https://github.com/docker/docker/pull/22264 Signed-off-by: Brandon Philips --- serialization.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/serialization.md b/serialization.md index 1a15117..0bc9c86 100644 --- a/serialization.md +++ b/serialization.md @@ -507,7 +507,9 @@ For example, here's what the full archive of `library/busybox` is (displayed in |-- repositories ``` -There are one or more directories named with the ChainID for each layer in a full image. +There is a directory for each layer in the image. +Each directory is named with 64 character hex name that is deterministically generated from the layer information. +These names are not necessarily layer DiffIDs or ChainIDs. Each of these directories contains 3 files: * `VERSION` - The schema version of the `json` file From 57a6699b8c2898e5ae2392991d326c3eaa4ebb99 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Thu, 26 May 2016 18:13:45 -0700 Subject: [PATCH 071/245] img: move images into the img dir Trying to cleanup the objects in the root directory so there is less random stuff to sort through. Signed-off-by: Brandon Philips --- Makefile | 2 +- media-types.dot => img/media-types.dot | 0 media-types.png => img/media-types.png | Bin 3 files changed, 1 insertion(+), 1 deletion(-) rename media-types.dot => img/media-types.dot (100%) rename media-types.png => img/media-types.png (100%) diff --git a/Makefile b/Makefile index 2d144ca..743bcc1 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DOC_FILES := \ serialization.md FIGURE_FILES := \ - media-types.png + img/media-types.png default: help diff --git a/media-types.dot b/img/media-types.dot similarity index 100% rename from media-types.dot rename to img/media-types.dot diff --git a/media-types.png b/img/media-types.png similarity index 100% rename from media-types.png rename to img/media-types.png From c82a2e736fa4b6e85d53fa9f3ba554eac48d22eb Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Fri, 27 May 2016 13:26:21 +1000 Subject: [PATCH 072/245] MAINTAINERS: disallow self-LGTMs Apart from being a sign of respect to other maintainers, it also ensures that *all* pull requests get equal amounts of review -- no matter who wrote them. Maintainers should know better than to make broken patchsets, but it's also a sign of respect to the community that all pull requests have equal treatment. Signed-off-by: Aleksa Sarai --- MAINTAINERS_GUIDE.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS_GUIDE.md b/MAINTAINERS_GUIDE.md index 67a8038..8f6f129 100644 --- a/MAINTAINERS_GUIDE.md +++ b/MAINTAINERS_GUIDE.md @@ -64,7 +64,9 @@ All decisions are pull requests, and the relevant maintainers make decisions by accepting or refusing the pull request. Review and acceptance by anyone is denoted by adding a comment in the pull request: `LGTM`. However, only currently listed `MAINTAINERS` are counted towards the required -two LGTMs. +two LGTMs. In addition, if a maintainer has created a pull request, they cannot +count toward the two LGTM rule (to ensure equal amounts of review for every pull +request, no matter who wrote it). Overall the maintainer system works because of mutual respect across the maintainers of the project. The maintainers trust one another to make decisions From d1874b653b3a870f28ebf19686000a2f085dc08b Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Thu, 19 May 2016 07:24:48 -0700 Subject: [PATCH 073/245] layout: implement unpacking and validation Fixes #75 Signed-off-by: Sergiusz Urbaniak --- .gitignore | 4 +- cmd/oci-image-tool/autodetect.go | 103 +++++++++++++++ cmd/oci-image-tool/main.go | 2 + cmd/oci-image-tool/unpack.go | 102 +++++++++++++++ cmd/oci-image-tool/validate.go | 118 +++-------------- image/descriptor.go | 115 +++++++++++++++++ image/doc.go | 16 +++ image/image.go | 103 +++++++++++++++ image/manifest.go | 212 +++++++++++++++++++++++++++++++ image/walker.go | 116 +++++++++++++++++ 10 files changed, 792 insertions(+), 99 deletions(-) create mode 100644 cmd/oci-image-tool/autodetect.go create mode 100644 cmd/oci-image-tool/unpack.go create mode 100644 image/descriptor.go create mode 100644 image/doc.go create mode 100644 image/image.go create mode 100644 image/manifest.go create mode 100644 image/walker.go diff --git a/.gitignore b/.gitignore index 0248ce3..50f67b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ code-of-conduct.md -oci-image-tool -oci-validate-examples +/oci-image-tool +/oci-validate-examples output diff --git a/cmd/oci-image-tool/autodetect.go b/cmd/oci-image-tool/autodetect.go new file mode 100644 index 0000000..32dad3c --- /dev/null +++ b/cmd/oci-image-tool/autodetect.go @@ -0,0 +1,103 @@ +// 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 ( + "encoding/json" + "io" + "io/ioutil" + "net/http" + "os" + + "github.com/opencontainers/image-spec/schema" + "github.com/pkg/errors" +) + +// supported autodetection types +const ( + typeImageLayout = "imageLayout" + typeImage = "image" + typeManifest = "manifest" + typeManifestList = "manifestList" + typeConfig = "config" +) + +// 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) { + 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 + } + + f, err := os.Open(path) + if err != nil { + return "", errors.Wrap(err, "unable to open file") // os.Open includes the filename + } + defer f.Close() + + buf, err := ioutil.ReadAll(io.LimitReader(f, 512)) // read some initial bytes to detect content + if err != nil { + return "", errors.Wrap(err, "unable to read") + } + + mimeType := http.DetectContentType(buf) + + switch mimeType { + case "application/x-gzip": + return typeImage, nil + + case "application/octet-stream": + return typeImage, nil + + case "text/plain; charset=utf-8": + // might be a JSON file, will be handled below + + default: + return "", errors.New("unknown file type") + } + + if _, err := f.Seek(0, os.SEEK_SET); err != nil { + return "", errors.Wrap(err, "unable to seek") + } + + header := struct { + SchemaVersion int `json:"schemaVersion"` + MediaType string `json:"mediaType"` + Config interface{} `json:"config"` + }{} + + if err := json.NewDecoder(f).Decode(&header); err != nil { + return "", errors.Wrap(err, "unable to parse JSON") + } + + switch { + case header.MediaType == string(schema.MediaTypeManifest): + return typeManifest, nil + + case header.MediaType == string(schema.MediaTypeManifestList): + return typeManifestList, nil + + case header.MediaType == "" && header.SchemaVersion == 0 && header.Config != nil: + // config files don't have mediaType/schemaVersion header + return typeConfig, nil + } + + return "", errors.New("unknown media type") +} diff --git a/cmd/oci-image-tool/main.go b/cmd/oci-image-tool/main.go index 9206f28..ab911bb 100644 --- a/cmd/oci-image-tool/main.go +++ b/cmd/oci-image-tool/main.go @@ -31,6 +31,8 @@ func main() { stderr := log.New(os.Stderr, "", 0) cmd.AddCommand(newValidateCmd(stdout, stderr)) + cmd.AddCommand(newUnpackCmd(stdout, stderr)) + if err := cmd.Execute(); err != nil { stderr.Println(err) os.Exit(1) diff --git a/cmd/oci-image-tool/unpack.go b/cmd/oci-image-tool/unpack.go new file mode 100644 index 0000000..ee9e67a --- /dev/null +++ b/cmd/oci-image-tool/unpack.go @@ -0,0 +1,102 @@ +// 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 ( + "fmt" + "log" + "os" + "strings" + + "github.com/opencontainers/image-spec/image" + "github.com/spf13/cobra" +) + +// supported unpack types +var unpackTypes = []string{ + typeImageLayout, + typeImage, +} + +type unpackCmd struct { + stdout *log.Logger + stderr *log.Logger + typ string // the type to validate, can be empty string + ref string +} + +func newUnpackCmd(stdout, stderr *log.Logger) *cobra.Command { + v := &unpackCmd{ + stdout: stdout, + stderr: stderr, + } + + cmd := &cobra.Command{ + Use: "unpack [src] [dest]", + Short: "Unpack an image or image source layout", + Long: `Unpack the OCI image .tar file or OCI image layout directory present at [src] to the destination directory [dest].`, + Run: v.Run, + } + + cmd.Flags().StringVar( + &v.typ, "type", "", + fmt.Sprintf( + `Type of the file to unpack. If unset, oci-image-tool will try to auto-detect the type. One of "%s"`, + strings.Join(unpackTypes, ","), + ), + ) + + cmd.Flags().StringVar( + &v.ref, "ref", "v1.0", + `The ref pointing to the manifest to be unpacked. This must be present in the "refs" subdirectory of the image.`, + ) + + return cmd +} + +func (v *unpackCmd) Run(cmd *cobra.Command, args []string) { + if len(args) != 2 { + v.stderr.Print("both src and dest must be provided") + if err := cmd.Usage(); err != nil { + v.stderr.Println(err) + } + os.Exit(1) + } + + if v.typ == "" { + typ, err := autodetect(args[0]) + if err != nil { + v.stderr.Printf("%q: autodetection failed: %v", args[0], err) + os.Exit(1) + } + v.typ = typ + } + + var err error + switch v.typ { + case typeImageLayout: + err = image.UnpackLayout(args[0], args[1], v.ref) + + case typeImage: + err = image.Unpack(args[0], args[1], v.ref) + } + + if err != nil { + v.stderr.Printf("unpacking failed: %v", err) + os.Exit(1) + } + + os.Exit(0) +} diff --git a/cmd/oci-image-tool/validate.go b/cmd/oci-image-tool/validate.go index c448eeb..fb7a3e1 100644 --- a/cmd/oci-image-tool/validate.go +++ b/cmd/oci-image-tool/validate.go @@ -15,29 +15,18 @@ package main import ( - "encoding/json" "fmt" - "io" - "io/ioutil" "log" - "net/http" "os" "strings" + "github.com/opencontainers/image-spec/image" "github.com/opencontainers/image-spec/schema" "github.com/pkg/errors" "github.com/spf13/cobra" ) // supported validation types -const ( - typeImageLayout = "imageLayout" - typeImage = "image" - typeManifest = "manifest" - typeManifestList = "manifestList" - typeConfig = "config" -) - var validateTypes = []string{ typeImageLayout, typeImage, @@ -50,6 +39,7 @@ type validateCmd struct { stdout *log.Logger stderr *log.Logger typ string // the type to validate, can be empty string + ref string } func newValidateCmd(stdout, stderr *log.Logger) *cobra.Command { @@ -67,11 +57,16 @@ func newValidateCmd(stdout, stderr *log.Logger) *cobra.Command { cmd.Flags().StringVar( &v.typ, "type", "", fmt.Sprintf( - `Type of the file to validate. If unset, oci-image-tool will try to auto-detect the type. One of "%s"`, + `Type of the file to validate. If unset, oci-image-tool will try to auto-detect the type. One of "%s".`, strings.Join(validateTypes, ","), ), ) + cmd.Flags().StringVar( + &v.ref, "ref", "v1.0", + `The ref pointing to the manifest to be validated. This must be present in the "refs" subdirectory of the image. Only applicable if type is image or imageLayout.`, + ) + return cmd } @@ -89,7 +84,7 @@ func (v *validateCmd) Run(cmd *cobra.Command, args []string) { err := v.validatePath(arg) if err == nil { - v.stdout.Printf("file %s: OK", arg) + v.stdout.Printf("%s: OK", arg) continue } @@ -97,13 +92,13 @@ func (v *validateCmd) Run(cmd *cobra.Command, args []string) { if verr, ok := errors.Cause(err).(schema.ValidationError); ok { errs = verr.Errs } else { - v.stderr.Printf("file %s: validation failed: %v", arg, err) + v.stderr.Printf("%s: validation failed: %v", arg, err) exitcode = 1 continue } for _, err := range errs { - v.stderr.Printf("file %s: validation failed: %v", arg, err) + v.stderr.Printf("%s: validation failed: %v", arg, err) } exitcode = 1 @@ -122,6 +117,13 @@ func (v *validateCmd) validatePath(name string) error { } } + switch typ { + case typeImageLayout: + return image.ValidateLayout(name, v.ref) + case typeImage: + return image.Validate(name, v.ref) + } + f, err := os.Open(name) if err != nil { return errors.Wrap(err, "unable to open file") @@ -130,92 +132,14 @@ func (v *validateCmd) validatePath(name string) error { switch typ { case typeManifest: - if err := schema.MediaTypeManifest.Validate(f); err != nil { - return err - } + return schema.MediaTypeManifest.Validate(f) - return nil case typeManifestList: - if err := schema.MediaTypeManifestList.Validate(f); err != nil { - return err - } + return schema.MediaTypeManifestList.Validate(f) - return nil case typeConfig: - if err := schema.MediaTypeImageSerializationConfig.Validate(f); err != nil { - return err - } - - return nil + return schema.MediaTypeImageSerializationConfig.Validate(f) } return fmt.Errorf("type %q unimplemented", typ) } - -// 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) { - 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 - } - - f, err := os.Open(path) - if err != nil { - return "", errors.Wrap(err, "unable to open file") // os.Open includes the filename - } - defer f.Close() - - buf, err := ioutil.ReadAll(io.LimitReader(f, 512)) // read some initial bytes to detect content - if err != nil { - return "", errors.Wrap(err, "unable to read") - } - - mimeType := http.DetectContentType(buf) - - switch mimeType { - case "application/x-gzip": - return typeImage, nil - - case "application/octet-stream": - return typeImage, nil - - case "text/plain; charset=utf-8": - // might be a JSON file, will be handled below - - default: - return "", errors.New("unknown file type") - } - - if _, err := f.Seek(0, os.SEEK_SET); err != nil { - return "", errors.Wrap(err, "unable to seek") - } - - header := struct { - SchemaVersion int `json:"schemaVersion"` - MediaType string `json:"mediaType"` - Config interface{} `json:"config"` - }{} - - if err := json.NewDecoder(f).Decode(&header); err != nil { - return "", errors.Wrap(err, "unable to parse JSON") - } - - switch { - case header.MediaType == string(schema.MediaTypeManifest): - return typeManifest, nil - - case header.MediaType == string(schema.MediaTypeManifestList): - return typeManifestList, nil - - case header.MediaType == "" && header.SchemaVersion == 0 && header.Config != nil: - // config files don't have mediaType/schemaVersion header - return typeConfig, nil - } - - return "", errors.New("unknown media type") -} diff --git a/image/descriptor.go b/image/descriptor.go new file mode 100644 index 0000000..ca576e6 --- /dev/null +++ b/image/descriptor.go @@ -0,0 +1,115 @@ +// 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 image + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/pkg/errors" +) + +type descriptor struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int64 `json:"size"` +} + +func findDescriptor(w walker, name string) (*descriptor, error) { + var d descriptor + dpath := filepath.Join("refs", name) + + f := func(path string, info os.FileInfo, r io.Reader) error { + if info.IsDir() { + return nil + } + + if filepath.Clean(path) != dpath { + return nil + } + + if err := json.NewDecoder(r).Decode(&d); err != nil { + return err + } + + return errEOW + } + + switch err := w.walk(f); err { + case nil: + return nil, fmt.Errorf("%s: descriptor not found", dpath) + case errEOW: + // found, continue below + default: + return nil, err + } + + return &d, nil +} + +func (d *descriptor) validate(w walker) error { + f := func(path string, info os.FileInfo, r io.Reader) error { + if info.IsDir() { + return nil + } + + digest, err := filepath.Rel("blobs", filepath.Clean(path)) + if err != nil || d.Digest != digest { + return nil // ignore + } + + if err := d.validateContent(r); err != nil { + return err + } + + return errEOW + } + + switch err := w.walk(f); err { + case nil: + return fmt.Errorf("%s: not found", d.Digest) + case errEOW: + // found, continue below + default: + return errors.Wrapf(err, "%s: validation failed", d.Digest) + } + + return nil +} + +func (d *descriptor) validateContent(r io.Reader) error { + h := sha256.New() + n, err := io.Copy(h, r) + if err != nil { + return errors.Wrap(err, "error generating hash") + } + + digest := "sha256:" + hex.EncodeToString(h.Sum(nil)) + + if digest != d.Digest { + return errors.New("digest mismatch") + } + + if n != d.Size { + return errors.New("size mismatch") + } + + return nil +} diff --git a/image/doc.go b/image/doc.go new file mode 100644 index 0000000..fc5de0f --- /dev/null +++ b/image/doc.go @@ -0,0 +1,16 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package image defines methods for validating, and unpacking OCI images. +package image diff --git a/image/image.go b/image/image.go new file mode 100644 index 0000000..8805f59 --- /dev/null +++ b/image/image.go @@ -0,0 +1,103 @@ +// 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 image + +import ( + "os" + + "github.com/pkg/errors" +) + +// ValidateLayout walks through the file tree given by src and +// validates the manifest pointed to by the given ref +// or returns an error if the validation failed. +func ValidateLayout(src, ref string) error { + return validate(newPathWalker(src), ref) +} + +// Validate walks through the given .tar file and +// validates the manifest pointed to by the given ref +// or returns an error if the validation failed. +func Validate(tarFile, ref string) error { + f, err := os.Open(tarFile) + if err != nil { + return errors.Wrap(err, "unable to open file") + } + defer f.Close() + + return validate(newTarWalker(f), ref) +} + +func validate(w walker, refName string) error { + ref, err := findDescriptor(w, refName) + if err != nil { + return err + } + + if err = ref.validate(w); err != nil { + return err + } + + m, err := findManifest(w, ref) + if err != nil { + return err + } + + return m.validate(w) +} + +// UnpackLayout walks through the file tree given given by src and +// using the layers specified in the manifest pointed to by the given ref +// and unpacks all layers in the given destination directory +// or returns an error if the unpacking failed. +func UnpackLayout(src, dest, ref string) error { + return unpack(newPathWalker(src), dest, ref) +} + +// Unpack walks through the given .tar file and +// using the layers specified in the manifest pointed to by the given ref +// and unpacks all layers in the given destination directory +// or returns an error if the unpacking failed. +func Unpack(tarFile, dest, ref string) error { + f, err := os.Open(tarFile) + if err != nil { + return errors.Wrap(err, "unable to open file") + } + defer f.Close() + + return unpack(newTarWalker(f), dest, ref) +} + +func unpack(w walker, dest, refName string) error { + ref, err := findDescriptor(w, refName) + if err != nil { + return err + } + + if err = ref.validate(w); err != nil { + return err + } + + m, err := findManifest(w, ref) + if err != nil { + return err + } + + if err = m.validate(w); err != nil { + return err + } + + return m.unpack(w, dest) +} diff --git a/image/manifest.go b/image/manifest.go new file mode 100644 index 0000000..61e7674 --- /dev/null +++ b/image/manifest.go @@ -0,0 +1,212 @@ +// 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 image + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "github.com/opencontainers/image-spec/schema" + "github.com/pkg/errors" +) + +type manifest struct { + Config descriptor `json:"config"` + Layers []descriptor `json:"layers"` +} + +func findManifest(w walker, d *descriptor) (*manifest, error) { + var m manifest + mpath := filepath.Join("blobs", d.Digest) + + f := func(path string, info os.FileInfo, r io.Reader) error { + if info.IsDir() { + return nil + } + + if filepath.Clean(path) != mpath { + return nil + } + + buf, err := ioutil.ReadAll(r) + if err != nil { + return errors.Wrapf(err, "%s: error reading manifest", path) + } + + if err := schema.MediaTypeManifest.Validate(bytes.NewReader(buf)); err != nil { + return errors.Wrapf(err, "%s: manifest validation failed", path) + } + + if err := json.Unmarshal(buf, &m); err != nil { + return err + } + + if len(m.Layers) == 0 { + return fmt.Errorf("%s: no layers found", path) + } + + return errEOW + } + + switch err := w.walk(f); err { + case nil: + return nil, fmt.Errorf("%s: manifest not found", mpath) + case errEOW: + // found, continue below + default: + return nil, err + } + + return &m, nil +} + +func (m *manifest) validate(w walker) error { + if err := m.Config.validate(w); err != nil { + return errors.Wrap(err, "config validation failed") + } + + for _, d := range m.Layers { + if err := d.validate(w); err != nil { + return errors.Wrap(err, "layer validation failed") + } + } + + return nil +} + +func (m *manifest) unpack(w walker, dest string) error { + for _, d := range m.Layers { + if d.MediaType != string(schema.MediaTypeImageSerialization) { + continue + } + + f := func(path string, info os.FileInfo, r io.Reader) error { + if info.IsDir() { + return nil + } + + dd, err := filepath.Rel("blobs", filepath.Clean(path)) + if err != nil || d.Digest != dd { + return nil // ignore + } + + if err := unpackLayer(dest, r); err != nil { + return errors.Wrap(err, "error extracting layer") + } + + return errEOW + } + + err := w.walk(f) + if err != nil && err != errEOW { + return err + } + } + + return nil +} + +func unpackLayer(dest string, r io.Reader) error { + gz, err := gzip.NewReader(r) + if err != nil { + return errors.Wrap(err, "error creating gzip reader") + } + defer gz.Close() + + tr := tar.NewReader(gz) + +loop: + for { + hdr, err := tr.Next() + switch err { + case io.EOF: + break loop + case nil: + // success, continue below + default: + return errors.Wrapf(err, "error advancing tar stream") + } + + path := filepath.Join(dest, filepath.Clean(hdr.Name)) + info := hdr.FileInfo() + + if strings.HasPrefix(info.Name(), ".wh.") { + path = strings.Replace(path, ".wh.", "", 1) + + if err := os.RemoveAll(path); err != nil { + return errors.Wrap(err, "unable to delete whiteout path") + } + + continue loop + } + + switch hdr.Typeflag { + case tar.TypeDir: + if err := os.MkdirAll(path, info.Mode()); err != nil { + return errors.Wrap(err, "error creating directory") + } + + case tar.TypeReg, tar.TypeRegA: + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode()) + if err != nil { + return errors.Wrap(err, "unable to open file") + } + + if _, err := io.Copy(f, tr); err != nil { + f.Close() + return errors.Wrap(err, "unable to copy") + } + f.Close() + + case tar.TypeLink: + target := filepath.Join(dest, hdr.Linkname) + + if !strings.HasPrefix(target, dest) { + return fmt.Errorf("invalid hardlink %q -> %q", target, hdr.Linkname) + } + + if err := os.Link(target, path); err != nil { + return err + } + + case tar.TypeSymlink: + target := filepath.Join(filepath.Dir(path), hdr.Linkname) + + if !strings.HasPrefix(target, dest) { + return fmt.Errorf("invalid symlink %q -> %q", path, hdr.Linkname) + } + + if err := os.Symlink(hdr.Linkname, path); err != nil { + return err + } + + } + + if err := os.Chtimes(path, time.Now().UTC(), info.ModTime()); err != nil { + return errors.Wrap(err, "error changing time") + } + } + + return nil +} diff --git a/image/walker.go b/image/walker.go new file mode 100644 index 0000000..958782c --- /dev/null +++ b/image/walker.go @@ -0,0 +1,116 @@ +// 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 image + +import ( + "archive/tar" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/pkg/errors" +) + +var ( + errEOW = fmt.Errorf("end of walk") // error to signal stop walking +) + +// walkFunc is a function type that gets called for each file or directory visited by the Walker. +type walkFunc func(path string, _ os.FileInfo, _ io.Reader) error + +// walker is the interface that walks through a file tree, +// calling walk for each file or directory in the tree. +type walker interface { + walk(walkFunc) error +} + +type tarWalker struct { + r io.ReadSeeker +} + +// newTarWalker returns a Walker that walks through .tar files. +func newTarWalker(r io.ReadSeeker) walker { + return &tarWalker{r} +} + +func (w *tarWalker) walk(f walkFunc) error { + if _, err := w.r.Seek(0, os.SEEK_SET); err != nil { + return errors.Wrapf(err, "unable to reset") + } + + tr := tar.NewReader(w.r) + +loop: + for { + hdr, err := tr.Next() + switch err { + case io.EOF: + break loop + case nil: + // success, continue below + default: + return errors.Wrapf(err, "error advancing tar stream") + } + + info := hdr.FileInfo() + if err := f(hdr.Name, info, tr); err != nil { + return err + } + } + + return nil +} + +type eofReader struct{} + +func (eofReader) Read(_ []byte) (int, error) { + return 0, io.EOF +} + +type pathWalker struct { + root string +} + +// newPathWalker returns a Walker that walks through directories +// starting at the given root path. It does not follow symlinks. +func newPathWalker(root string) walker { + return &pathWalker{root} +} + +func (w *pathWalker) walk(f walkFunc) error { + return filepath.Walk(w.root, func(path string, info os.FileInfo, err error) error { + rel, err := filepath.Rel(w.root, path) + if err != nil { + return errors.Wrap(err, "error walking path") + } + + if err != nil { + return errors.Wrap(err, "error walking path") // err from filepath.Walk includes path name + } + + if info.IsDir() { // behave like a tar reader for directories + return f(rel, info, eofReader{}) + } + + file, err := os.Open(path) + if err != nil { + return errors.Wrap(err, "unable to open file") // os.Open includes the path + } + defer file.Close() + + return f(rel, info, file) + }) +} From 6d0f302f955a3e4861e2ad4827836f533fed59bc Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Fri, 27 May 2016 11:57:21 -0700 Subject: [PATCH 074/245] serialization: remove combined spec The combined spec isn't really part of the image format and introduces new objects that we don't have a schema for. Largely I think that it should be replaced by something like the image layout. https://github.com/opencontainers/image-spec/pull/94 Signed-off-by: Brandon Philips --- serialization.md | 99 ------------------------------------------------ 1 file changed, 99 deletions(-) diff --git a/serialization.md b/serialization.md index 0bc9c86..b06a66a 100644 --- a/serialization.md +++ b/serialization.md @@ -473,102 +473,3 @@ The resulting Tar archive for `f60c56784b83` has the following entries: ``` Any given image is likely to be composed of several of these Image Filesystem Changeset tar archives. - -## Combined Image JSON + Filesystem Changeset Format - -There is also a format for a single archive which contains complete information about an image, including: - - - repository names/tags - - image configuration JSON file - - all tar archives of each layer filesystem changesets - -For example, here's what the full archive of `library/busybox` is (displayed in `tree` format): - -``` -. -|-- 88ecdbb5a908bd1bdbb921110a6134d6916f962680da0c4628102ff0691b38b3.json -|-- 5785b62b697b99a5af6cd5d0aabc804d5748abbb6d3d07da5d1d3795f2dcc83e -| |-- VERSION -| |-- json -| |-- layer.tar -|-- a7b8b41220991bfc754d7ad445ad27b7f272ab8b4a2c175b9512b97471d02a8a -| |-- VERSION -| |-- json -| |-- layer.tar -|-- a936027c5ca8bf8f517923169a233e391cbb38469a75de8383b5228dc2d26ceb -| |-- VERSION -| |-- json -| |-- layer.tar -|-- f60c56784b832dd990022afc120b8136ab3da9528094752ae13fe63a2d28dc8c -| |-- VERSION -| |-- json -| |-- layer.tar -|-- manifest.json -|-- repositories -``` - -There is a directory for each layer in the image. -Each directory is named with 64 character hex name that is deterministically generated from the layer information. -These names are not necessarily layer DiffIDs or ChainIDs. -Each of these directories contains 3 files: - - * `VERSION` - The schema version of the `json` file - * `json` - The legacy JSON metadata for an image layer. - In this version of the image specification, layers don't have JSON metadata, but in [version 1](v1.md), they did. - A file is created for each layer in the v1 format for backward compatiblity. - * `layer.tar` - The Tar archive of the filesystem changeset for an image layer. - -Note that this directory layout is only important for backward compatibility. -Current implementations use the paths specified in `manifest.json`. - -The content of the `VERSION` files is simply the semantic version of the JSON metadata schema: - -``` -1.0 -``` - -The `repositories` file is another JSON file which describes names/tags: - -``` -{ - "busybox":{ - "latest":"5f29f704785248ddb9d06b90a11b5ea36c534865e9035e4022bb2e71d4ecbb9a" - } -} -``` - -Every key in this object is the name of a repository, and maps to a collection of tag suffixes. -Each tag maps to the ID of the image represented by that tag. -This file is only used for backwards compatibility. -Current implementations use the `manifest.json` file instead. - -The `manifest.json` file provides the image JSON for the top-level image, and optionally for parent images that this image was derived from. -It consists of an array of metadata entries: - -``` -[ - { - "Config": "47bcc53f74dc94b1920f0b34f6036096526296767650f223433fe65c35f149eb.json", - "RepoTags": ["busybox:latest"], - "Layers": [ - "a65da33792c5187473faa80fa3e1b975acba06712852d1dea860692ccddf3198/layer.tar", - "5f29f704785248ddb9d06b90a11b5ea36c534865e9035e4022bb2e71d4ecbb9a/layer.tar" - ] - } -] -``` - -There is an entry in the array for each image. - -The `Config` field references another file in the tar which includes the image JSON for this image. - -The `RepoTags` field lists references pointing to this image. - -The `Layers` field points to the filesystem changeset tars. - -An optional `Parent` field references the imageID of the parent image. -This parent must be part of the same `manifest.json` file. - -This file shouldn't be confused with the distribution manifest, used to push and pull images. - -Generally, implementations that support this version of the spec will use the `manifest.json` file if available, and older implementations will use the legacy `*/json` files and `repositories`. From e1d2e989212ffb4008482410a53a8e4c81ea04cf Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 31 May 2016 17:11:31 -0400 Subject: [PATCH 075/245] Makefile: fix the image paths for docs Signed-off-by: Vincent Batts --- Makefile | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 743bcc1..733c109 100644 --- a/Makefile +++ b/Makefile @@ -12,42 +12,46 @@ DOC_FILES := \ FIGURE_FILES := \ img/media-types.png +OUTPUT ?= output/ default: help help: @echo "Usage: make " @echo + @echo " * 'docs' - produce document in the $(OUTPUT) directory" @echo " * 'fmt' - format the json with indentation" @echo " * 'validate' - build the validation tool" fmt: for i in *.json ; do jq --indent 2 -M . "$${i}" > xx && cat xx > "$${i}" && rm xx ; done -docs: output/docs.pdf output/docs.html +docs: $(OUTPUT)/docs.pdf $(OUTPUT)/docs.html -output/docs.pdf: $(DOC_FILES) $(FIGURE_FILES) - @mkdir -p output/ && \ - cp *.png $(shell pwd)/output && \ +$(OUTPUT)/docs.pdf: $(DOC_FILES) $(FIGURE_FILES) + @mkdir -p $(OUTPUT)/ && \ + cp -ap img/ $(shell pwd)/$(OUTPUT)/&& \ $(DOCKER) run \ -it \ --rm \ -v $(shell pwd)/:/input/:ro \ - -v $(shell pwd)/output/:/output/ \ + -v $(shell pwd)/$(OUTPUT)/:/$(OUTPUT)/ \ -u $(shell id -u) \ - vbatts/pandoc -f markdown_github -t latex -o /output/docs.pdf $(patsubst %,/input/%,$(DOC_FILES)) && \ + --workdir /input \ + vbatts/pandoc -f markdown_github -t latex -o /$(OUTPUT)/docs.pdf $(patsubst %,/input/%,$(DOC_FILES)) && \ ls -sh $(shell readlink -f $@) -output/docs.html: $(DOC_FILES) $(FIGURE_FILES) - @mkdir -p output/ && \ - cp *.png $(shell pwd)/output && \ +$(OUTPUT)/docs.html: $(DOC_FILES) $(FIGURE_FILES) + @mkdir -p $(OUTPUT)/ && \ + cp -ap img/ $(shell pwd)/$(OUTPUT)/&& \ $(DOCKER) run \ -it \ --rm \ -v $(shell pwd)/:/input/:ro \ - -v $(shell pwd)/output/:/output/ \ + -v $(shell pwd)/$(OUTPUT)/:/$(OUTPUT)/ \ -u $(shell id -u) \ - vbatts/pandoc -f markdown_github -t html5 -o /output/docs.html $(patsubst %,/input/%,$(DOC_FILES)) && \ + --workdir /input \ + vbatts/pandoc -f markdown_github -t html5 -o /$(OUTPUT)/docs.html $(patsubst %,/input/%,$(DOC_FILES)) && \ ls -sh $(shell readlink -f $@) code-of-conduct.md: @@ -74,16 +78,19 @@ lint: test: go test -race ./... -%.png: %.dot +img/%.png: %.dot dot -Tpng $^ > $@ -inline-png-%.md: %.png +inline-png-%.md: img/%.png @printf '$*\n' "$(shell base64 $^)" > $@ +clean: + rm -rf *~ $(OUTPUT) .PHONY: \ validate-examples \ oci-image-tool \ check-license \ + clean \ lint \ docs \ test From 76fa1c74ebbbbd4e005bcfad2db5c18524020f0e Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 31 May 2016 11:13:12 -0400 Subject: [PATCH 076/245] travis: make the docs in CI Signed-off-by: Vincent Batts --- .travis.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c9e05a4..256bbe9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,16 @@ language: go go: - 1.6 -sudo: false +sudo: required + +services: + - docker before_script: - export PATH=$HOME/gopath/bin:$PATH before_install: + - docker pull vbatts/pandoc - go get github.com/vbatts/git-validation - go get github.com/alecthomas/gometalinter - gometalinter --install --update @@ -21,3 +25,4 @@ script: - make check-license - make test - make oci-image-tool + - make docs From 9bb56d8ea0a0eff3e051ec9c737347f691208445 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Thu, 26 May 2016 19:32:29 -0700 Subject: [PATCH 077/245] image-layout: add an initial image layout spec In order to build tooling that converts the set of OCI Image objects into an OCI Runtime object we need an Image Layout that test tools can be built around. Longer term this Image Layout could be used to define a "single object image" that is a tar/zip/etc that could be posted over https/ftp/etc. The layout comes from several different discussions: https://github.com/opencontainers/image-spec/issues/23 https://github.com/opencontainers/image-spec/issues/92 Signed-off-by: Brandon Philips --- image-layout.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 image-layout.md diff --git a/image-layout.md b/image-layout.md new file mode 100644 index 0000000..53fbb10 --- /dev/null +++ b/image-layout.md @@ -0,0 +1,62 @@ +## 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 appopriate manifest from the manifest list, unpacking the filesystem serializations 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 pointing to an image manifest list + +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-afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 +./blobs/sha256-5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270 +./blobs/sha256-e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f +./oci-layout +./refs +./refs/v1.0 +./refs/v1.1 +``` + +Blobs are named by their contents: + +``` +$ shasum -a 256 ./blobs/sha256-afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 +afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 ./blobs/sha256-afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 +``` + +Object names in the refs and blobs MUST NOT include characters outside of the set of "A" to "Z", "a" to "z", the hyphen `-`, the dot `.`, and the underscore `_`. +Hash algorithm identifiers containing the colon `:` will be converted to the hyphen `-`. +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. + +Each object in the refs subdirectory MUST be of type `application/vnd.oci.descriptor.v1+json`. +In general the `mediatype` of this descriptor 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. + +This illustrates the expected contents of a given ref and the manifest list it points to. + +``` +$ cat ./refs/v1.0 +{"size": 4096, "digest": "sha256:afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51", "mediatype": "application/vnd.oci.image.manifest.list.v1+json"} +``` +``` +$ cat ./blobs/sha256-afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.list.v1+json", + "manifests": [ + { +... +``` From 97e7fc9a411c57bc3d9f48b3b2074dc88890493a Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 31 May 2016 18:58:51 -0700 Subject: [PATCH 078/245] README: add the weekly call Many of the OCI image spec maintainers didn't know about this call. Add it. Signed-off-by: Brandon Philips --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 9088ccf..34bf717 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,13 @@ It also guarantees that the design is sound before code is written; a GitHub pul Typos and grammatical errors can go straight to a pull-request. When in doubt, start on the [mailing-list](#mailing-list). +## Weekly Call + +The contributors and maintainers of all OCI projects have a weekly meeting Wednesdays at 10:00 AM (USA Pacific.) +Everyone is welcome to participate via [UberConference web][UberConference] or audio-only: 646-494-8704 (no PIN needed.) +An initial agenda will be posted to the [mailing list](#mailing-list) earlier in the week, and everyone is welcome to propose additional topics or suggest other agenda alterations there. +Minutes are posted to the [mailing list](#mailing-list) and minutes from past calls are archived to the [wiki](https://github.com/opencontainers/runtime-spec/wiki) for those who are unable to join the call. + ## Mailing List You can subscribe and join the mailing list on [Google Groups](https://groups.google.com/a/opencontainers.org/forum/#!forum/dev). @@ -159,4 +166,6 @@ Read more on [How to Write a Git Commit Message](http://chris.beams.io/posts/git * If there was important/useful/essential conversation or information, copy or include a reference 8. When possible, one keyword to scope the change in the subject (i.e. "README: ...", "runtime: ...") + +[UberConference]: https://www.uberconference.com/ssaul [irc-logs]: http://ircbot.wl.linuxfoundation.org/eavesdrop/%23opencontainers/ From 95a46754de85cb643a8f2ead19d785aab8bbed72 Mon Sep 17 00:00:00 2001 From: Chris Aniszczyk Date: Wed, 1 Jun 2016 11:24:16 -0600 Subject: [PATCH 079/245] Add PullApprove support to enforce review Ensure that OCI projects share the same approval settings. https://github.com/opencontainers/project-template/issues/4 Signed-off-by: Chris Aniszczyk --- .pullapprove.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .pullapprove.yml diff --git a/.pullapprove.yml b/.pullapprove.yml new file mode 100644 index 0000000..2cf4336 --- /dev/null +++ b/.pullapprove.yml @@ -0,0 +1,9 @@ +approve_by_comment: true +approve_regex: ^LGTM +reject_regex: ^Rejected +reset_on_push: true +reviewers: + teams: + - image-spec-maintainers + name: default + required: 2 From 30bea7fbd31797f1491cbb022a48fce2f02be43b Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 1 Jun 2016 13:14:55 -0400 Subject: [PATCH 080/245] *: fixup the docs output This includes: * better doc filename, and variablized for ease * inline images do not seem needed now. put the media-types back Signed-off-by: Vincent Batts --- Makefile | 18 ++++++++---------- media-types.md | 2 ++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 733c109..3e59c1e 100644 --- a/Makefile +++ b/Makefile @@ -6,13 +6,14 @@ DOC_FILES := \ code-of-conduct.md \ project.md \ media-types.md \ - inline-png-media-types.md \ manifest.md \ serialization.md FIGURE_FILES := \ img/media-types.png -OUTPUT ?= output/ + +OUTPUT ?= output/ +DOC_FILENAME ?= oci-image-spec default: help @@ -26,9 +27,9 @@ help: fmt: for i in *.json ; do jq --indent 2 -M . "$${i}" > xx && cat xx > "$${i}" && rm xx ; done -docs: $(OUTPUT)/docs.pdf $(OUTPUT)/docs.html +docs: $(OUTPUT)/$(DOC_FILENAME).pdf $(OUTPUT)/$(DOC_FILENAME).html -$(OUTPUT)/docs.pdf: $(DOC_FILES) $(FIGURE_FILES) +$(OUTPUT)/$(DOC_FILENAME).pdf: $(DOC_FILES) $(FIGURE_FILES) @mkdir -p $(OUTPUT)/ && \ cp -ap img/ $(shell pwd)/$(OUTPUT)/&& \ $(DOCKER) run \ @@ -38,10 +39,10 @@ $(OUTPUT)/docs.pdf: $(DOC_FILES) $(FIGURE_FILES) -v $(shell pwd)/$(OUTPUT)/:/$(OUTPUT)/ \ -u $(shell id -u) \ --workdir /input \ - vbatts/pandoc -f markdown_github -t latex -o /$(OUTPUT)/docs.pdf $(patsubst %,/input/%,$(DOC_FILES)) && \ + vbatts/pandoc -f markdown_github -t latex -o /$(OUTPUT)/$(DOC_FILENAME).pdf $(patsubst %,/input/%,$(DOC_FILES)) && \ ls -sh $(shell readlink -f $@) -$(OUTPUT)/docs.html: $(DOC_FILES) $(FIGURE_FILES) +$(OUTPUT)/$(DOC_FILENAME).html: $(DOC_FILES) $(FIGURE_FILES) @mkdir -p $(OUTPUT)/ && \ cp -ap img/ $(shell pwd)/$(OUTPUT)/&& \ $(DOCKER) run \ @@ -51,7 +52,7 @@ $(OUTPUT)/docs.html: $(DOC_FILES) $(FIGURE_FILES) -v $(shell pwd)/$(OUTPUT)/:/$(OUTPUT)/ \ -u $(shell id -u) \ --workdir /input \ - vbatts/pandoc -f markdown_github -t html5 -o /$(OUTPUT)/docs.html $(patsubst %,/input/%,$(DOC_FILES)) && \ + vbatts/pandoc -f markdown_github -t html5 -o /$(OUTPUT)/$(DOC_FILENAME).html $(patsubst %,/input/%,$(DOC_FILES)) && \ ls -sh $(shell readlink -f $@) code-of-conduct.md: @@ -81,9 +82,6 @@ test: img/%.png: %.dot dot -Tpng $^ > $@ -inline-png-%.md: img/%.png - @printf '$*\n' "$(shell base64 $^)" > $@ - clean: rm -rf *~ $(OUTPUT) .PHONY: \ diff --git a/media-types.md b/media-types.md index 9551d74..9efa5df 100644 --- a/media-types.md +++ b/media-types.md @@ -46,4 +46,6 @@ This section shows where the OCI Image Specification is compatible with formats 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. From 70341a6b2b594d5cd6772253a1687bc972d7d531 Mon Sep 17 00:00:00 2001 From: "Rob Dolin (MSFT)" Date: Wed, 1 Jun 2016 12:55:08 -0700 Subject: [PATCH 081/245] [Manifest] Fix typo Typo of specification Signed-off-by: Rob Dolin --- manifest.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.md b/manifest.md index 0b81f1d..98bd7a0 100644 --- a/manifest.md +++ b/manifest.md @@ -50,7 +50,7 @@ A client will distinguish a manifest list from an image manifest based on the Co - **`digest`** *string* - The digest of the content, as defined by the [Registry V2 HTTP API Specificiation](https://docs.docker.com/registry/spec/api/#digest-parameter). + The digest of the content, as defined by the [Registry V2 HTTP API Specification](https://docs.docker.com/registry/spec/api/#digest-parameter). - **`platform`** *object* From f55a869ae6409c0b53c8a8fb586ae637c3e07bbb Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 1 Jun 2016 16:31:54 -0400 Subject: [PATCH 082/245] travis: use a `make .gitvalidation` target Signed-off-by: Vincent Batts --- .travis.yml | 4 ++-- Makefile | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 256bbe9..d116db3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ before_script: before_install: - docker pull vbatts/pandoc - - go get github.com/vbatts/git-validation + - make install.tools - go get github.com/alecthomas/gometalinter - gometalinter --install --update - go get -t -d ./... @@ -20,7 +20,7 @@ before_install: install: true script: - - git-validation -run DCO,short-subject -v -range ${TRAVIS_COMMIT_RANGE} + - make .gitvalidation - make lint - make check-license - make test diff --git a/Makefile b/Makefile index 3e59c1e..53df4a6 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,8 @@ FIGURE_FILES := \ OUTPUT ?= output/ DOC_FILENAME ?= oci-image-spec +EPOCH_TEST_COMMIT ?= v0.2.0 + default: help help: @@ -82,6 +84,23 @@ test: img/%.png: %.dot dot -Tpng $^ > $@ +.PHONY: .gitvalidation + +# When this is running in travis, it will only check the travis commit range +.gitvalidation: + @which git-validation > /dev/null 2>/dev/null || (echo "ERROR: git-validation not found. Consider 'make install.tools' target" && false) +ifeq ($(TRAVIS),true) + git-validation -q -run DCO,short-subject,dangling-whitespace +else + git-validation -v -run DCO,short-subject,dangling-whitespace -range $(EPOCH_TEST_COMMIT)..HEAD +endif + +.PHONY: install.tools +install.tools: .install.gitvalidation + +.install.gitvalidation: + go get github.com/vbatts/git-validation + clean: rm -rf *~ $(OUTPUT) .PHONY: \ From 51b7399b9af3422374c8b9d21c652dd8680d3dbd Mon Sep 17 00:00:00 2001 From: Chris Aniszczyk Date: Wed, 1 Jun 2016 14:37:13 -0600 Subject: [PATCH 083/245] Fix the PullApprove approve_regex to cover more cases '^(Approved|lgtm|LGTM|:shipit:|:star:|:\+1:|:ship:)' Signed-off-by: Chris Aniszczyk --- .pullapprove.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pullapprove.yml b/.pullapprove.yml index 2cf4336..c9dc352 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -1,5 +1,5 @@ approve_by_comment: true -approve_regex: ^LGTM +approve_regex: '^(Approved|lgtm|LGTM|:shipit:|:star:|:\+1:|:ship:)' reject_regex: ^Rejected reset_on_push: true reviewers: From 981a30c533a5b2abcdfd6ed57388ff061ab867a0 Mon Sep 17 00:00:00 2001 From: "Rob Dolin (MSFT)" Date: Wed, 1 Jun 2016 14:25:43 -0700 Subject: [PATCH 084/245] [ReadMe] remove "should" from FAQ question Replaces "should strive" below with "strives" since "should" could be construed to have specific meaning per RFC2119 > Q: What happens to AppC or Docker Image Formats? > A: ... The OCI Image Format project should strive to provide a dependable open specification ... Signed-off-by: Rob Dolin --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34bf717..56f87bc 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ A: We are seeing many independent implementations of container image handling in **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 should strive 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. +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 From 839b48d374dd3e1816084546ba75bf3c4a4c5d3a Mon Sep 17 00:00:00 2001 From: "Rob Dolin (MSFT)" Date: Wed, 1 Jun 2016 16:33:22 -0700 Subject: [PATCH 085/245] [Manifest] Mark digest as required Clarifies that digest is required per Issue #103 Signed-off-by: Rob Dolin --- manifest.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.md b/manifest.md index 98bd7a0..e64dad1 100644 --- a/manifest.md +++ b/manifest.md @@ -50,7 +50,7 @@ A client will distinguish a manifest list from an image manifest based on the Co - **`digest`** *string* - The digest of the content, as defined by the [Registry V2 HTTP API Specification](https://docs.docker.com/registry/spec/api/#digest-parameter). + This REQUIRED property is the digest of the content, as defined by the [Registry V2 HTTP API Specification](https://docs.docker.com/registry/spec/api/#digest-parameter). - **`platform`** *object* From 1547f108646561de63c9948c053c2271507b74d0 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 3 Jun 2016 21:22:33 -0700 Subject: [PATCH 086/245] *: Remove 'echo -e ...' (use 'echo' and 'printf') Bash requires 'echo -e' to enable interpretation of backslash escapes [1], but POSIX does not standardize any options for 'echo' [2]. From the POSIX docs [2]: It is not possible to use echo portably across all POSIX systems unless both -n (as the first argument) and escape sequences are omitted. The printf utility can be used portably to emulate any of the traditional behaviors of the echo utility as follows... printf is a better bet. From the POSIX docs [3]: The printf utility was added to provide functionality that has historically been provided by echo. However, due to irreconcilable differences in the various versions of echo extant, the version has few special features, leaving those to this new printf utility, which is based on one in the Ninth Edition system. The check-license script has a Bash shabang, so the 'echo -e' is legal. But the argument doesn't actually use backslash-escaped characters so there's no point in setting the option. Unsetting it closes an extremely low-risk bug where "${file}" contains bytes that could be interpreted as a backslash-escaped character: $ file='a\nb.go' $ echo -e "${file}:c" a b.go:c $ echo "${file}:c" a\nb.go:c [1]: https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html#index-echo [2]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html [3]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html Signed-off-by: W. Trevor King --- .tool/check-license | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.tool/check-license b/.tool/check-license index 122a7c2..ab6f803 100755 --- a/.tool/check-license +++ b/.tool/check-license @@ -7,7 +7,7 @@ set -o pipefail ret=0 for file in $(find . -type f -iname '*.go' ! -path './vendor/*'); do - (head -n3 "${file}" | grep -Eq "(Copyright|generated|GENERATED)") || (echo -e "${file}:missing license header" && ret=1) + (head -n3 "${file}" | grep -Eq "(Copyright|generated|GENERATED)") || (echo "${file}:missing license header" && ret=1) done exit $ret diff --git a/Makefile b/Makefile index 53df4a6..147408d 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ oci-image-tool: schema-fs: @echo "generating schema fs" - @cd schema && echo -e "$$(cat ../.header)\n\n$$(go generate)" > fs.go + @cd schema && printf "%s\n\n%s\n" "$$(cat ../.header)" "$$(go generate)" > fs.go check-license: @echo "checking license headers" From 08a1966865a7c878b97dfb99db544ff30b638039 Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Sun, 5 Jun 2016 20:53:50 +0200 Subject: [PATCH 087/245] schema: add line/col info in case of JSON err Previously no line/col information was provided if JSON parsing failed. This fixes it. Unfortunately we still cannot determine line/col information in case of schema validation because the original JSON decoderState is lost. Signed-off-by: Sergiusz Urbaniak --- cmd/oci-image-tool/autodetect.go | 11 +++++++- cmd/oci-image-tool/validate.go | 4 +++ schema/error.go | 44 ++++++++++++++++++++++++++++++++ schema/validator.go | 5 +++- 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 schema/error.go diff --git a/cmd/oci-image-tool/autodetect.go b/cmd/oci-image-tool/autodetect.go index 32dad3c..094e7b9 100644 --- a/cmd/oci-image-tool/autodetect.go +++ b/cmd/oci-image-tool/autodetect.go @@ -84,7 +84,16 @@ func autodetect(path string) (string, error) { }{} if err := json.NewDecoder(f).Decode(&header); err != nil { - return "", errors.Wrap(err, "unable to parse JSON") + if _, errSeek := f.Seek(0, os.SEEK_SET); errSeek != nil { + return "", errors.Wrap(err, "unable to seek") + } + + e := errors.Wrap( + schema.WrapSyntaxError(f, err), + "unable to parse JSON", + ) + + return "", e } switch { diff --git a/cmd/oci-image-tool/validate.go b/cmd/oci-image-tool/validate.go index fb7a3e1..c53ba79 100644 --- a/cmd/oci-image-tool/validate.go +++ b/cmd/oci-image-tool/validate.go @@ -91,6 +91,10 @@ func (v *validateCmd) Run(cmd *cobra.Command, args []string) { var errs []error if verr, ok := errors.Cause(err).(schema.ValidationError); ok { errs = verr.Errs + } else if serr, ok := errors.Cause(err).(*schema.SyntaxError); ok { + v.stderr.Printf("%s:%d:%d: validation failed: %v", arg, serr.Line, serr.Col, err) + exitcode = 1 + continue } else { v.stderr.Printf("%s: validation failed: %v", arg, err) exitcode = 1 diff --git a/schema/error.go b/schema/error.go new file mode 100644 index 0000000..8b0bfc2 --- /dev/null +++ b/schema/error.go @@ -0,0 +1,44 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package schema + +import ( + "encoding/json" + "io" + + "go4.org/errorutil" +) + +// A SyntaxError is a description of a JSON syntax error +// including line, column and offset in the JSON file. +type SyntaxError struct { + msg string + Line, Col int + Offset int64 +} + +func (e *SyntaxError) Error() string { return e.msg } + +// WrapSyntaxError checks whether the given error is a *json.SyntaxError +// and converts it into a *schema.SyntaxError containing line/col information using the given reader. +// If the given error is not a *json.SyntaxError it is returned unchanged. +func WrapSyntaxError(r io.Reader, err error) error { + if serr, ok := err.(*json.SyntaxError); ok { + line, col, _ := errorutil.HighlightBytePosition(r, serr.Offset) + return &SyntaxError{serr.Error(), line, col, serr.Offset} + } + + return err +} diff --git a/schema/validator.go b/schema/validator.go index 900edcd..abc8d6d 100644 --- a/schema/validator.go +++ b/schema/validator.go @@ -15,6 +15,7 @@ package schema import ( + "bytes" "fmt" "io" "io/ioutil" @@ -48,7 +49,9 @@ func (v Validator) Validate(src io.Reader) error { result, err := gojsonschema.Validate(sl, ml) if err != nil { - return errors.Wrapf(err, "schema %s: unable to validate manifest", v) + return errors.Wrapf( + WrapSyntaxError(bytes.NewReader(buf), err), + "schema %s: unable to validate", v) } if result.Valid() { From b744ed47218c6ea56bbcac2bd012b47648efab5a Mon Sep 17 00:00:00 2001 From: "Rob Dolin (MSFT)" Date: Tue, 7 Jun 2016 00:11:00 -0700 Subject: [PATCH 088/245] [Manifest] Mark config as required Per discussion in #113, this is REQUIRED Signed-off-by: Rob Dolin --- manifest.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.md b/manifest.md index e64dad1..1f2648d 100644 --- a/manifest.md +++ b/manifest.md @@ -148,7 +148,7 @@ Unlike the [Manifest List](#manifest-list), which contains information about a s - **`config`** *object* - The `config` property references a configuration object for a container, by digest. + 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](serialization.md#image-json-description). Properties of `config` are: From d3ffc1ce4ce6d5400a5db2a9649eef56ebede6b7 Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Thu, 2 Jun 2016 16:03:42 +0200 Subject: [PATCH 089/245] oci-image-tool: implement create-runtime-bundle Fixes #99 Signed-off-by: Sergiusz Urbaniak --- cmd/oci-image-tool/create_runtime_bundle.go | 109 ++++++++++++++ cmd/oci-image-tool/main.go | 1 + cmd/oci-image-tool/unpack.go | 2 +- image/config.go | 156 ++++++++++++++++++++ image/image.go | 65 ++++++++ 5 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 cmd/oci-image-tool/create_runtime_bundle.go create mode 100644 image/config.go diff --git a/cmd/oci-image-tool/create_runtime_bundle.go b/cmd/oci-image-tool/create_runtime_bundle.go new file mode 100644 index 0000000..1ec8ab1 --- /dev/null +++ b/cmd/oci-image-tool/create_runtime_bundle.go @@ -0,0 +1,109 @@ +// 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 ( + "fmt" + "log" + "os" + "strings" + + "github.com/opencontainers/image-spec/image" + "github.com/spf13/cobra" +) + +// supported bundle types +var bundleTypes = []string{ + typeImageLayout, + typeImage, +} + +type bundleCmd struct { + stdout *log.Logger + stderr *log.Logger + typ string // the type to bundle, can be empty string + ref string + root string +} + +func newBundleCmd(stdout, stderr *log.Logger) *cobra.Command { + v := &bundleCmd{ + stdout: stdout, + stderr: stderr, + } + + cmd := &cobra.Command{ + Use: "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, + } + + cmd.Flags().StringVar( + &v.typ, "type", "", + fmt.Sprintf( + `Type of the file to unpack. If unset, oci-image-tool will try to auto-detect the type. One of "%s"`, + strings.Join(bundleTypes, ","), + ), + ) + + cmd.Flags().StringVar( + &v.ref, "ref", "v1.0", + `The ref pointing to the manifest of the OCI image. This must be present in the "refs" subdirectory of the image.`, + ) + + cmd.Flags().StringVar( + &v.root, "rootfs", "rootfs", + `A directory representing the root filesystem of the container in the OCI runtime bundle. +It is strongly recommended to keep the default value.`, + ) + + return cmd +} + +func (v *bundleCmd) Run(cmd *cobra.Command, args []string) { + if len(args) != 2 { + v.stderr.Print("both src and dest must be provided") + if err := cmd.Usage(); err != nil { + v.stderr.Println(err) + } + os.Exit(1) + } + + if v.typ == "" { + typ, err := autodetect(args[0]) + if err != nil { + v.stderr.Printf("%q: autodetection failed: %v", args[0], err) + os.Exit(1) + } + v.typ = typ + } + + var err error + switch v.typ { + case typeImageLayout: + err = image.CreateRuntimeBundleLayout(args[0], args[1], v.ref, v.root) + + case typeImage: + err = image.CreateRuntimeBundle(args[0], args[1], v.ref, v.root) + } + + if err != nil { + v.stderr.Printf("unpacking failed: %v", err) + os.Exit(1) + } + + os.Exit(0) +} diff --git a/cmd/oci-image-tool/main.go b/cmd/oci-image-tool/main.go index ab911bb..7cd350e 100644 --- a/cmd/oci-image-tool/main.go +++ b/cmd/oci-image-tool/main.go @@ -32,6 +32,7 @@ func main() { cmd.AddCommand(newValidateCmd(stdout, stderr)) cmd.AddCommand(newUnpackCmd(stdout, stderr)) + cmd.AddCommand(newBundleCmd(stdout, stderr)) if err := cmd.Execute(); err != nil { stderr.Println(err) diff --git a/cmd/oci-image-tool/unpack.go b/cmd/oci-image-tool/unpack.go index ee9e67a..e02ad70 100644 --- a/cmd/oci-image-tool/unpack.go +++ b/cmd/oci-image-tool/unpack.go @@ -33,7 +33,7 @@ var unpackTypes = []string{ type unpackCmd struct { stdout *log.Logger stderr *log.Logger - typ string // the type to validate, can be empty string + typ string // the type to unpack, can be empty string ref string } diff --git a/image/config.go b/image/config.go new file mode 100644 index 0000000..d486c01 --- /dev/null +++ b/image/config.go @@ -0,0 +1,156 @@ +// 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 image + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/opencontainers/image-spec/schema" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" +) + +type cfg struct { + User string + Memory int64 + MemorySwap int64 + CPUShares int64 `json:"CpuShares"` + ExposedPorts map[string]struct{} + Env []string + Entrypoint []string + Cmd []string + Volumes map[string]struct{} + WorkingDir string +} + +type config struct { + Architecture string `json:"architecture"` + OS string `json:"os"` + Config cfg `json:"config"` +} + +func findConfig(w walker, d *descriptor) (*config, error) { + var c config + cpath := filepath.Join("blobs", d.Digest) + + f := func(path string, info os.FileInfo, r io.Reader) error { + if info.IsDir() { + return nil + } + + if filepath.Clean(path) != cpath { + return nil + } + + buf, err := ioutil.ReadAll(r) + if err != nil { + return errors.Wrapf(err, "%s: error reading config", path) + } + + if err := schema.MediaTypeImageSerializationConfig.Validate(bytes.NewReader(buf)); err != nil { + return errors.Wrapf(err, "%s: config validation failed", path) + } + + if err := json.Unmarshal(buf, &c); err != nil { + return err + } + + return errEOW + } + + switch err := w.walk(f); err { + case nil: + return nil, fmt.Errorf("%s: config not found", cpath) + case errEOW: + // found, continue below + default: + return nil, err + } + + return &c, nil +} + +func (c *config) runtimeSpec(rootfs string) (*specs.Spec, error) { + if c.OS != "linux" { + return nil, fmt.Errorf("%s: unsupported OS", c.OS) + } + + var s specs.Spec + s.Version = "0.5.0" + s.Root.Path = rootfs + s.Process.Cwd = c.Config.WorkingDir + s.Process.Env = append([]string(nil), c.Config.Env...) + s.Process.Args = append([]string(nil), c.Config.Entrypoint...) + s.Process.Args = append(s.Process.Args, c.Config.Cmd...) + + if uid, err := strconv.Atoi(c.Config.User); err == nil { + s.Process.User.UID = uint32(uid) + } else if ug := strings.Split(c.Config.User, ":"); len(ug) == 2 { + uid, err := strconv.Atoi(ug[0]) + if err != nil { + return nil, errors.New("config.User: unsupported uid format") + } + + gid, err := strconv.Atoi(ug[1]) + if err != nil { + return nil, errors.New("config.User: unsupported gid format") + } + + s.Process.User.UID = uint32(uid) + s.Process.User.GID = uint32(gid) + } else { + return nil, errors.New("config.User: unsupported format") + } + + s.Platform.OS = c.OS + s.Platform.Arch = c.Architecture + + mem := uint64(c.Config.Memory) + swap := uint64(c.Config.MemorySwap) + shares := uint64(c.Config.CPUShares) + + s.Linux.Resources = &specs.Resources{ + CPU: &specs.CPU{ + Shares: &shares, + }, + + Memory: &specs.Memory{ + Limit: &mem, + Reservation: &mem, + Swap: &swap, + }, + } + + for vol := range c.Config.Volumes { + s.Mounts = append( + s.Mounts, + specs.Mount{ + Destination: vol, + Type: "bind", + Options: []string{"rbind"}, + }, + ) + } + + return &s, nil +} diff --git a/image/image.go b/image/image.go index 8805f59..ffc2ca1 100644 --- a/image/image.go +++ b/image/image.go @@ -15,7 +15,9 @@ package image import ( + "encoding/json" "os" + "path/filepath" "github.com/pkg/errors" ) @@ -101,3 +103,66 @@ func unpack(w walker, dest, refName string) error { return m.unpack(w, dest) } + +// CreateRuntimeBundleLayout walks through the file tree given given by src and +// creates an OCI runtime bundle in the given destination dest +// or returns an error if the unpacking failed. +func CreateRuntimeBundleLayout(src, dest, ref, root string) error { + return createRuntimeBundle(newPathWalker(src), dest, ref, root) +} + +// CreateRuntimeBundle walks through the given .tar file and +// creates an OCI runtime bundle in the given destination dest +// or returns an error if the unpacking failed. +func CreateRuntimeBundle(tarFile, dest, ref, root string) error { + f, err := os.Open(tarFile) + if err != nil { + return errors.Wrap(err, "unable to open file") + } + defer f.Close() + + return createRuntimeBundle(newTarWalker(f), dest, ref, root) +} + +func createRuntimeBundle(w walker, dest, refName, rootfs string) error { + ref, err := findDescriptor(w, refName) + if err != nil { + return err + } + + if err = ref.validate(w); err != nil { + return err + } + + m, err := findManifest(w, ref) + if err != nil { + return err + } + + if err = m.validate(w); err != nil { + return err + } + + c, err := findConfig(w, &m.Config) + if err != nil { + return err + } + + err = m.unpack(w, filepath.Join(dest, rootfs)) + if err != nil { + return err + } + + spec, err := c.runtimeSpec(rootfs) + if err != nil { + return err + } + + f, err := os.Create(filepath.Join(dest, "config.json")) + if err != nil { + return err + } + defer f.Close() + + return json.NewEncoder(f).Encode(spec) +} From 773be54c2aa429d654c83eaf6fc2443c4a1d352c Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 7 Jun 2016 14:35:34 -0700 Subject: [PATCH 090/245] manifest.md: remove backwards compatibility section that refers to registries This section essentially talks about the HTTP registry API. OCI doesn't have the concept of a HTTP transport so drop the entire section. We should add this sort of language later once we sort out in the OCI TDC making an HTTP transport. Signed-off-by: Brandon Philips --- manifest.md | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/manifest.md b/manifest.md index e64dad1..3943c44 100644 --- a/manifest.md +++ b/manifest.md @@ -239,25 +239,3 @@ Unlike the [Manifest List](#manifest-list), which contains information about a s } } ``` - -# Backward compatibility - -The registry will continue to accept uploads of manifests in both the old and new formats. - -When pushing images, clients which support the new manifest format SHOULD first construct a manifest in the new format. -If uploading this manifest fails, presumably because the registry only supports the old format, the client MAY fall back to uploading a manifest in the old format. - -When pulling images, clients indicate support for this new version of the manifest format by sending the -`application/vnd.oci.image.manifest.v1+json` and -`application/vnd.oci.image.manifest.list.v1+json` media types in an `Accept` header when making a request to the `manifests` endpoint. -Updated clients SHOULD check the `Content-Type` header to see whether the manifest returned from the endpoint is in the old format, or is an image manifest or manifest list in the new format. - -If the manifest being requested uses the new format, and the appropriate media type is not present in an `Accept` header, the registry MUST assume that the client cannot handle the manifest as-is, and MUST rewrite it on the fly into the old format. -If the object that would otherwise be returned is a manifest list, the registry MUST look up the appropriate manifest for the amd64 platform and linux OS, rewrite that manifest into the old format if necessary, and return the result to the client. -If no suitable manifest is found in the manifest list, the registry will return a 404 error. - -One of the challenges in rewriting manifests to the old format is that the old format involves an image configuration for each layer in the manifest, but the new format only provides one image configuration. -To work around this, the registry MUST create synthetic image configurations for all layers except the top layer. -These image configurations will not result in runnable images on their own, but only serve to fill in the parent chain in a compatible way. -The IDs in these synthetic configurations will be derived from hashes of their respective blobs. -The registry MUST create these configurations and their IDs using the same scheme as Docker 1.10 when it creates a legacy manifest to push to a registry which doesn't support the new format. From d49d0552db3d73af305c2cd3645359acde98cf8a Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Wed, 1 Jun 2016 16:08:00 -0700 Subject: [PATCH 091/245] spec: describe descriptors and digests Add a definition for content descriptors and digests, which are used as content identifiers in the OCI image specification. Signed-off-by: Stephen J Day --- descriptor.md | 123 +++++++++++++++++++++++++++++++++++++++++++++++++ media-types.md | 1 + 2 files changed, 124 insertions(+) create mode 100644 descriptor.md diff --git a/descriptor.md b/descriptor.md new file mode 100644 index 0000000..c137733 --- /dev/null +++ b/descriptor.md @@ -0,0 +1,123 @@ + + +# OpenContainers Content Descriptors + +OCI have several components that come to together to describe an image. +References between components form a [Merkle Directed Acyclic Graph (DAG)](https://en.wikipedia.org/wiki/Merkle_tree). +The references in the _Merkle DAG_ are expressed through _Content Descriptors_. +A _Content Descriptor_ or _Descriptor_, describes the disposition of targeted content. +A _Descriptor_ includes the type of content, an independently-verifiable content identifier, known as a "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 + +The following describe the primary set of properties that make up a _Descriptor_. + +- **`mediaType`** *string* + + This REQUIRED property contains the MIME type of the referenced object. + +- **`digest`** *string* + + This REQUIRED property is the _digest_ of the targeted content, meeting the requirements outlined in [Digests and Verification](#digests-and—verification). + Retrieved content SHOULD be verified against this digest when consumed via untrusted sources. + +- **`size`** *int* + This REQUIRED property specifies the size in bytes of the blob. + This property exists so that a client will have an expected size for the content before validating. + If the length of the retrieved content does not match the specified length, the content SHOULD NOT be trusted. + +### Reserved + +The following are field keys that MUST NOT be used in descriptors specified in other OCI specifications: + +- **`urls`** *array* + + This key is RESERVED for future versions of the specification. + +- **`data`** *string* + + This key is RESERVED for futures 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_ component of a _Descriptor_ acts as a content identifier, employing [content addressability](http://en.wikipedia.org/wiki/Content-addressable_storage) for the OCI image format. +It uniquely identifies content by taking a collision-resistant hash of the bytes. +Such an identifier can be independently calculated and verified by selection of a common _algorithm_. +If such an identifier can be communicated in a secure manner, one can retrieve the content from an insecure source, calculate it independently and be certain that the correct content was obtained. +Put simply, the identifier is a property of the content. + +To disambiguate from other concepts, we call this identifier a _digest_. +A _digest_ is a serialized hash result, consisting of a _algorithm_ and _hex_ portion. +The _algorithm_ identifies the methodology used to calculate the digest, which are shared by implementations. +The _hex_ portion is the hex-encoded result of the hash. + +We define a _digest_ string to match the following grammar: + +``` +digest := algorithm ":" hex +algorithm := /[A-Fa-f0-9_+.-]+/ +hex := /[A-Fa-f0-9]+/ +``` + +Some examples of _digests_ 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_. +The size of the content SHOULD be verified, as well, to protect against [Length Extension Attacks](https://en.wikipedia.org/wiki/Length_extension_attack). +Heavy processing of before calculating a hash SHOULD be avoided. +Implementations MAY employ some canonicalization to ensure stable content identifiers. + +### Algorithms + +While the _algorithm_ does allow one to implement 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/media-types.md b/media-types.md index 9efa5df..bf28e30 100644 --- a/media-types.md +++ b/media-types.md @@ -2,6 +2,7 @@ The following `mediaType` MIME types are used by the formats described here, and the resources they reference: +- `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.serialization.rootfs.tar.gzip`: ["Layer", as a gzipped tar archive](serialization.md#creating-an-image-filesystem-changeset) From 0b2b3fd913b034a86b915173634555b9351fc9ce Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Thu, 2 Jun 2016 17:57:29 -0700 Subject: [PATCH 092/245] schema: break out definition of descriptor and verify examples Signed-off-by: Stephen J Day --- descriptor.md | 8 +--- schema/content-descriptor.json | 24 ++++++++++++ schema/defs-image.json | 22 ----------- schema/fs.go | 65 ++++++++++++++++++------------- schema/image-manifest-schema.json | 4 +- schema/schema.go | 2 + schema/spec_test.go | 4 ++ 7 files changed, 72 insertions(+), 57 deletions(-) create mode 100644 schema/content-descriptor.json diff --git a/descriptor.md b/descriptor.md index c137733..8b7fe61 100644 --- a/descriptor.md +++ b/descriptor.md @@ -1,9 +1,3 @@ - - # OpenContainers Content Descriptors OCI have several components that come to together to describe an image. @@ -77,7 +71,7 @@ digest 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_. -The size of the content SHOULD be verified, as well, to protect against [Length Extension Attacks](https://en.wikipedia.org/wiki/Length_extension_attack). +The size of the content SHOULD be verified to reduce hash collision space. Heavy processing of before calculating a hash SHOULD be avoided. Implementations MAY employ some canonicalization to ensure stable content identifiers. diff --git a/schema/content-descriptor.json b/schema/content-descriptor.json new file mode 100644 index 0000000..c4bdb6b --- /dev/null +++ b/schema/content-descriptor.json @@ -0,0 +1,24 @@ +{ + "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", + "type": "integer" + }, + "digest": { + "$ref": "defs-image.json#definitions/digest" + } + }, + "required": [ + "mediaType", + "size", + "digest" + ] +} diff --git a/schema/defs-image.json b/schema/defs-image.json index a26d1c9..d0d6b78 100644 --- a/schema/defs-image.json +++ b/schema/defs-image.json @@ -11,28 +11,6 @@ "type": "string", "pattern": "^[a-z0-9]+:[a-fA-F0-9]+$" }, - "descriptor": { - "id": "https://opencontainers.org/schema/image/descriptor", - "type": "object", - "required": [ - "mediaType", - "size", - "digest" - ], - "properties": { - "mediaType": { - "description": "the mediatype of the referenced object", - "$ref": "#definitions/mediaType" - }, - "size": { - "description": "the size in bytes of the referenced object", - "type": "integer" - }, - "digest": { - "$ref": "#definitions/digest" - } - } - }, "manifestDescriptor": { "id": "https://opencontainers.org/schema/image/manifestDescriptor", "type": "object", diff --git a/schema/fs.go b/schema/fs.go index 53ad3e3..e49730a 100644 --- a/schema/fs.go +++ b/schema/fs.go @@ -203,7 +203,7 @@ var _escData = map[string]*_escFile{ "/config-schema.json": { local: "config-schema.json", size: 707, - modtime: 1463700693, + modtime: 1464139547, compressed: ` H4sIAAAJbogA/5SRPW7DMAyF5/oUhpOxjjp0ytoDdOgJVJmKGcCiQDJDUPju1U/c2kvhLobx+L73JOqr adtuAHGMUZFCd2679wjhjYJaDMBt+vN4aT8iOPTobHE9Z+woboTJZmRUjWdjrkKhr+qJ+GIGtl77l1dT @@ -213,10 +213,23 @@ T4aE9IoTdGU2V0tnbzoS/xG1dbMbUdPhbgx7GZK9zscuVu4jgy+HBy99HZ/yKxxMUjBgfi1ZdrjJYiL1 `, }, + "/content-descriptor.json": { + local: "content-descriptor.json", + size: 616, + modtime: 1464914956, + compressed: ` +H4sIAAAJbogA/4yRMVPDMAyF9/wKXdqR1gxMXWFngI1jcG0lVe9iG1kMhet/x4oTSGGgW/L8vvck+7MB +aD1mx5SEYmh30D4mDPcxiKWADPqFQeBhMkWGp4SOOnJ2JG40Yp3dAQer+EEk7Yw55hg2Vd1G7o1n28nm +9s5UbVU58jOSCxNLs5ub84hVt/Hf7ZWTU0Il4/6ITqqWuPAshLmc6GJFG9CTfa7mKv3dVw4Io09DIXag +AmOHXKZBD4uOEV+XM+U8dnlDg+1xq8uuyj8F0tRsfnpH6lzhNtPHf5OoBSjA/iSYr5hmvgkqz9QjX/Z5 +6jHLsvGa4SeqJjVTWsv49k6M+mAvvy93ud5ldfl5bc7NVwAAAP//Zc2MR2gCAAA= +`, + }, + "/defs-config.json": { local: "defs-config.json", size: 1755, - modtime: 1463700704, + modtime: 1464139547, compressed: ` H4sIAAAJbogA/7xUzc7TMBA8N09hBY6BXhAHri1HVKQIOFZusm63xF5rvQEi1HfHSauS/qRKKV8PVeOx Z2bXY/t3olRaQigYvSC59INK52DQYTsKymsWLOpKsxJSCw9uRk40OmAVvwyuVe6hQIOF7vjZXvCoEAVb @@ -230,27 +243,27 @@ W1SwXppll8oQfUVUgXaDqSTtb5f8CQAA//8Cok052wYAAA== "/defs-image.json": { local: "defs-image.json", - size: 3100, - modtime: 1462965360, + size: 2528, + modtime: 1464914958, compressed: ` -H4sIAAAJbogA/+xWy27bMBC86ysIJUAOfqjXGkGAoEGBnlIgPdVQgY20kphKpLqkgzqB/r3U+x3Xidte -erK55A5nhmPSzxZjto/KI55qLoW9YfYNBlzwfKRYCqS5t4uBmJbsNkXxQQoNXCCxTwmEyO5S9HjAPSja -lyVeA2Dw8i1MMUGfw5d9ik3JFLmfbxhpnaqN40gD79Xwai0pdJQXYQIOz7dyWohlDaBLQFtp4iJs6ylo -jVTI+baF1ZO7cLbvVu/Nt+vV1/XCXZzbxdKs7LB9HqLSXWoDU3SEzKN9qmVIkEbcY4aZ913tElb2Mhmw -fJG8f0BPLxkXxbAiwi4uI1DR1eYywp/gG8sSiKvOq4vj9Rgt7mJjvgXXq4/FYCiooi/p9X53MEYES5lt -nfDHjhPm+Nuq1jv0ZVtU/Kk3rryvCm6rmQxBEz9UHQkzSZo7smJtzrk+HsIAychGnw0kFBDnZj7vPetk -uJO7Zmk21HOYSr4sT8X9XqP6TTq121xoDJGm9x9ld15J32oDY3U/6+wkIHhg1t2cIEMTWH8hS51KGoMO -JCX/8/Uv8jV1EAOg4/LUoEzqmNI4maZiBsiLuDYNO8Jej5mTyu4U3B7iTHDGmMPZV6t1XqA6fjV609lY -2OloGbA3klk/mh3KFJ+OVAP6VnIBQu74aS1rUWfpARHsx9MmAckUlwPC2nt+WugjEAcx/IUf7ddLZv0x -YSMo8P3iMoL4c/dnGkCs0JrzJDvwIoIQUkP/H+3REeiCNI+QFHgb9K6myVvWXLJq/aCkOHN6Lwekd4Uv -dwN3Or48T12UYhfH478BrlWPMiuzfgUAAP//VjUNyBwMAAA= +H4sIAAAJbogA/7RWy27bMBC8+ysIJUAOfqjXGkGAoEGBnlIgPTVQgY20kphKpLqkgzqB/r2k3k+nrt0b +ueQOZ4Zjym8LxpwAlU8801wKZ8ucOwy54HamWAakub9LgJiW7D5D8UkKDVwgsS8pRMgeMvR5yH0o2lcl +XgNg8OwRpphiwOHbPsOmZIo8sAfGWmdq67rSwPs1vNpIilzlx5iCy+1RbguxqgF0CegoTVxEbT0DrZEK +OT8eYf3qLd3HD+uPZnS7/r5ZestLp9ialx1OwCNUukttYIqOkfm0z7SMCLKY+8ww83+qXcrKXiZDZjfJ +p2f09YpxUUwrIuzqOgYV32yvY/wNgbEshaTqvLk6Xo/R4i23ZhTerj8Xk4GgFAQPDfhdJUPSCb6PsUaE +S9ltnfDXjhPacx6rWi8Eq7ao+GtvXt1Fp5IloENJqVOVvNYXMuRNRFF15M2kbe5ai71WR32FhCGSsQQD +NpBVQFyaddt70cl5J5vN1nyo8X0qdptNztNeo/pLOvUNcKExQpo+f5TveSXV1kmY5iIGQMflqUGZ1DGl +cTJNxQqQH3NtGnaEvR6zJpXTKXg9xJngjDGHq/+s1j1AdfzL7y3nY2Hno2XATiSzeTEnlCk+H6kG9FRy +IYJ1/LyWtaiz9IAI9uNlk4B0iss7woy0g0JfgDiI4S/8aL8OmfXfhI2gIAiKxwiSr92faQiJwsWcJ+24 +HuW9LyIIITX0/5UcHYEuSPMRkgLvw97TNPnKmkdWbZ6VFBdu78sB2UPhy8PAnY4vb1MPpdgliTMS7S3q +Wb7IF38CAAD//wKthPngCQAA `, }, "/defs.json": { local: "defs.json", size: 3193, - modtime: 1463700693, + modtime: 1464139547, compressed: ` H4sIAAAJbogA/7RWTXPaMBC98ys8tEfa2PIX9NYp/cghAzOZnjo9uGYBtSCpstxpmuG/VzLGWPZiMKWH JPau9r23T6tYzwPHGS4gSyUVinI2fOMMp7CkjJq3zMkzWDhqLXm+WvNc6UdwZgLYO85UQhlI51FASpc0 @@ -268,23 +281,23 @@ MrVJbn8cB+ZnN/gbAAD//0JyEpx5DAAA "/image-manifest-schema.json": { local: "image-manifest-schema.json", - size: 1064, - modtime: 1462965360, + size: 1032, + modtime: 1464914959, compressed: ` -H4sIAAAJbogA/6RTvVLjMBDu/RQ7TspzdMVVaa+64oaCDA1DIeyVvZlYMlrBTCaTd0c/UZAJBSSlV/v9 -Sj5UAHWH3FqaHBldr6G+m1D/NdpJ0mjh3yh7hP9Sk0J2cD9hS4paGbd/BfiS2wFHGaCDc9NaiC0b3aTp -ythedFYq1/z+I9JskXDUZQh7jPGqbVblCEvbgoIDMZ4cJKzbTxjQ5nmL7Wk2Wc9hHSH7kxDMzxLFg2dM -4dL4MvNmIAZFuOuAU0JkcANCFIcsDokP3hIhSAapgbTDHm10EcmvSybmZs9sOWuWifNjOq5H7Ehu0sbh -Rv0PrrP20qIKXB0qbuL6KlzuQvgBaQr1cYGbWfOaivrS17fY8s2YT0l3cu/tl3S5GGmt3BftOxzLvWuF -vfTMgNTauPju+faymx35xkvKn3VeIqvsNTqtLb68ksVg6/Grv+Di5czva163/3iqjtV7AAAA///++ypf -KAQAAA== +H4sIAAAJbogA/6RSPU/zMBDe8ytOacc39TswdWViQAxULIjBJOfkqsYOPoNUVf3v+KMujsoAdMyTez7u +8R0qgLpDbi1Njoyu11A/TKhvjXaSNFq4G2WPcC81KWQHjxO2pKiVcfpfoC+5HXCUgTo4N62F2LLRTUJX +xvais1K55v+NSNgi8ajLFPYc413b7MqRlqYFhQRiPCVIXLefMLDN6xbbEzZZr2EdIfs/YTGPJYknr5iW +S/DlzpuBGBThrgNOGyKDGxCiOWRzSHrwkQRBMkgNpB32aGOKKP63zcQ87Fkt75ptIn5Mv+sRO5KbNHG4 +0v9L6+y9tKiCVoeKmzi+Co+7EB4gTaE+LnizaN5TUV/mymohDWrX5EcwNqrO6Tu593FLei5CWiv3RdsO +x3Lup0beamYotTYu3jVfX2azI99oKfm7TktmlbPGpLXFt3eyGGI9f3flF5cxf495vf7jpTpWnwEAAP// +X3p8DwgEAAA= `, }, "/manifest-list-schema.json": { local: "manifest-list-schema.json", size: 1010, - modtime: 1462965360, + modtime: 1464139547, compressed: ` H4sIAAAJbogA/6ySMU/7MBDF93yKU9rxn/o/MHWFBQnEQMWCGExyaa5q7OAzSFXV747tS0qiMIDoUqkv fu9+7+xjBpBXyKWjzpM1+Rryhw7NtTVek0EHt63eItxrQzWyhzsKP48dllRTqZPlX8xYctlgq6O/8b5b diff --git a/schema/image-manifest-schema.json b/schema/image-manifest-schema.json index 197eeb1..f25c85f 100644 --- a/schema/image-manifest-schema.json +++ b/schema/image-manifest-schema.json @@ -14,12 +14,12 @@ "$ref": "defs-image.json#/definitions/mediaType" }, "config": { - "$ref": "defs-image.json#/definitions/descriptor" + "$ref": "content-descriptor.json" }, "layers": { "type": "array", "items": { - "$ref": "defs-image.json#/definitions/descriptor" + "$ref": "content-descriptor.json" } }, "annotations": { diff --git a/schema/schema.go b/schema/schema.go index 7f9b9f2..2d104bb 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -18,6 +18,7 @@ import "net/http" // Media types for the OCI image formats const ( + MediaTypeDescriptor Validator = `application/vnd.oci.descriptor.v1+json` MediaTypeManifest Validator = `application/vnd.oci.image.manifest.v1+json` MediaTypeManifestList Validator = `application/vnd.oci.image.manifest.list.v1+json` MediaTypeImageSerialization unimplemented = `application/vnd.oci.image.serialization.rootfs.tar.gzip` @@ -32,6 +33,7 @@ var ( // specs maps OCI schema media types to schema files. specs = map[Validator]string{ + MediaTypeDescriptor: "content-descriptor.json", MediaTypeManifest: "image-manifest-schema.json", MediaTypeManifestList: "manifest-list-schema.json", MediaTypeImageSerializationConfig: "config-schema.json", diff --git a/schema/spec_test.go b/schema/spec_test.go index b24f45e..02c336d 100644 --- a/schema/spec_test.go +++ b/schema/spec_test.go @@ -33,6 +33,10 @@ var ( errFormatInvalid = errors.New("format: invalid") ) +func TestValidateDescriptor(t *testing.T) { + validate(t, "../descriptor.md") +} + func TestValidateManifest(t *testing.T) { validate(t, "../manifest.md") } From 6b4694f02c62bb0a9153e05baaef8b437a14579c Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 7 Jun 2016 14:43:06 -0700 Subject: [PATCH 093/245] README: add language around base and optional layers Based on feedback new users might not know what the base and optional layer language is about. Point to the scope doc that uses this language. Fixes #120. Signed-off-by: Brandon Philips --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 56f87bc..b225c43 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ The high level components of the spec include: * 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. From c04fca2d7183cf6bbf581c5b3b56bda53c881c02 Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Mon, 6 Jun 2016 12:19:23 +0200 Subject: [PATCH 094/245] schema: add more tests Currently samples in the specification are being tested. This adds additional tests. Fixes #83 Signed-off-by: Sergiusz Urbaniak --- schema/config_test.go | 165 ++++++++++++++++++++++++++++++++++++++++ schema/manifest_test.go | 122 +++++++++++++++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 schema/config_test.go create mode 100644 schema/manifest_test.go diff --git a/schema/config_test.go b/schema/config_test.go new file mode 100644 index 0000000..8dba321 --- /dev/null +++ b/schema/config_test.go @@ -0,0 +1,165 @@ +// 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 <alyspdev@example.com>", + "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 <alyspdev@example.com>", + "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.MediaTypeImageSerializationConfig.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 new file mode 100644 index 0000000..ad82bf7 --- /dev/null +++ b/schema/manifest_test.go @@ -0,0 +1,122 @@ +// 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 integer, expected string + { + manifest: ` +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.oci.image.serialization.v1+json", + "size": "1470", + "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" + } +} +`, + 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.serialization.v1+json", + "size": 1470, + "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" + }, + "layers": [ + { + "mediaType": "application/vnd.oci.image.serialization.rootfs.tar.gzip", + "size": "675598" + } + ] +} +`, + fail: true, + }, + + // valid manifest + { + manifest: ` +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.oci.image.serialization.v1+json", + "size": 1470, + "digest": "sha256:c86f7763873b6c0aae22d963bab59b4f5debbed6685761b5951584f6efb0633b" + }, + "layers": [ + { + "mediaType": "application/vnd.oci.image.serialization.rootfs.tar.gzip", + "size": 675598, + "digest": "sha256:9d3dd9504c685a304985025df4ed0283e47ac9ffa9bd0326fddf4d59513f0827" + }, + { + "mediaType": "application/vnd.oci.image.serialization.rootfs.tar.gzip", + "size": 156, + "digest": "sha256:2b689805fbd00b2db1df73fae47562faac1a626d5f61744bfe29946ecff5d73d" + }, + { + "mediaType": "application/vnd.oci.image.serialization.rootfs.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) + } + } +} From 0afb2b7fd3f725716c32cc85c10ae30ee19b728f Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 14 Jun 2016 04:31:48 +0200 Subject: [PATCH 095/245] *: remove references to combined schema Signed-off-by: Antonio Murdaca --- media-types.md | 5 ----- schema/schema.go | 11 +++++------ 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/media-types.md b/media-types.md index bf28e30..747d7c4 100644 --- a/media-types.md +++ b/media-types.md @@ -7,7 +7,6 @@ The following `mediaType` MIME types are used by the formats described here, and - `application/vnd.oci.image.manifest.v1+json`: [Image manifest format](manifest.md#image-manifest) - `application/vnd.oci.image.serialization.rootfs.tar.gzip`: ["Layer", as a gzipped tar archive](serialization.md#creating-an-image-filesystem-changeset) - `application/vnd.oci.image.serialization.config.v1+json`: [Container config JSON](serialization.md#image-json-description) -- `application/vnd.oci.image.serialization.combined.v1+json`: [Combined image JSON and filesystem changesets](serialization.md#combined-image-json--filesystem-changeset-format) ## Compatibility Matrix @@ -39,10 +38,6 @@ This section shows where the OCI Image Specification is compatible with formats - [application/vnd.docker.container.image.v1+json](https://github.com/docker/docker/blob/master/image/spec/v1.md#image-json-description) -### application/vnd.oci.image.serialization.combined.v1+json - -- [layout compatible with docker save/load format](https://github.com/opencontainers/image-spec/blob/master/serialization.md#combined-image-json--filesystem-changeset-format) - ## Relations The following figure shows how the above media types reference each other: diff --git a/schema/schema.go b/schema/schema.go index 2d104bb..b917139 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -18,12 +18,11 @@ import "net/http" // Media types for the OCI image formats const ( - MediaTypeDescriptor Validator = `application/vnd.oci.descriptor.v1+json` - MediaTypeManifest Validator = `application/vnd.oci.image.manifest.v1+json` - MediaTypeManifestList Validator = `application/vnd.oci.image.manifest.list.v1+json` - MediaTypeImageSerialization unimplemented = `application/vnd.oci.image.serialization.rootfs.tar.gzip` - MediaTypeImageSerializationConfig Validator = `application/vnd.oci.image.serialization.config.v1+json` - MediaTypeImageSerializationCombined unimplemented = `application/vnd.oci.image.serialization.combined.v1+json` + MediaTypeDescriptor Validator = `application/vnd.oci.descriptor.v1+json` + MediaTypeManifest Validator = `application/vnd.oci.image.manifest.v1+json` + MediaTypeManifestList Validator = `application/vnd.oci.image.manifest.list.v1+json` + MediaTypeImageSerialization unimplemented = `application/vnd.oci.image.serialization.rootfs.tar.gzip` + MediaTypeImageSerializationConfig Validator = `application/vnd.oci.image.serialization.config.v1+json` ) var ( From 2f247912d1656d689e47fb2c11e9a7ecf4672b37 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 14 Jun 2016 11:17:30 +0200 Subject: [PATCH 096/245] image-layout.md: explain blobs can be anything Signed-off-by: Antonio Murdaca --- image-layout.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/image-layout.md b/image-layout.md index 53fbb10..e5a1ff2 100644 --- a/image-layout.md +++ b/image-layout.md @@ -42,21 +42,39 @@ 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. -Each object in the refs subdirectory MUST be of type `application/vnd.oci.descriptor.v1+json`. +Each object in the `refs` subdirectory MUST be of type `application/vnd.oci.descriptor.v1+json`. In general the `mediatype` of this descriptor 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. -This illustrates the expected contents of a given ref and the manifest list it points to. +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:afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51", "mediatype": "application/vnd.oci.image.manifest.list.v1+json"} +{"size": 4096, "digest": "sha256:afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51", "mediatype": "application/vnd.oci.image.manifest.v1+json"} ``` ``` $ cat ./blobs/sha256-afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 { "schemaVersion": 2, - "mediaType": "application/vnd.oci.image.manifest.list.v1+json", - "manifests": [ + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": [ + "mediaType": "application/vnd.oci.image.serialization.config.v1+json", + "size": 7023, + "digest": "sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270" + }, + "layers": [ { + "mediaType": "application/vnd.oci.image.serialization.rootfs.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] +``` From ca809b38500f5454421152f9f3b82719ee7d250b Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 14 Jun 2016 11:21:32 +0200 Subject: [PATCH 097/245] image-layout.md: clarify ref names are arbitrary Signed-off-by: Antonio Murdaca --- image-layout.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/image-layout.md b/image-layout.md index e5a1ff2..937c778 100644 --- a/image-layout.md +++ b/image-layout.md @@ -26,7 +26,7 @@ $ find . ./oci-layout ./refs ./refs/v1.0 -./refs/v1.1 +./refs/stable-release ``` Blobs are named by their contents: @@ -36,12 +36,13 @@ $ shasum -a 256 ./blobs/sha256-afff3924849e458c5ef237db5f89539274d5e609db5db935e afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 ./blobs/sha256-afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 ``` -Object names in the refs and blobs MUST NOT include characters outside of the set of "A" to "Z", "a" to "z", the hyphen `-`, the dot `.`, and the underscore `_`. +Object names in the `refs` and `blobs` MUST NOT include characters outside of the set of "A" to "Z", "a" to "z", the hyphen `-`, the dot `.`, and the underscore `_`. Hash algorithm identifiers containing the colon `:` will be converted to the hyphen `-`. 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 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. From b0996bacd78d42afeacbdac424f9143291e55477 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Wed, 15 Jun 2016 11:03:02 +0200 Subject: [PATCH 098/245] image-layout.md: references can contain digits Signed-off-by: Antonio Murdaca --- image-layout.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/image-layout.md b/image-layout.md index 937c778..0606b7a 100644 --- a/image-layout.md +++ b/image-layout.md @@ -36,7 +36,7 @@ $ shasum -a 256 ./blobs/sha256-afff3924849e458c5ef237db5f89539274d5e609db5db935e afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 ./blobs/sha256-afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 ``` -Object names in the `refs` and `blobs` MUST NOT include characters outside of the set of "A" to "Z", "a" to "z", the hyphen `-`, the dot `.`, and the underscore `_`. +Object names in the `refs` and `blobs` 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 `_`. Hash algorithm identifiers containing the colon `:` will be converted to the hyphen `-`. 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. From 790e2d3d5ec317a1d2fa6546ca956fdeb134a36a Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Wed, 15 Jun 2016 11:10:30 +0200 Subject: [PATCH 099/245] image-layout.md: include example of a manifests list Signed-off-by: Antonio Murdaca --- image-layout.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/image-layout.md b/image-layout.md index 0606b7a..f7ed7fe 100644 --- a/image-layout.md +++ b/image-layout.md @@ -20,6 +20,7 @@ $ cd example.com/app/ $ find . . ./blobs +./blobs/sha256-e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f ./blobs/sha256-afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 ./blobs/sha256-5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270 ./blobs/sha256-e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f @@ -50,7 +51,24 @@ This illustrates the expected contents of a given ref, the manifest list it poin ``` $ cat ./refs/v1.0 -{"size": 4096, "digest": "sha256:afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51", "mediatype": "application/vnd.oci.image.manifest.v1+json"} +{"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 From 346ca18588ba9fcb89aeffaa02facece66e6591a Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Wed, 15 Jun 2016 11:13:41 +0200 Subject: [PATCH 100/245] image-layout.md: refs can contain descriptors to both a manifest and manifests list Signed-off-by: Antonio Murdaca --- image-layout.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/image-layout.md b/image-layout.md index f7ed7fe..a3e8f20 100644 --- a/image-layout.md +++ b/image-layout.md @@ -7,7 +7,7 @@ Given an image layout a tool can convert a given ref into a runnable OCI Image F 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 pointing to an image manifest list +- "refs" contains descriptors pointing to an image manifest It also contains a file that is used to identify the layout version: From fe63795d299c44a8fe6c60423402dcc27d497eef Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 15 Jun 2016 13:06:54 -0400 Subject: [PATCH 101/245] image-layout: clarify refs can be generic As the refs are descriptors, the media-type is not strictly a manifest or manifest-list. Reported-by: W. Trevor King Signed-off-by: Vincent Batts --- image-layout.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/image-layout.md b/image-layout.md index a3e8f20..6834b98 100644 --- a/image-layout.md +++ b/image-layout.md @@ -7,7 +7,8 @@ Given an image layout a tool can convert a given ref into a runnable OCI Image F 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 pointing to an image manifest +- "refs" contains [descriptors][descriptors]. Commonly pointing to an image manifest. + It also contains a file that is used to identify the layout version: @@ -45,7 +46,7 @@ The blobs directory MAY be missing referenced blobs, in which case the missing b 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 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. +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. This illustrates the expected contents of a given ref, the manifest list it points to and the blobs the manifest references. @@ -97,3 +98,5 @@ $ cat ./blobs/sha256-5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a43335 $ cat ./blobs/sha256-e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f [tar stream] ``` + +[descriptors]: ./descriptor.md From 3723ab6acca324942d2c3f0c6084035499adbeea Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Wed, 15 Jun 2016 16:03:05 -0700 Subject: [PATCH 102/245] serialization: add language about whiteout This fixes https://github.com/opencontainers/image-spec/issues/130 Signed-off-by: Brandon Philips --- serialization.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/serialization.md b/serialization.md index b06a66a..e9961d9 100644 --- a/serialization.md +++ b/serialization.md @@ -442,7 +442,7 @@ The `f60c56784b83` directory then looks like this: ``` f60c56784b83/ etc/ - .wh.my-app.cfg + .wh.my-app-config my-app.d/ default.cfg bin/ @@ -461,9 +461,15 @@ Modified: /bin/my-app-tools Deleted: /etc/my-app-config ``` -A Tar Archive is then created which contains *only* this changeset: The added and modified files and directories in their entirety, and for each deleted item an entry for an empty file at the same location but with the basename of the deleted file or directory prefixed with `.wh.`. -The filenames prefixed with `.wh.` are known as "whiteout" files. -NOTE: For this reason, it is not possible to create an image root filesystem which contains a file or directory with a name beginning with `.wh.`. +A Tar Archive is then created which contains *only* this changeset: + +- Added and modified files and directories in their entirety +- Deleted files or directory marked with a whiteout file + +A whiteout file is an empty file that prefixes the deleted paths basename `.wh.`. +When a whiteout is found in the upper changeset of a filesystem, any matching name in the lower changeset is ignored, and the whiteout itself is also hidden. +As files prefixed with `.wh.` are special whiteout tombstones it is not possible to create a filesystem which has a file or directory with a name beginning with `.wh.`. + The resulting Tar archive for `f60c56784b83` has the following entries: ``` From 1149faca2b8b9421e6a6ee5b2b47b5e3e234d157 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Wed, 15 Jun 2016 16:21:01 -0700 Subject: [PATCH 103/245] manifest: use descriptor doc as canonical source Right now manifest is relying on the external Docker docs to explain the digest format. Instead use the OCI Descriptor docs. Signed-off-by: Brandon Philips --- manifest.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.md b/manifest.md index ada1e51..919db9c 100644 --- a/manifest.md +++ b/manifest.md @@ -50,7 +50,7 @@ A client will distinguish a manifest list from an image manifest based on the Co - **`digest`** *string* - This REQUIRED property is the digest of the content, as defined by the [Registry V2 HTTP API Specification](https://docs.docker.com/registry/spec/api/#digest-parameter). + This REQUIRED property is the digest of the content, as defined by the [Descriptor](descriptor.md) digest format. - **`platform`** *object* @@ -166,7 +166,7 @@ Unlike the [Manifest List](#manifest-list), which contains information about a s - **`digest`** *string* - The digest of the content, as defined by the [Registry V2 HTTP API Specificiation](https://docs.docker.com/registry/spec/api/#digest-parameter). + This REQUIRED property is the digest of the content, as defined by the [Descriptor](descriptor.md) digest format. - **`layers`** *array* @@ -189,7 +189,7 @@ Unlike the [Manifest List](#manifest-list), which contains information about a s - **`digest`** *string* - The digest of the content, as defined by the [Registry V2 HTTP API Specificiation](https://docs.docker.com/registry/spec/api/#digest-parameter). + This REQUIRED property is the digest of the content, as defined by the [Descriptor](descriptor.md) digest format. - **`annotations`** *hashmap* From e94aa35aaa324ffc63d567ecb34d4914473702ed Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Wed, 15 Jun 2016 22:34:19 -0700 Subject: [PATCH 104/245] schema: add a docker v2.2 backwards compat test This adds a test of the schema compatibility by taking the manifest of library/docker from the docker hub, with sha256 digest verification, and simply finding/replacing the four constant strings documented in media-types.md and ensuring that it passes OCI validation. https://github.com/opencontainers/image-spec/blob/master/media-types.md This test is functionaly equivalent to downloading a Docker v2.2 manifest into the file `docker-manifest: ``` $ curl -L -H "Authorization: Bearer ..." \ -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \ https://registry-1.docker.io/v2/library/docker/manifests/sha256fg:888206c77cd2811ec47e752ba291e5b7734e3ef137dfd222daadaca39a9f17bc > docker-manifest ``` And then running a regex to change `docker.distribution` to `oci.image` and changing `v2` to `v1` ``` $ cat docker-manifest | sed -e "s%docker\.distribution%oci\.image%g" -e "s%v2%v1%g" > oci-manifest ``` And finally testing the manifest with oci-image-tool: ``` $ oci-image-tool validate oci-manifest oci-manifest: OK ``` Signed-off-by: Brandon Philips --- .../manifest_backwards_compatibility_test.go | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 schema/manifest_backwards_compatibility_test.go diff --git a/schema/manifest_backwards_compatibility_test.go b/schema/manifest_backwards_compatibility_test.go new file mode 100644 index 0000000..5260a5f --- /dev/null +++ b/schema/manifest_backwards_compatibility_test.go @@ -0,0 +1,106 @@ +// 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.serialization.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 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) + } + } +} From 453673de4f184a6a6122648afde67f7da80405c8 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 15 Jun 2016 23:00:21 -0700 Subject: [PATCH 105/245] *: Fix '<' -> '<' and '>' -> '>' typos These quasi-entities (which are missing the usual closing semicolon) have been floating around since c22ca799 (serialization: docker v1 image format media type, 2016-04-04, #6). Generated with: $ sed -i 's/<//' $(git grep -l alyspdev@example.com) Signed-off-by: W. Trevor King --- schema/config_test.go | 4 ++-- serialization.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/schema/config_test.go b/schema/config_test.go index 8dba321..a98f918 100644 --- a/schema/config_test.go +++ b/schema/config_test.go @@ -42,7 +42,7 @@ func TestConfig(t *testing.T) { config: ` { "created": "2015-10-31T22:22:56.015925234Z", - "author": "Alyssa P. Hacker <alyspdev@example.com>", + "author": "Alyssa P. Hacker ", "architecture": "amd64", "os": "linux", "config": { @@ -102,7 +102,7 @@ func TestConfig(t *testing.T) { config: ` { "created": "2015-10-31T22:22:56.015925234Z", - "author": "Alyssa P. Hacker <alyspdev@example.com>", + "author": "Alyssa P. Hacker ", "architecture": "amd64", "os": "linux", "config": { diff --git a/serialization.md b/serialization.md index b06a66a..40d966d 100644 --- a/serialization.md +++ b/serialization.md @@ -90,7 +90,7 @@ Here is an example image JSON file: ```json,title=Image%20JSON&mediatype=application/vnd.oci.image.serialization.config.v1%2Bjson { "created": "2015-10-31T22:22:56.015925234Z", - "author": "Alyssa P. Hacker <alyspdev@example.com>", + "author": "Alyssa P. Hacker ", "architecture": "amd64", "os": "linux", "config": { From d67f1e5e63c0bf99f590eb1c4f88ed00a85201d7 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 16 Jun 2016 12:47:30 +0200 Subject: [PATCH 106/245] media-types.md: fix typo Signed-off-by: Antonio Murdaca --- media-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media-types.md b/media-types.md index 747d7c4..57f7f59 100644 --- a/media-types.md +++ b/media-types.md @@ -28,7 +28,7 @@ This section shows where the OCI Image Specification is compatible with formats ### application/vnd.oci.image.rootfs.tar.gzip -**Interchangable and fully compatible mime-types** +**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) From 4e3789d281ff3af72a0ce74b10067786967558c9 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 16 Jun 2016 23:24:37 +0200 Subject: [PATCH 107/245] descriptor: size int64 Signed-off-by: Antonio Murdaca --- descriptor.md | 2 +- schema/content-descriptor.json | 2 +- schema/defs-image.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/descriptor.md b/descriptor.md index 8b7fe61..de7d38f 100644 --- a/descriptor.md +++ b/descriptor.md @@ -23,7 +23,7 @@ The following describe the primary set of properties that make up a _Descriptor_ This REQUIRED property is the _digest_ of the targeted content, meeting the requirements outlined in [Digests and Verification](#digests-and—verification). Retrieved content SHOULD be verified against this digest when consumed via untrusted sources. -- **`size`** *int* +- **`size`** *int64* This REQUIRED property specifies the size in bytes of the blob. This property exists so that a client will have an expected size for the content before validating. If the length of the retrieved content does not match the specified length, the content SHOULD NOT be trusted. diff --git a/schema/content-descriptor.json b/schema/content-descriptor.json index c4bdb6b..2e6ceba 100644 --- a/schema/content-descriptor.json +++ b/schema/content-descriptor.json @@ -10,7 +10,7 @@ }, "size": { "description": "the size in bytes of the referenced object", - "type": "integer" + "$ref": "defs.json#/definitions/int64" }, "digest": { "$ref": "defs-image.json#definitions/digest" diff --git a/schema/defs-image.json b/schema/defs-image.json index d0d6b78..3c3e708 100644 --- a/schema/defs-image.json +++ b/schema/defs-image.json @@ -27,7 +27,7 @@ }, "size": { "description": "the size in bytes of the referenced object", - "type": "integer" + "$ref": "defs.json#/definitions/int64" }, "digest": { "$ref": "#definitions/digest" From 8427ef16a88aae62871fb732a2f5b70d883698e1 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Fri, 17 Jun 2016 11:20:11 +0200 Subject: [PATCH 108/245] manifest: explain schemaVersion == 2 Signed-off-by: Antonio Murdaca --- manifest.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.md b/manifest.md index 919db9c..c9428fe 100644 --- a/manifest.md +++ b/manifest.md @@ -23,7 +23,7 @@ A client will distinguish a manifest list from an image manifest based on the Co - **`schemaVersion`** *int* This REQUIRED property specifies the image manifest schema version. - This schema uses the version `2`. + 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* @@ -139,7 +139,7 @@ Unlike the [Manifest List](#manifest-list), which contains information about a s - **`schemaVersion`** *int* This REQUIRED property specifies the image manifest schema version. - For this version of the specification, this MUST be `2`. + 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* From 7c10f516139f9791cd8cd3bc46e0349297810f94 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Fri, 17 Jun 2016 15:02:33 +0200 Subject: [PATCH 109/245] schema: fix json formatting Signed-off-by: Antonio Murdaca --- schema/config-schema.json | 2 +- schema/defs-config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/schema/config-schema.json b/schema/config-schema.json index da64dbf..3de0e73 100644 --- a/schema/config-schema.json +++ b/schema/config-schema.json @@ -24,7 +24,7 @@ "$ref": "defs-config.json#/definitions/rootfs" }, "history": { - "type": "array", + "type": "array", "items": { "$ref": "defs-config.json#/definitions/history" } diff --git a/schema/defs-config.json b/schema/defs-config.json index 2faf3c6..dbf3b72 100644 --- a/schema/defs-config.json +++ b/schema/defs-config.json @@ -64,7 +64,7 @@ "properties": { "created": { "type": "string", - "format": "date-time" + "format": "date-time" }, "author": { "type": "string" From 16879e981475ce99979ed7798878e1c1165bb8ad Mon Sep 17 00:00:00 2001 From: Jonathan Boulle Date: Fri, 17 Jun 2016 15:22:31 +0200 Subject: [PATCH 110/245] Makefile: fix fmt target and format json files Signed-off-by: Jonathan Boulle --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 147408d..903d8d4 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ help: @echo " * 'validate' - build the validation tool" fmt: - for i in *.json ; do jq --indent 2 -M . "$${i}" > xx && cat xx > "$${i}" && rm xx ; done + for i in schema/*.json ; do jq --indent 2 -M . "$${i}" > xx && cat xx > "$${i}" && rm xx ; done docs: $(OUTPUT)/$(DOC_FILENAME).pdf $(OUTPUT)/$(DOC_FILENAME).html From 3490642876db28495f5a7d9252b93b87a43071b6 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Fri, 17 Jun 2016 15:15:36 +0200 Subject: [PATCH 111/245] schema: close files Signed-off-by: Antonio Murdaca --- schema/spec_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/schema/spec_test.go b/schema/spec_test.go index 02c336d..4deab17 100644 --- a/schema/spec_test.go +++ b/schema/spec_test.go @@ -51,6 +51,7 @@ func validate(t *testing.T, name string) { if err != nil { t.Fatal(err) } + defer m.Close() examples, err := extractExamples(m) if err != nil { From 67893d597446d3e7bc42c8bbee4a3d567f93e2c8 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Wed, 15 Jun 2016 16:10:35 -0700 Subject: [PATCH 112/245] serialization: add explanation of DiffIDs DiffIDs and Manifest list digests were a bit confusing. Explain the difference. Fixes: #115 Signed-off-by: Brandon Philips --- serialization.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/serialization.md b/serialization.md index b06a66a..fa175b1 100644 --- a/serialization.md +++ b/serialization.md @@ -36,10 +36,9 @@ This specification uses the following terms: Layer DiffID
    - Layers are referenced by cryptographic hashes of their serialized representation. - This is a SHA256 digest over the tar archive used to transport the layer, represented as a hexadecimal encoding of 256 bits, e.g., sha256:a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9. + 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 that the digest used as the layer ID is taken over an uncompressed version of the tar. + 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.serialization.rootfs.tar.gzip` types.
    Layer ChainID From 6f763438c58ca05530036eafb65917b44bb01e0b Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Fri, 17 Jun 2016 18:28:06 +0200 Subject: [PATCH 113/245] test: exercise manifest list backwards compat Signed-off-by: Antonio Murdaca --- .../manifest_backwards_compatibility_test.go | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/schema/manifest_backwards_compatibility_test.go b/schema/manifest_backwards_compatibility_test.go index 5260a5f..6153d2e 100644 --- a/schema/manifest_backwards_compatibility_test.go +++ b/schema/manifest_backwards_compatibility_test.go @@ -42,6 +42,89 @@ func convertFormats(input string) string { 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 From 1059b104fbc201a9d0a35048a5ce27ad736f50f8 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sat, 18 Jun 2016 16:55:25 +0200 Subject: [PATCH 114/245] schema: update from updated esc command Signed-off-by: Antonio Murdaca --- schema/fs.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/schema/fs.go b/schema/fs.go index e49730a..fb447c7 100644 --- a/schema/fs.go +++ b/schema/fs.go @@ -169,7 +169,9 @@ func _escFSByte(useLocal bool, name string) ([]byte, error) { if err != nil { return nil, err } - return ioutil.ReadAll(f) + b, err := ioutil.ReadAll(f) + f.Close() + return b, err } f, err := _escStatic.prepare(name) if err != nil { @@ -203,7 +205,7 @@ var _escData = map[string]*_escFile{ "/config-schema.json": { local: "config-schema.json", size: 707, - modtime: 1464139547, + modtime: 1466261692, compressed: ` H4sIAAAJbogA/5SRPW7DMAyF5/oUhpOxjjp0ytoDdOgJVJmKGcCiQDJDUPju1U/c2kvhLobx+L73JOqr adtuAHGMUZFCd2679wjhjYJaDMBt+vN4aT8iOPTobHE9Z+woboTJZmRUjWdjrkKhr+qJ+GIGtl77l1dT @@ -216,7 +218,7 @@ T4aE9IoTdGU2V0tnbzoS/xG1dbMbUdPhbgx7GZK9zscuVu4jgy+HBy99HZ/yKxxMUjBgfi1ZdrjJYiL1 "/content-descriptor.json": { local: "content-descriptor.json", size: 616, - modtime: 1464914956, + modtime: 1466180793, compressed: ` H4sIAAAJbogA/4yRMVPDMAyF9/wKXdqR1gxMXWFngI1jcG0lVe9iG1kMhet/x4oTSGGgW/L8vvck+7MB aD1mx5SEYmh30D4mDPcxiKWADPqFQeBhMkWGp4SOOnJ2JG40Yp3dAQer+EEk7Yw55hg2Vd1G7o1n28nm @@ -229,7 +231,7 @@ AmOHXKZBD4uOEV+XM+U8dnlDg+1xq8uuyj8F0tRsfnpH6lzhNtPHf5OoBSjA/iSYr5hmvgkqz9QjX/Z5 "/defs-config.json": { local: "defs-config.json", size: 1755, - modtime: 1464139547, + modtime: 1466261692, compressed: ` H4sIAAAJbogA/7xUzc7TMBA8N09hBY6BXhAHri1HVKQIOFZusm63xF5rvQEi1HfHSauS/qRKKV8PVeOx Z2bXY/t3olRaQigYvSC59INK52DQYTsKymsWLOpKsxJSCw9uRk40OmAVvwyuVe6hQIOF7vjZXvCoEAVb @@ -244,7 +246,7 @@ W1SwXppll8oQfUVUgXaDqSTtb5f8CQAA//8Cok052wYAAA== "/defs-image.json": { local: "defs-image.json", size: 2528, - modtime: 1464914958, + modtime: 1466180793, compressed: ` H4sIAAAJbogA/7RWy27bMBC8+ysIJUAOfqjXGkGAoEGBnlIgPTVQgY20kphKpLqkgzqB/r2k3k+nrt0b ueQOZ4Zjym8LxpwAlU8801wKZ8ucOwy54HamWAakub9LgJiW7D5D8UkKDVwgsS8pRMgeMvR5yH0o2lcl @@ -263,7 +265,7 @@ Wb7IF38CAAD//wKthPngCQAA "/defs.json": { local: "defs.json", size: 3193, - modtime: 1464139547, + modtime: 1466180793, compressed: ` H4sIAAAJbogA/7RWTXPaMBC98ys8tEfa2PIX9NYp/cghAzOZnjo9uGYBtSCpstxpmuG/VzLGWPZiMKWH JPau9r23T6tYzwPHGS4gSyUVinI2fOMMp7CkjJq3zMkzWDhqLXm+WvNc6UdwZgLYO85UQhlI51FASpc0 @@ -282,7 +284,7 @@ MrVJbn8cB+ZnN/gbAAD//0JyEpx5DAAA "/image-manifest-schema.json": { local: "image-manifest-schema.json", size: 1032, - modtime: 1464914959, + modtime: 1466180793, compressed: ` H4sIAAAJbogA/6RSPU/zMBDe8ytOacc39TswdWViQAxULIjBJOfkqsYOPoNUVf3v+KMujsoAdMyTez7u 8R0qgLpDbi1Njoyu11A/TKhvjXaSNFq4G2WPcC81KWQHjxO2pKiVcfpfoC+5HXCUgTo4N62F2LLRTUJX @@ -297,7 +299,7 @@ X3p8DwgEAAA= "/manifest-list-schema.json": { local: "manifest-list-schema.json", size: 1010, - modtime: 1464139547, + modtime: 1466180793, compressed: ` H4sIAAAJbogA/6ySMU/7MBDF93yKU9rxn/o/MHWFBQnEQMWCGExyaa5q7OAzSFXV747tS0qiMIDoUqkv fu9+7+xjBpBXyKWjzpM1+Rryhw7NtTVek0EHt63eItxrQzWyhzsKP48dllRTqZPlX8xYctlgq6O/8b5b From e0afe63b306bfc7eefc196aa8f32b664f54fac8b Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Thu, 16 Jun 2016 10:17:17 -0700 Subject: [PATCH 115/245] serialization: add test, make volumes null This is a spec change based on testing live Docker Hub configs. Volume may be null as found in the hub.docker.com/library/docker image. Signed-off-by: Brandon Philips --- schema/defs-config.json | 5 ++- schema/fs.go | 34 +++++++++---------- .../manifest_backwards_compatibility_test.go | 33 ++++++++++++++++++ serialization.md | 2 +- 4 files changed, 55 insertions(+), 19 deletions(-) diff --git a/schema/defs-config.json b/schema/defs-config.json index dbf3b72..c23bfb6 100644 --- a/schema/defs-config.json +++ b/schema/defs-config.json @@ -38,7 +38,10 @@ } }, "Volumes": { - "$ref": "defs.json#/definitions/mapStringObject" + "oneOf": [ + {"$ref": "defs.json#/definitions/mapStringObject"}, + {"type": "null"} + ] }, "WorkingDir": { "type": "string" diff --git a/schema/fs.go b/schema/fs.go index fb447c7..577d6cd 100644 --- a/schema/fs.go +++ b/schema/fs.go @@ -204,14 +204,14 @@ var _escData = map[string]*_escFile{ "/config-schema.json": { local: "config-schema.json", - size: 707, - modtime: 1466261692, + size: 710, + modtime: 1466466955, compressed: ` -H4sIAAAJbogA/5SRPW7DMAyF5/oUhpOxjjp0ytoDdOgJVJmKGcCiQDJDUPju1U/c2kvhLobx+L73JOqr -adtuAHGMUZFCd2679wjhjYJaDMBt+vN4aT8iOPTobHE9Z+woboTJZmRUjWdjrkKhr+qJ+GIGtl77l1dT -tUPlcFgQSQylNre0ScGq2+BkL2Bc6a+k3iNklj6v4LRqkVMCK4KkSb5O0hyDVRh+hBUqyhhqXNE98WQ1 -T4aE9IoTdGU2V0tnbzoS/xG1dbMbUdPhbgx7GZK9zscuVu4jgy+HBy99HZ/yKxxMUjBgfi1ZdrjJYiL1 -8v+sB7fJGlGU+F7CnlZ3sMz2/rvrtJhp3bi7c8l/cHOzfOdmbr4DAAD//1EkCUvDAgAA +H4sIAAAJbogA/5SRPW7DMAyFd5/CcDLWUYdOWXuADj2BKlMxA1gUSGYICt+9+olbGygKdzGMx/e9J1Gf +Tdt2A4hjjIoUunPbvUUIrxTUYgBu05/HS/sewaFHZ4vrKWNHcSNMNiOjajwbcxUKfVVPxBczsPXaP7+Y +qh0qh8OCSGIotbmlTQpW3QYnewHjSn8l9R4hs/RxBadVi5wSWBEkTfJ1kuYYrMLwLaxQUcZQ44ruiSer +eTIkpFecoCuzuVo6e9OR+I+orZvdiJoOd2PYy5DsdT52sXIfGXw5PHjp6/iUX+FgkoIB82vJssNNFhOp +l/9nPbhN1oiixPffrmGZ7f1n3Wk307p0d+1S8eDmZvnOzdx8BQAA//964XeexgIAAA== `, }, @@ -230,23 +230,23 @@ AmOHXKZBD4uOEV+XM+U8dnlDg+1xq8uuyj8F0tRsfnpH6lzhNtPHf5OoBSjA/iSYr5hmvgkqz9QjX/Z5 "/defs-config.json": { local: "defs-config.json", - size: 1755, - modtime: 1466261692, + size: 1828, + modtime: 1466466976, compressed: ` -H4sIAAAJbogA/7xUzc7TMBA8N09hBY6BXhAHri1HVKQIOFZusm63xF5rvQEi1HfHSauS/qRKKV8PVeOx -Z2bXY/t3olRaQigYvSC59INK52DQYTsKymsWLOpKsxJSCw9uRk40OmAVvwyuVe6hQIOF7vjZXvCoEAVb -jwgW3fLjOCLSeGgNabWFQjpqh3smD9EXQm91xL8E4BOkpxGE0a3T49Qu+8v7BJa4OWe+ZjAtMxYb3m4D -uVfTXt1TdPL+3S29/Kf2/09z5ut8o/ms5YckP/7yFKD8TCz3qlrt825DF/toruu7H0NpaGbdpFl/CgXs -eRk38otWA6bCjafY9vO9Z7Z8vulXqmp797EYFeA34u9xyRzH36qk/3/QSplITHjkZpdozBLLiy5ffnsr -3QAP+o7rf4NBTh+YuzegYNACg8frUEeWTCYRNcRWS5d+JL0RtHA9YF3Lhv7pyTzUs1xdPJuj2GQtDN/Q -W1SwXppll8oQfUVUgXaDqSTtb5f8CQAA//8Cok052wYAAA== +H4sIAAAJbogA/7xUzY7TMBC+5ymswDGwF8SBa5cjKlIEHBCq3GS8nSX2WOMJEK3y7jhpKUnaVC1le6ia +jPP9eD57nhKl0hJCwegFyaXvVHoPBh12b0F5zYJFXWlWQmrpwS3IiUYHrOKTwQeVeyjQYKF7fLYl3DNE +wk4jFov+8/17rEjjoROk9SMU0kP7umfyEHUhDL6O9U8BeFQZcARhdA/pfqnN/uI+gCVupsiXDKZDRrPh +9WMg9+Ju4PsOnbx9c4ov/6n9/+Nc+DrfaJ5s+SrK9788BSg/EsulrFb7vG/ochvNcX73Yy4NzaybNBsu +oYCd2jiRX5SaERVuPMVt3157Ycvbi36mqraHx4IcLLsEv47Yny4Nts0m+D/2XF1VaTtY/HbU3Rfi75Ht +Hs+/mMnwf8eVMpGYcM1wKNGYFZYHjXr+hCrdAM/qnrf/DQYZz6iLG1AwaIHZE7rzMWqAIbZa+uMSoa8E +LRy/6rqWDf3T7N25Wq0P5u9ZaLIW5q/6KShYL82qz2YOviaqQLvZbJLu1ya/AwAA//+5mfnPJAcAAA== `, }, "/defs-image.json": { local: "defs-image.json", size: 2528, - modtime: 1466180793, + modtime: 1466438460, compressed: ` H4sIAAAJbogA/7RWy27bMBC8+ysIJUAOfqjXGkGAoEGBnlIgPTVQgY20kphKpLqkgzqB/r2k3k+nrt0b ueQOZ4Zjym8LxpwAlU8801wKZ8ucOwy54HamWAakub9LgJiW7D5D8UkKDVwgsS8pRMgeMvR5yH0o2lcl diff --git a/schema/manifest_backwards_compatibility_test.go b/schema/manifest_backwards_compatibility_test.go index 6153d2e..876d1b0 100644 --- a/schema/manifest_backwards_compatibility_test.go +++ b/schema/manifest_backwards_compatibility_test.go @@ -187,3 +187,36 @@ func TestBackwardsCompatibilityManifest(t *testing.T) { } } } + +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, + }, + } { + 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.MediaTypeImageSerializationConfig.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/serialization.md b/serialization.md index 4fa1858..6e69188 100644 --- a/serialization.md +++ b/serialization.md @@ -297,7 +297,7 @@ Note: whitespace has been added to this example for clarity. Whitespace is OPTIO Volumes struct
    - A set of directories which should be created as data volumes in a container running this image. + 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.

    From d365b7994711ab6a4e096d36b8ad3975419bd033 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 16 Jun 2016 21:39:37 +0200 Subject: [PATCH 116/245] serialization: Entrypoint and Cmd nullable Signed-off-by: Antonio Murdaca --- schema/defs-config.json | 38 ++++++++++++++----- schema/fs.go | 19 +++++----- .../manifest_backwards_compatibility_test.go | 7 ++++ serialization.md | 4 +- 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/schema/defs-config.json b/schema/defs-config.json index c23bfb6..47ad2c6 100644 --- a/schema/defs-config.json +++ b/schema/defs-config.json @@ -26,21 +26,39 @@ } }, "Entrypoint": { - "type": "array", - "items": { - "type": "string" - } + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ] }, "Cmd": { - "type": "array", - "items": { - "type": "string" - } + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ] }, "Volumes": { "oneOf": [ - {"$ref": "defs.json#/definitions/mapStringObject"}, - {"type": "null"} + { + "$ref": "defs.json#/definitions/mapStringObject" + }, + { + "type": "null" + } ] }, "WorkingDir": { diff --git a/schema/fs.go b/schema/fs.go index 577d6cd..5f4db5d 100644 --- a/schema/fs.go +++ b/schema/fs.go @@ -230,16 +230,17 @@ AmOHXKZBD4uOEV+XM+U8dnlDg+1xq8uuyj8F0tRsfnpH6lzhNtPHf5OoBSjA/iSYr5hmvgkqz9QjX/Z5 "/defs-config.json": { local: "defs-config.json", - size: 1828, - modtime: 1466466976, + size: 2154, + modtime: 1466467007, compressed: ` -H4sIAAAJbogA/7xUzY7TMBC+5ymswDGwF8SBa5cjKlIEHBCq3GS8nSX2WOMJEK3y7jhpKUnaVC1le6ia -jPP9eD57nhKl0hJCwegFyaXvVHoPBh12b0F5zYJFXWlWQmrpwS3IiUYHrOKTwQeVeyjQYKF7fLYl3DNE -wk4jFov+8/17rEjjoROk9SMU0kP7umfyEHUhDL6O9U8BeFQZcARhdA/pfqnN/uI+gCVupsiXDKZDRrPh -9WMg9+Ju4PsOnbx9c4ov/6n9/+Nc+DrfaJ5s+SrK9788BSg/EsulrFb7vG/ochvNcX73Yy4NzaybNBsu -oYCd2jiRX5SaERVuPMVt3157Ycvbi36mqraHx4IcLLsEv47Yny4Nts0m+D/2XF1VaTtY/HbU3Rfi75Ht -Hs+/mMnwf8eVMpGYcM1wKNGYFZYHjXr+hCrdAM/qnrf/DQYZz6iLG1AwaIHZE7rzMWqAIbZa+uMSoa8E -LRy/6rqWDf3T7N25Wq0P5u9ZaLIW5q/6KShYL82qz2YOviaqQLvZbJLu1ya/AwAA//+5mfnPJAcAAA== +H4sIAAAJbogA/+RVzY7TMBC+5ymswLGwF8SB6y5HVKQIOCBUucl4O0vsscYTIEL77jjZquSnCWlLT3uo +mkz8/cyMPf6dKJUWEHJGL0gufafSOzDosHkLymsWzKtSsxJSaw/ulpxodMAqPhm8V5mHHA3musWvnggP +DJGw0YjBvF1+eI8RqT00grR9gFxaaBv3TB6iLoTO6hj/FIB7kQ5HEEZ3nx4+Pa7+4j6AJa6HyJcMpkFG +s+H1QyD34qbj+wadvH0zx5f91P7/cd76KttpHqR8EeX7X54CFB+J5VRWq33WFnT91Jrj/O7HVDc0s67T +VfcTCtihjZn+RakJUeHaU0x7qE0O1k1OX3sCfblZizM2/2G1b3dgedaFq8qyz9Tl+XZ8q9ji2eb+mcrK +jg/JwvzP3fXXzuoL8fcoe4fLx1vS/d9zpUwkJlwyYgs0ZoPFqMDXP9ilroEndZflv8Mg/Ul/cgFyBi0w +OmADH70CGGKrpd1XEfpK0MLxgakr2dFZN9je1WY7usUWoclaGA/MJVCwXupN25sp+JaoBO0me5M0v8fk +TwAAAP//dkZ6ZWoIAAA= `, }, diff --git a/schema/manifest_backwards_compatibility_test.go b/schema/manifest_backwards_compatibility_test.go index 876d1b0..531cc77 100644 --- a/schema/manifest_backwards_compatibility_test.go +++ b/schema/manifest_backwards_compatibility_test.go @@ -204,6 +204,13 @@ func TestBackwardsCompatibilityConfig(t *testing.T) { 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[:])) diff --git a/serialization.md b/serialization.md index 6e69188..3f037e4 100644 --- a/serialization.md +++ b/serialization.md @@ -283,7 +283,7 @@ Note: whitespace has been added to this example for clarity. Whitespace is OPTIO
    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 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 @@ -291,7 +291,7 @@ Note: whitespace has been added to this example for clarity. Whitespace is OPTIO
    Default arguments to the entry point 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. + 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 From fab787e6c7b5019a7db67ebee99a9cfff7aba16a Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Thu, 16 Jun 2016 09:25:42 +0200 Subject: [PATCH 117/245] glide: introduce glide.yaml Signed-off-by: Sergiusz Urbaniak --- .tool/lint | 2 +- glide.yaml | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 glide.yaml diff --git a/.tool/lint b/.tool/lint index 062eb9b..f25a668 100755 --- a/.tool/lint +++ b/.tool/lint @@ -9,7 +9,7 @@ if [ ! $(command -v gometalinter) ]; then gometalinter --update --install fi -for d in $(find . -type d -not -iwholename '*.git*' -a -not -iname '.tool'); do +for d in $(find . -type d -not -iwholename '*.git*' -a -not -iname '.tool' -a -not -iwholename '*vendor*'); do gometalinter \ --exclude='error return value not checked.*(Close|Log|Print).*\(errcheck\)$' \ --exclude='.*_test\.go:.*error return value not checked.*\(errcheck\)$' \ diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 0000000..eb2f54f --- /dev/null +++ b/glide.yaml @@ -0,0 +1,10 @@ +package: github.com/opencontainers/image-spec +import: +- package: github.com/opencontainers/runtime-spec + version: ^1.0.0-rc1 + subpackages: + - specs-go +- package: github.com/pkg/errors + version: ">=0.7.0" +- package: github.com/spf13/cobra +- package: github.com/xeipuuv/gojsonschema From ecb2556e949ed3cba0a15c85a1edbfd59ed482ab Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Thu, 16 Jun 2016 11:47:18 +0200 Subject: [PATCH 118/245] Makefile: implemented update-deps target, add hacking guide Signed-off-by: Sergiusz Urbaniak --- HACKING.md | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 22 +++++++++- glide.yaml | 1 + 3 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 HACKING.md diff --git a/HACKING.md b/HACKING.md new file mode 100644 index 0000000..45f59b3 --- /dev/null +++ b/HACKING.md @@ -0,0 +1,124 @@ +# 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 903d8d4..8d983c0 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +GO15VENDOREXPERIMENT=1 +export GO15VENDOREXPERIMENT DOCKER ?= $(shell which docker) # These docs are in an order that determines how they show up in the PDF/HTML docs. @@ -24,7 +26,14 @@ help: @echo @echo " * 'docs' - produce document in the $(OUTPUT) directory" @echo " * 'fmt' - format the json with indentation" - @echo " * 'validate' - build the validation tool" + @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 " * '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 @@ -81,7 +90,13 @@ lint: test: go test -race ./... -img/%.png: %.dot +update-deps: + glide update --strip-vcs --strip-vendor --update-vendored --delete + glide-vc --only-code --no-tests + # see http://sed.sourceforge.net/sed1line.txt + for f in $$(find vendor -type f); do sed -i -e :a -e '/^\n*$$/{$$d;N;ba' -e '}' $$f; done + +img/%.png: img/%.dot dot -Tpng $^ > $@ .PHONY: .gitvalidation @@ -96,6 +111,7 @@ else endif .PHONY: install.tools + install.tools: .install.gitvalidation .install.gitvalidation: @@ -103,6 +119,8 @@ install.tools: .install.gitvalidation clean: rm -rf *~ $(OUTPUT) + rm -f oci-image-tool + .PHONY: \ validate-examples \ oci-image-tool \ diff --git a/glide.yaml b/glide.yaml index eb2f54f..496a379 100644 --- a/glide.yaml +++ b/glide.yaml @@ -8,3 +8,4 @@ import: version: ">=0.7.0" - package: github.com/spf13/cobra - package: github.com/xeipuuv/gojsonschema + version: d5336c75940ef31c9ceeb0ae64cf92944bccb4ee From a664febfc07d78e8e2ea8572f722cb213a6ac3de Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Thu, 23 Jun 2016 09:01:46 +0200 Subject: [PATCH 119/245] glide: vendor dependencies Signed-off-by: Sergiusz Urbaniak --- glide.lock | 26 + .../inconshreveable/mousetrap/LICENSE | 13 + .../inconshreveable/mousetrap/trap_others.go | 15 + .../inconshreveable/mousetrap/trap_windows.go | 98 ++ .../mousetrap/trap_windows_1.4.go | 46 + .../opencontainers/runtime-spec/LICENSE | 191 +++ .../runtime-spec/specs-go/config.go | 471 +++++++ .../runtime-spec/specs-go/state.go | 17 + .../runtime-spec/specs-go/version.go | 18 + vendor/github.com/pkg/errors/LICENSE | 23 + vendor/github.com/pkg/errors/errors.go | 211 +++ vendor/github.com/pkg/errors/stack.go | 165 +++ vendor/github.com/spf13/cobra/LICENSE.txt | 174 +++ .../spf13/cobra/bash_completions.go | 630 +++++++++ vendor/github.com/spf13/cobra/cobra.go | 175 +++ vendor/github.com/spf13/cobra/command.go | 1256 +++++++++++++++++ .../github.com/spf13/cobra/command_notwin.go | 5 + vendor/github.com/spf13/cobra/command_win.go | 26 + vendor/github.com/spf13/pflag/LICENSE | 28 + vendor/github.com/spf13/pflag/bool.go | 97 ++ vendor/github.com/spf13/pflag/count.go | 97 ++ vendor/github.com/spf13/pflag/duration.go | 86 ++ vendor/github.com/spf13/pflag/flag.go | 934 ++++++++++++ vendor/github.com/spf13/pflag/float32.go | 91 ++ vendor/github.com/spf13/pflag/float64.go | 87 ++ vendor/github.com/spf13/pflag/golangflag.go | 104 ++ vendor/github.com/spf13/pflag/int.go | 87 ++ vendor/github.com/spf13/pflag/int32.go | 91 ++ vendor/github.com/spf13/pflag/int64.go | 87 ++ vendor/github.com/spf13/pflag/int8.go | 91 ++ vendor/github.com/spf13/pflag/int_slice.go | 128 ++ vendor/github.com/spf13/pflag/ip.go | 96 ++ vendor/github.com/spf13/pflag/ipmask.go | 122 ++ vendor/github.com/spf13/pflag/ipnet.go | 100 ++ vendor/github.com/spf13/pflag/string.go | 82 ++ vendor/github.com/spf13/pflag/string_slice.go | 111 ++ vendor/github.com/spf13/pflag/uint.go | 91 ++ vendor/github.com/spf13/pflag/uint16.go | 89 ++ vendor/github.com/spf13/pflag/uint32.go | 89 ++ vendor/github.com/spf13/pflag/uint64.go | 91 ++ vendor/github.com/spf13/pflag/uint8.go | 91 ++ .../gojsonpointer/LICENSE-APACHE-2.0.txt | 202 +++ .../xeipuuv/gojsonpointer/pointer.go | 217 +++ .../gojsonreference/LICENSE-APACHE-2.0.txt | 202 +++ .../xeipuuv/gojsonreference/reference.go | 141 ++ .../gojsonschema/LICENSE-APACHE-2.0.txt | 202 +++ .../github.com/xeipuuv/gojsonschema/errors.go | 242 ++++ .../xeipuuv/gojsonschema/format_checkers.go | 178 +++ .../xeipuuv/gojsonschema/internalLog.go | 37 + .../xeipuuv/gojsonschema/jsonContext.go | 72 + .../xeipuuv/gojsonschema/jsonLoader.go | 314 +++++ .../xeipuuv/gojsonschema/locales.go | 275 ++++ .../github.com/xeipuuv/gojsonschema/result.go | 171 +++ .../github.com/xeipuuv/gojsonschema/schema.go | 908 ++++++++++++ .../xeipuuv/gojsonschema/schemaPool.go | 110 ++ .../gojsonschema/schemaReferencePool.go | 67 + .../xeipuuv/gojsonschema/schemaType.go | 83 ++ .../xeipuuv/gojsonschema/subSchema.go | 227 +++ .../github.com/xeipuuv/gojsonschema/types.go | 58 + .../github.com/xeipuuv/gojsonschema/utils.go | 203 +++ .../xeipuuv/gojsonschema/validation.go | 829 +++++++++++ vendor/go4.org/LICENSE | 201 +++ vendor/go4.org/errorutil/highlight.go | 58 + 63 files changed, 11527 insertions(+) create mode 100644 glide.lock create mode 100644 vendor/github.com/inconshreveable/mousetrap/LICENSE create mode 100644 vendor/github.com/inconshreveable/mousetrap/trap_others.go create mode 100644 vendor/github.com/inconshreveable/mousetrap/trap_windows.go create mode 100644 vendor/github.com/inconshreveable/mousetrap/trap_windows_1.4.go create mode 100644 vendor/github.com/opencontainers/runtime-spec/LICENSE create mode 100644 vendor/github.com/opencontainers/runtime-spec/specs-go/config.go create mode 100644 vendor/github.com/opencontainers/runtime-spec/specs-go/state.go create mode 100644 vendor/github.com/opencontainers/runtime-spec/specs-go/version.go create mode 100644 vendor/github.com/pkg/errors/LICENSE create mode 100644 vendor/github.com/pkg/errors/errors.go create mode 100644 vendor/github.com/pkg/errors/stack.go create mode 100644 vendor/github.com/spf13/cobra/LICENSE.txt create mode 100644 vendor/github.com/spf13/cobra/bash_completions.go create mode 100644 vendor/github.com/spf13/cobra/cobra.go create mode 100644 vendor/github.com/spf13/cobra/command.go create mode 100644 vendor/github.com/spf13/cobra/command_notwin.go create mode 100644 vendor/github.com/spf13/cobra/command_win.go create mode 100644 vendor/github.com/spf13/pflag/LICENSE create mode 100644 vendor/github.com/spf13/pflag/bool.go create mode 100644 vendor/github.com/spf13/pflag/count.go create mode 100644 vendor/github.com/spf13/pflag/duration.go create mode 100644 vendor/github.com/spf13/pflag/flag.go create mode 100644 vendor/github.com/spf13/pflag/float32.go create mode 100644 vendor/github.com/spf13/pflag/float64.go create mode 100644 vendor/github.com/spf13/pflag/golangflag.go create mode 100644 vendor/github.com/spf13/pflag/int.go create mode 100644 vendor/github.com/spf13/pflag/int32.go create mode 100644 vendor/github.com/spf13/pflag/int64.go create mode 100644 vendor/github.com/spf13/pflag/int8.go create mode 100644 vendor/github.com/spf13/pflag/int_slice.go create mode 100644 vendor/github.com/spf13/pflag/ip.go create mode 100644 vendor/github.com/spf13/pflag/ipmask.go create mode 100644 vendor/github.com/spf13/pflag/ipnet.go create mode 100644 vendor/github.com/spf13/pflag/string.go create mode 100644 vendor/github.com/spf13/pflag/string_slice.go create mode 100644 vendor/github.com/spf13/pflag/uint.go create mode 100644 vendor/github.com/spf13/pflag/uint16.go create mode 100644 vendor/github.com/spf13/pflag/uint32.go create mode 100644 vendor/github.com/spf13/pflag/uint64.go create mode 100644 vendor/github.com/spf13/pflag/uint8.go create mode 100644 vendor/github.com/xeipuuv/gojsonpointer/LICENSE-APACHE-2.0.txt create mode 100644 vendor/github.com/xeipuuv/gojsonpointer/pointer.go create mode 100644 vendor/github.com/xeipuuv/gojsonreference/LICENSE-APACHE-2.0.txt create mode 100644 vendor/github.com/xeipuuv/gojsonreference/reference.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/LICENSE-APACHE-2.0.txt create mode 100644 vendor/github.com/xeipuuv/gojsonschema/errors.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/format_checkers.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/internalLog.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/jsonContext.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/jsonLoader.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/locales.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/result.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/schema.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/schemaPool.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/schemaReferencePool.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/schemaType.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/subSchema.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/types.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/utils.go create mode 100644 vendor/github.com/xeipuuv/gojsonschema/validation.go create mode 100644 vendor/go4.org/LICENSE create mode 100644 vendor/go4.org/errorutil/highlight.go diff --git a/glide.lock b/glide.lock new file mode 100644 index 0000000..3329039 --- /dev/null +++ b/glide.lock @@ -0,0 +1,26 @@ +hash: 9eee25c418553e1d79fe7fb956eae4b056402d2c1fe7c8c79f5849da571dd99b +updated: 2016-06-23T09:00:42.232695928+02:00 +imports: +- name: github.com/inconshreveable/mousetrap + version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +- name: github.com/opencontainers/runtime-spec + version: 06479209bdc0d4135911688c18157bd39bd99c22 + subpackages: + - specs-go +- name: github.com/pkg/errors + version: 01fa4104b9c248c8945d14d9f128454d5b28d595 +- name: github.com/spf13/cobra + version: 6a8bd97bdb1fc0d08a83459940498ea49d3e8c93 +- name: github.com/spf13/pflag + version: 367864438f1b1a3c7db4da06a2f55b144e6784e0 +- name: github.com/xeipuuv/gojsonpointer + version: e0fe6f68307607d540ed8eac07a342c33fa1b54a +- name: github.com/xeipuuv/gojsonreference + version: e02fc20de94c78484cd5ffb007f8af96be030a45 +- name: github.com/xeipuuv/gojsonschema + version: d5336c75940ef31c9ceeb0ae64cf92944bccb4ee +- name: go4.org + version: 15c19124e43b90eba9aa27b4341e38365254a84a + subpackages: + - errorutil +devImports: [] diff --git a/vendor/github.com/inconshreveable/mousetrap/LICENSE b/vendor/github.com/inconshreveable/mousetrap/LICENSE new file mode 100644 index 0000000..5f0d1fb --- /dev/null +++ b/vendor/github.com/inconshreveable/mousetrap/LICENSE @@ -0,0 +1,13 @@ +Copyright 2014 Alan Shreve + +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/vendor/github.com/inconshreveable/mousetrap/trap_others.go b/vendor/github.com/inconshreveable/mousetrap/trap_others.go new file mode 100644 index 0000000..9d2d8a4 --- /dev/null +++ b/vendor/github.com/inconshreveable/mousetrap/trap_others.go @@ -0,0 +1,15 @@ +// +build !windows + +package mousetrap + +// StartedByExplorer returns true if the program was invoked by the user +// double-clicking on the executable from explorer.exe +// +// It is conservative and returns false if any of the internal calls fail. +// It does not guarantee that the program was run from a terminal. It only can tell you +// whether it was launched from explorer.exe +// +// On non-Windows platforms, it always returns false. +func StartedByExplorer() bool { + return false +} diff --git a/vendor/github.com/inconshreveable/mousetrap/trap_windows.go b/vendor/github.com/inconshreveable/mousetrap/trap_windows.go new file mode 100644 index 0000000..336142a --- /dev/null +++ b/vendor/github.com/inconshreveable/mousetrap/trap_windows.go @@ -0,0 +1,98 @@ +// +build windows +// +build !go1.4 + +package mousetrap + +import ( + "fmt" + "os" + "syscall" + "unsafe" +) + +const ( + // defined by the Win32 API + th32cs_snapprocess uintptr = 0x2 +) + +var ( + kernel = syscall.MustLoadDLL("kernel32.dll") + CreateToolhelp32Snapshot = kernel.MustFindProc("CreateToolhelp32Snapshot") + Process32First = kernel.MustFindProc("Process32FirstW") + Process32Next = kernel.MustFindProc("Process32NextW") +) + +// ProcessEntry32 structure defined by the Win32 API +type processEntry32 struct { + dwSize uint32 + cntUsage uint32 + th32ProcessID uint32 + th32DefaultHeapID int + th32ModuleID uint32 + cntThreads uint32 + th32ParentProcessID uint32 + pcPriClassBase int32 + dwFlags uint32 + szExeFile [syscall.MAX_PATH]uint16 +} + +func getProcessEntry(pid int) (pe *processEntry32, err error) { + snapshot, _, e1 := CreateToolhelp32Snapshot.Call(th32cs_snapprocess, uintptr(0)) + if snapshot == uintptr(syscall.InvalidHandle) { + err = fmt.Errorf("CreateToolhelp32Snapshot: %v", e1) + return + } + defer syscall.CloseHandle(syscall.Handle(snapshot)) + + var processEntry processEntry32 + processEntry.dwSize = uint32(unsafe.Sizeof(processEntry)) + ok, _, e1 := Process32First.Call(snapshot, uintptr(unsafe.Pointer(&processEntry))) + if ok == 0 { + err = fmt.Errorf("Process32First: %v", e1) + return + } + + for { + if processEntry.th32ProcessID == uint32(pid) { + pe = &processEntry + return + } + + ok, _, e1 = Process32Next.Call(snapshot, uintptr(unsafe.Pointer(&processEntry))) + if ok == 0 { + err = fmt.Errorf("Process32Next: %v", e1) + return + } + } +} + +func getppid() (pid int, err error) { + pe, err := getProcessEntry(os.Getpid()) + if err != nil { + return + } + + pid = int(pe.th32ParentProcessID) + return +} + +// StartedByExplorer returns true if the program was invoked by the user double-clicking +// on the executable from explorer.exe +// +// It is conservative and returns false if any of the internal calls fail. +// It does not guarantee that the program was run from a terminal. It only can tell you +// whether it was launched from explorer.exe +func StartedByExplorer() bool { + ppid, err := getppid() + if err != nil { + return false + } + + pe, err := getProcessEntry(ppid) + if err != nil { + return false + } + + name := syscall.UTF16ToString(pe.szExeFile[:]) + return name == "explorer.exe" +} diff --git a/vendor/github.com/inconshreveable/mousetrap/trap_windows_1.4.go b/vendor/github.com/inconshreveable/mousetrap/trap_windows_1.4.go new file mode 100644 index 0000000..9a28e57 --- /dev/null +++ b/vendor/github.com/inconshreveable/mousetrap/trap_windows_1.4.go @@ -0,0 +1,46 @@ +// +build windows +// +build go1.4 + +package mousetrap + +import ( + "os" + "syscall" + "unsafe" +) + +func getProcessEntry(pid int) (*syscall.ProcessEntry32, error) { + snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0) + if err != nil { + return nil, err + } + defer syscall.CloseHandle(snapshot) + var procEntry syscall.ProcessEntry32 + procEntry.Size = uint32(unsafe.Sizeof(procEntry)) + if err = syscall.Process32First(snapshot, &procEntry); err != nil { + return nil, err + } + for { + if procEntry.ProcessID == uint32(pid) { + return &procEntry, nil + } + err = syscall.Process32Next(snapshot, &procEntry) + if err != nil { + return nil, err + } + } +} + +// StartedByExplorer returns true if the program was invoked by the user double-clicking +// on the executable from explorer.exe +// +// It is conservative and returns false if any of the internal calls fail. +// It does not guarantee that the program was run from a terminal. It only can tell you +// whether it was launched from explorer.exe +func StartedByExplorer() bool { + pe, err := getProcessEntry(os.Getppid()) + if err != nil { + return false + } + return "explorer.exe" == syscall.UTF16ToString(pe.ExeFile[:]) +} diff --git a/vendor/github.com/opencontainers/runtime-spec/LICENSE b/vendor/github.com/opencontainers/runtime-spec/LICENSE new file mode 100644 index 0000000..bdc4036 --- /dev/null +++ b/vendor/github.com/opencontainers/runtime-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 2015 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/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go new file mode 100644 index 0000000..ec99035 --- /dev/null +++ b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go @@ -0,0 +1,471 @@ +package specs + +import "os" + +// Spec is the base configuration for the container. +type Spec struct { + // Version is the version of the specification that is supported. + Version string `json:"ociVersion"` + // Platform is the host information for OS and Arch. + Platform Platform `json:"platform"` + // Process is the container's main process. + Process Process `json:"process"` + // Root is the root information for the container's filesystem. + Root Root `json:"root"` + // Hostname is the container's host name. + Hostname string `json:"hostname,omitempty"` + // Mounts profile configuration for adding mounts to the container's filesystem. + Mounts []Mount `json:"mounts,omitempty"` + // Hooks are the commands run at various lifecycle events of the container. + Hooks Hooks `json:"hooks"` + // Annotations is an unstructured key value map that may be set by external tools to store and retrieve arbitrary metadata. + Annotations map[string]string `json:"annotations,omitempty"` + + // Linux is platform specific configuration for Linux based containers. + Linux Linux `json:"linux" platform:"linux,omitempty"` + // Solaris is platform specific configuration for Solaris containers. + Solaris Solaris `json:"solaris" platform:"solaris,omitempty"` +} + +// Process contains information to start a specific application inside the container. +type Process struct { + // Terminal creates an interactive terminal for the container. + Terminal bool `json:"terminal,omitempty"` + // User specifies user information for the process. + User User `json:"user"` + // Args specifies the binary and arguments for the application to execute. + Args []string `json:"args"` + // Env populates the process environment for the process. + Env []string `json:"env,omitempty"` + // Cwd is the current working directory for the process and must be + // relative to the container's root. + Cwd string `json:"cwd"` + // Capabilities are Linux capabilities that are kept for the container. + Capabilities []string `json:"capabilities,omitempty" platform:"linux"` + // Rlimits specifies rlimit options to apply to the process. + Rlimits []Rlimit `json:"rlimits,omitempty"` + // NoNewPrivileges controls whether additional privileges could be gained by processes in the container. + NoNewPrivileges bool `json:"noNewPrivileges,omitempty"` + + // ApparmorProfile specified the apparmor profile for the container. (this field is platform dependent) + ApparmorProfile string `json:"apparmorProfile,omitempty" platform:"linux"` + // SelinuxLabel specifies the selinux context that the container process is run as. (this field is platform dependent) + SelinuxLabel string `json:"selinuxLabel,omitempty" platform:"linux"` +} + +// User specifies Linux specific user and group information for the container's +// main process. +type User struct { + // UID is the user id. (this field is platform dependent) + UID uint32 `json:"uid" platform:"linux"` + // GID is the group id. (this field is platform dependent) + GID uint32 `json:"gid" platform:"linux"` + // AdditionalGids are additional group ids set for the container's process. (this field is platform dependent) + AdditionalGids []uint32 `json:"additionalGids,omitempty" platform:"linux"` +} + +// Root contains information about the container's root filesystem on the host. +type Root struct { + // Path is the absolute path to the container's root filesystem. + Path string `json:"path"` + // Readonly makes the root filesystem for the container readonly before the process is executed. + Readonly bool `json:"readonly,omitempty"` +} + +// Platform specifies OS and arch information for the host system that the container +// is created for. +type Platform struct { + // OS is the operating system. + OS string `json:"os"` + // Arch is the architecture + Arch string `json:"arch"` +} + +// Mount specifies a mount for a container. +type Mount struct { + // Destination is the path where the mount will be placed relative to the container's root. The path and child directories MUST exist, a runtime MUST NOT create directories automatically to a mount point. + Destination string `json:"destination"` + // Type specifies the mount kind. + Type string `json:"type"` + // Source specifies the source path of the mount. In the case of bind mounts on + // Linux based systems this would be the file on the host. + Source string `json:"source"` + // Options are fstab style mount options. + Options []string `json:"options,omitempty"` +} + +// Hook specifies a command that is run at a particular event in the lifecycle of a container +type Hook struct { + Path string `json:"path"` + Args []string `json:"args,omitempty"` + Env []string `json:"env,omitempty"` + Timeout *int `json:"timeout,omitempty"` +} + +// Hooks for container setup and teardown +type Hooks struct { + // Prestart is a list of hooks to be run before the container process is executed. + // On Linux, they are run after the container namespaces are created. + Prestart []Hook `json:"prestart,omitempty"` + // Poststart is a list of hooks to be run after the container process is started. + Poststart []Hook `json:"poststart,omitempty"` + // Poststop is a list of hooks to be run after the container process exits. + Poststop []Hook `json:"poststop,omitempty"` +} + +// Linux contains platform specific configuration for Linux based containers. +type Linux struct { + // UIDMapping specifies user mappings for supporting user namespaces on Linux. + UIDMappings []IDMapping `json:"uidMappings,omitempty"` + // GIDMapping specifies group mappings for supporting user namespaces on Linux. + GIDMappings []IDMapping `json:"gidMappings,omitempty"` + // Sysctl are a set of key value pairs that are set for the container on start + Sysctl map[string]string `json:"sysctl,omitempty"` + // Resources contain cgroup information for handling resource constraints + // for the container + Resources *Resources `json:"resources,omitempty"` + // CgroupsPath specifies the path to cgroups that are created and/or joined by the container. + // The path is expected to be relative to the cgroups mountpoint. + // If resources are specified, the cgroups at CgroupsPath will be updated based on resources. + CgroupsPath *string `json:"cgroupsPath,omitempty"` + // Namespaces contains the namespaces that are created and/or joined by the container + Namespaces []Namespace `json:"namespaces,omitempty"` + // Devices are a list of device nodes that are created for the container + Devices []Device `json:"devices,omitempty"` + // Seccomp specifies the seccomp security settings for the container. + Seccomp *Seccomp `json:"seccomp,omitempty"` + // RootfsPropagation is the rootfs mount propagation mode for the container. + RootfsPropagation string `json:"rootfsPropagation,omitempty"` + // MaskedPaths masks over the provided paths inside the container. + MaskedPaths []string `json:"maskedPaths,omitempty"` + // ReadonlyPaths sets the provided paths as RO inside the container. + ReadonlyPaths []string `json:"readonlyPaths,omitempty"` + // MountLabel specifies the selinux context for the mounts in the container. + MountLabel string `json:"mountLabel,omitempty"` +} + +// Namespace is the configuration for a Linux namespace +type Namespace struct { + // Type is the type of Linux namespace + Type NamespaceType `json:"type"` + // Path is a path to an existing namespace persisted on disk that can be joined + // and is of the same type + Path string `json:"path,omitempty"` +} + +// NamespaceType is one of the Linux namespaces +type NamespaceType string + +const ( + // PIDNamespace for isolating process IDs + PIDNamespace NamespaceType = "pid" + // NetworkNamespace for isolating network devices, stacks, ports, etc + NetworkNamespace = "network" + // MountNamespace for isolating mount points + MountNamespace = "mount" + // IPCNamespace for isolating System V IPC, POSIX message queues + IPCNamespace = "ipc" + // UTSNamespace for isolating hostname and NIS domain name + UTSNamespace = "uts" + // UserNamespace for isolating user and group IDs + UserNamespace = "user" + // CgroupNamespace for isolating cgroup hierarchies + CgroupNamespace = "cgroup" +) + +// IDMapping specifies UID/GID mappings +type IDMapping struct { + // HostID is the UID/GID of the host user or group + HostID uint32 `json:"hostID"` + // ContainerID is the UID/GID of the container's user or group + ContainerID uint32 `json:"containerID"` + // Size is the length of the range of IDs mapped between the two namespaces + Size uint32 `json:"size"` +} + +// Rlimit type and restrictions +type Rlimit struct { + // Type of the rlimit to set + Type string `json:"type"` + // Hard is the hard limit for the specified type + Hard uint64 `json:"hard"` + // Soft is the soft limit for the specified type + Soft uint64 `json:"soft"` +} + +// HugepageLimit structure corresponds to limiting kernel hugepages +type HugepageLimit struct { + // Pagesize is the hugepage size + Pagesize *string `json:"pageSize,omitempty"` + // Limit is the limit of "hugepagesize" hugetlb usage + Limit *uint64 `json:"limit,omitempty"` +} + +// InterfacePriority for network interfaces +type InterfacePriority struct { + // Name is the name of the network interface + Name string `json:"name"` + // Priority for the interface + Priority uint32 `json:"priority"` +} + +// blockIODevice holds major:minor format supported in blkio cgroup +type blockIODevice struct { + // Major is the device's major number. + Major int64 `json:"major"` + // Minor is the device's minor number. + Minor int64 `json:"minor"` +} + +// WeightDevice struct holds a `major:minor weight` pair for blkioWeightDevice +type WeightDevice struct { + blockIODevice + // Weight is the bandwidth rate for the device, range is from 10 to 1000 + Weight *uint16 `json:"weight,omitempty"` + // LeafWeight is the bandwidth rate for the device while competing with the cgroup's child cgroups, range is from 10 to 1000, CFQ scheduler only + LeafWeight *uint16 `json:"leafWeight,omitempty"` +} + +// ThrottleDevice struct holds a `major:minor rate_per_second` pair +type ThrottleDevice struct { + blockIODevice + // Rate is the IO rate limit per cgroup per device + Rate *uint64 `json:"rate,omitempty"` +} + +// BlockIO for Linux cgroup 'blkio' resource management +type BlockIO struct { + // Specifies per cgroup weight, range is from 10 to 1000 + Weight *uint16 `json:"blkioWeight,omitempty"` + // Specifies tasks' weight in the given cgroup while competing with the cgroup's child cgroups, range is from 10 to 1000, CFQ scheduler only + LeafWeight *uint16 `json:"blkioLeafWeight,omitempty"` + // Weight per cgroup per device, can override BlkioWeight + WeightDevice []WeightDevice `json:"blkioWeightDevice,omitempty"` + // IO read rate limit per cgroup per device, bytes per second + ThrottleReadBpsDevice []ThrottleDevice `json:"blkioThrottleReadBpsDevice,omitempty"` + // IO write rate limit per cgroup per device, bytes per second + ThrottleWriteBpsDevice []ThrottleDevice `json:"blkioThrottleWriteBpsDevice,omitempty"` + // IO read rate limit per cgroup per device, IO per second + ThrottleReadIOPSDevice []ThrottleDevice `json:"blkioThrottleReadIOPSDevice,omitempty"` + // IO write rate limit per cgroup per device, IO per second + ThrottleWriteIOPSDevice []ThrottleDevice `json:"blkioThrottleWriteIOPSDevice,omitempty"` +} + +// Memory for Linux cgroup 'memory' resource management +type Memory struct { + // Memory limit (in bytes). + Limit *uint64 `json:"limit,omitempty"` + // Memory reservation or soft_limit (in bytes). + Reservation *uint64 `json:"reservation,omitempty"` + // Total memory limit (memory + swap). + Swap *uint64 `json:"swap,omitempty"` + // Kernel memory limit (in bytes). + Kernel *uint64 `json:"kernel,omitempty"` + // Kernel memory limit for tcp (in bytes) + KernelTCP *uint64 `json:"kernelTCP"` + // How aggressive the kernel will swap memory pages. Range from 0 to 100. + Swappiness *uint64 `json:"swappiness,omitempty"` +} + +// CPU for Linux cgroup 'cpu' resource management +type CPU struct { + // CPU shares (relative weight (ratio) vs. other cgroups with cpu shares). + Shares *uint64 `json:"shares,omitempty"` + // CPU hardcap limit (in usecs). Allowed cpu time in a given period. + Quota *uint64 `json:"quota,omitempty"` + // CPU period to be used for hardcapping (in usecs). + Period *uint64 `json:"period,omitempty"` + // How much time realtime scheduling may use (in usecs). + RealtimeRuntime *uint64 `json:"realtimeRuntime,omitempty"` + // CPU period to be used for realtime scheduling (in usecs). + RealtimePeriod *uint64 `json:"realtimePeriod,omitempty"` + // CPUs to use within the cpuset. Default is to use any CPU available. + Cpus *string `json:"cpus,omitempty"` + // List of memory nodes in the cpuset. Default is to use any available memory node. + Mems *string `json:"mems,omitempty"` +} + +// Pids for Linux cgroup 'pids' resource management (Linux 4.3) +type Pids struct { + // Maximum number of PIDs. Default is "no limit". + Limit *int64 `json:"limit,omitempty"` +} + +// Network identification and priority configuration +type Network struct { + // Set class identifier for container's network packets + ClassID *uint32 `json:"classID"` + // Set priority of network traffic for container + Priorities []InterfacePriority `json:"priorities,omitempty"` +} + +// Resources has container runtime resource constraints +type Resources struct { + // Devices are a list of device rules for the whitelist controller + Devices []DeviceCgroup `json:"devices"` + // DisableOOMKiller disables the OOM killer for out of memory conditions + DisableOOMKiller *bool `json:"disableOOMKiller,omitempty"` + // Specify an oom_score_adj for the container. + OOMScoreAdj *int `json:"oomScoreAdj,omitempty"` + // Memory restriction configuration + Memory *Memory `json:"memory,omitempty"` + // CPU resource restriction configuration + CPU *CPU `json:"cpu,omitempty"` + // Task resource restriction configuration. + Pids *Pids `json:"pids,omitempty"` + // BlockIO restriction configuration + BlockIO *BlockIO `json:"blockIO,omitempty"` + // Hugetlb limit (in bytes) + HugepageLimits []HugepageLimit `json:"hugepageLimits,omitempty"` + // Network restriction configuration + Network *Network `json:"network,omitempty"` +} + +// Device represents the mknod information for a Linux special device file +type Device struct { + // Path to the device. + Path string `json:"path"` + // Device type, block, char, etc. + Type string `json:"type"` + // Major is the device's major number. + Major int64 `json:"major"` + // Minor is the device's minor number. + Minor int64 `json:"minor"` + // FileMode permission bits for the device. + FileMode *os.FileMode `json:"fileMode,omitempty"` + // UID of the device. + UID *uint32 `json:"uid,omitempty"` + // Gid of the device. + GID *uint32 `json:"gid,omitempty"` +} + +// DeviceCgroup represents a device rule for the whitelist controller +type DeviceCgroup struct { + // Allow or deny + Allow bool `json:"allow"` + // Device type, block, char, etc. + Type *string `json:"type,omitempty"` + // Major is the device's major number. + Major *int64 `json:"major,omitempty"` + // Minor is the device's minor number. + Minor *int64 `json:"minor,omitempty"` + // Cgroup access permissions format, rwm. + Access *string `json:"access,omitempty"` +} + +// Seccomp represents syscall restrictions +type Seccomp struct { + DefaultAction Action `json:"defaultAction"` + Architectures []Arch `json:"architectures"` + Syscalls []Syscall `json:"syscalls,omitempty"` +} + +// Solaris contains platform specific configuration for Solaris application containers. +type Solaris struct { + // SMF FMRI which should go "online" before we start the container process. + Milestone string `json:"milestone,omitempty"` + // Maximum set of privileges any process in this container can obtain. + LimitPriv string `json:"limitpriv,omitempty"` + // The maximum amount of shared memory allowed for this container. + MaxShmMemory string `json:"maxShmMemory,omitempty"` + // Specification for automatic creation of network resources for this container. + Anet []Anet `json:"anet,omitempty"` + // Set limit on the amount of CPU time that can be used by container. + CappedCPU CappedCPU `json:"cappedCPU,omitempty"` + // The physical and swap caps on the memory that can be used by this container. + CappedMemory CappedMemory `json:"cappedMemory,omitempty"` +} + +// CappedCPU allows users to set limit on the amount of CPU time that can be used by container. +type CappedCPU struct { + Ncpus string `json:"ncpus,omitempty"` +} + +// CappedMemory allows users to set the physical and swap caps on the memory that can be used by this container. +type CappedMemory struct { + Physical string `json:"physical,omitempty"` + Swap string `json:"swap,omitempty"` +} + +// Anet provides the specification for automatic creation of network resources for this container. +type Anet struct { + // Specify a name for the automatically created VNIC datalink. + Linkname string `json:"linkname,omitempty"` + // Specify the link over which the VNIC will be created. + Lowerlink string `json:"lowerLink,omitempty"` + // The set of IP addresses that the container can use. + Allowedaddr string `json:"allowedAddress,omitempty"` + // Specifies whether allowedAddress limitation is to be applied to the VNIC. + Configallowedaddr string `json:"configureAllowedAddress,omitempty"` + // The value of the optional default router. + Defrouter string `json:"defrouter,omitempty"` + // Enable one or more types of link protection. + Linkprotection string `json:"linkProtection,omitempty"` + // Set the VNIC's macAddress + Macaddress string `json:"macAddress,omitempty"` +} + +// Arch used for additional architectures +type Arch string + +// Additional architectures permitted to be used for system calls +// By default only the native architecture of the kernel is permitted +const ( + ArchX86 Arch = "SCMP_ARCH_X86" + ArchX86_64 Arch = "SCMP_ARCH_X86_64" + ArchX32 Arch = "SCMP_ARCH_X32" + ArchARM Arch = "SCMP_ARCH_ARM" + ArchAARCH64 Arch = "SCMP_ARCH_AARCH64" + ArchMIPS Arch = "SCMP_ARCH_MIPS" + ArchMIPS64 Arch = "SCMP_ARCH_MIPS64" + ArchMIPS64N32 Arch = "SCMP_ARCH_MIPS64N32" + ArchMIPSEL Arch = "SCMP_ARCH_MIPSEL" + ArchMIPSEL64 Arch = "SCMP_ARCH_MIPSEL64" + ArchMIPSEL64N32 Arch = "SCMP_ARCH_MIPSEL64N32" + ArchPPC Arch = "SCMP_ARCH_PPC" + ArchPPC64 Arch = "SCMP_ARCH_PPC64" + ArchPPC64LE Arch = "SCMP_ARCH_PPC64LE" + ArchS390 Arch = "SCMP_ARCH_S390" + ArchS390X Arch = "SCMP_ARCH_S390X" +) + +// Action taken upon Seccomp rule match +type Action string + +// Define actions for Seccomp rules +const ( + ActKill Action = "SCMP_ACT_KILL" + ActTrap Action = "SCMP_ACT_TRAP" + ActErrno Action = "SCMP_ACT_ERRNO" + ActTrace Action = "SCMP_ACT_TRACE" + ActAllow Action = "SCMP_ACT_ALLOW" +) + +// Operator used to match syscall arguments in Seccomp +type Operator string + +// Define operators for syscall arguments in Seccomp +const ( + OpNotEqual Operator = "SCMP_CMP_NE" + OpLessThan Operator = "SCMP_CMP_LT" + OpLessEqual Operator = "SCMP_CMP_LE" + OpEqualTo Operator = "SCMP_CMP_EQ" + OpGreaterEqual Operator = "SCMP_CMP_GE" + OpGreaterThan Operator = "SCMP_CMP_GT" + OpMaskedEqual Operator = "SCMP_CMP_MASKED_EQ" +) + +// Arg used for matching specific syscall arguments in Seccomp +type Arg struct { + Index uint `json:"index"` + Value uint64 `json:"value"` + ValueTwo uint64 `json:"valueTwo"` + Op Operator `json:"op"` +} + +// Syscall is used to match a syscall in Seccomp +type Syscall struct { + Name string `json:"name"` + Action Action `json:"action"` + Args []Arg `json:"args,omitempty"` +} diff --git a/vendor/github.com/opencontainers/runtime-spec/specs-go/state.go b/vendor/github.com/opencontainers/runtime-spec/specs-go/state.go new file mode 100644 index 0000000..445f8c5 --- /dev/null +++ b/vendor/github.com/opencontainers/runtime-spec/specs-go/state.go @@ -0,0 +1,17 @@ +package specs + +// State holds information about the runtime state of the container. +type State struct { + // Version is the version of the specification that is supported. + Version string `json:"version"` + // ID is the container ID + ID string `json:"id"` + // Status is the runtime state of the container. + Status string `json:"status"` + // Pid is the process id for the container's main process. + Pid int `json:"pid"` + // BundlePath is the path to the container's bundle directory. + BundlePath string `json:"bundlePath"` + // Annotations are the annotations associated with the container. + Annotations map[string]string `json:"annotations"` +} diff --git a/vendor/github.com/opencontainers/runtime-spec/specs-go/version.go b/vendor/github.com/opencontainers/runtime-spec/specs-go/version.go new file mode 100644 index 0000000..2dbd302 --- /dev/null +++ b/vendor/github.com/opencontainers/runtime-spec/specs-go/version.go @@ -0,0 +1,18 @@ +package specs + +import "fmt" + +const ( + // VersionMajor is for an API incompatible changes + VersionMajor = 1 + // VersionMinor is for functionality in a backwards-compatible manner + VersionMinor = 0 + // VersionPatch is for backwards-compatible bug fixes + VersionPatch = 0 + + // VersionDev indicates development branch. Releases will be empty string. + VersionDev = "-rc1" +) + +// Version is the specification version that the package types support. +var Version = fmt.Sprintf("%d.%d.%d%s", VersionMajor, VersionMinor, VersionPatch, VersionDev) diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE new file mode 100644 index 0000000..835ba3e --- /dev/null +++ b/vendor/github.com/pkg/errors/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* 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/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go new file mode 100644 index 0000000..65bf7a0 --- /dev/null +++ b/vendor/github.com/pkg/errors/errors.go @@ -0,0 +1,211 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type Causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported +// +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// 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 { +// StackTrace() errors.StackTrace +// } +// +// Where errors.StackTrace is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stacktrace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stacktrace of this error. For example: +// +// if err, ok := err.(StackTrace); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } +// +// See the documentation for Frame.Format for more details. +package errors + +import ( + "fmt" + "io" +) + +// _error is an error implementation returned by New and Errorf +// that implements its own fmt.Formatter. +type _error struct { + msg string + *stack +} + +func (e _error) Error() string { return e.msg } + +func (e _error) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, e.msg) + fmt.Fprintf(s, "%+v", e.StackTrace()) + return + } + fallthrough + case 's': + io.WriteString(s, e.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 + *stack +} + +func (w wrapper) 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) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + } +} + +// Wrap returns an error annotating err with message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + return wrapper{ + cause: cause{ + cause: err, + msg: message, + }, + stack: callers(), + } +} + +// Wrapf returns an error annotating err with the format specifier. +// If err is nil, Wrapf returns nil. +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(), + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type Causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go new file mode 100644 index 0000000..243a64a --- /dev/null +++ b/vendor/github.com/pkg/errors/stack.go @@ -0,0 +1,165 @@ +package errors + +import ( + "fmt" + "io" + "path" + "runtime" + "strings" +) + +// Frame represents a program counter inside a stack frame. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s path of source file relative to the compile time GOPATH +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + pc := f.pc() + fn := runtime.FuncForPC(pc) + if fn == nil { + io.WriteString(s, "unknown") + } else { + file, _ := fn.FileLine(pc) + fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + } + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + fmt.Fprintf(s, "%d", f.line()) + case 'n': + name := runtime.FuncForPC(f.pc()).Name() + io.WriteString(s, funcname(name)) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + fmt.Fprintf(s, "\n%+v", f) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + fmt.Fprintf(s, "%v", []Frame(st)) + } + case 's': + fmt.Fprintf(s, "%s", []Frame(st)) + } +} + +// stack represents a stack of program counters. +type stack []uintptr + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} + +func trimGOPATH(name, file string) string { + // Here we want to get the source file path relative to the compile time + // GOPATH. As of Go 1.6.x there is no direct way to know the compiled + // GOPATH at runtime, but we can infer the number of path segments in the + // GOPATH. We note that fn.Name() returns the function name qualified by + // the import path, which does not include the GOPATH. Thus we can trim + // segments from the beginning of the file path until the number of path + // separators remaining is one more than the number of path separators in + // the function name. For example, given: + // + // GOPATH /home/user + // file /home/user/src/pkg/sub/file.go + // fn.Name() pkg/sub.Type.Method + // + // We want to produce: + // + // pkg/sub/file.go + // + // From this we can easily see that fn.Name() has one less path separator + // than our desired output. We count separators from the end of the file + // path until it finds two more than in the function name and then move + // one character forward to preserve the initial path segment without a + // leading separator. + const sep = "/" + goal := strings.Count(name, sep) + 2 + i := len(file) + for n := 0; n < goal; n++ { + i = strings.LastIndex(file[:i], sep) + if i == -1 { + // not enough separators found, set i so that the slice expression + // below leaves file unmodified + i = -len(sep) + break + } + } + // get back to 0 or trim the leading separator + file = file[i+len(sep):] + return file +} diff --git a/vendor/github.com/spf13/cobra/LICENSE.txt b/vendor/github.com/spf13/cobra/LICENSE.txt new file mode 100644 index 0000000..298f0e2 --- /dev/null +++ b/vendor/github.com/spf13/cobra/LICENSE.txt @@ -0,0 +1,174 @@ + 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. diff --git a/vendor/github.com/spf13/cobra/bash_completions.go b/vendor/github.com/spf13/cobra/bash_completions.go new file mode 100644 index 0000000..236dee6 --- /dev/null +++ b/vendor/github.com/spf13/cobra/bash_completions.go @@ -0,0 +1,630 @@ +package cobra + +import ( + "fmt" + "io" + "os" + "sort" + "strings" + + "github.com/spf13/pflag" +) + +const ( + BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extentions" + BashCompCustom = "cobra_annotation_bash_completion_custom" + BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag" + BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir" +) + +func preamble(out io.Writer, name string) error { + _, err := fmt.Fprintf(out, "# bash completion for %-36s -*- shell-script -*-\n", name) + if err != nil { + return err + } + _, err = fmt.Fprint(out, ` +__debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi +} + +# Homebrew on Macs have version 1.3 of bash-completion which doesn't include +# _init_completion. This is a very minimal version of that function. +__my_init_completion() +{ + COMPREPLY=() + _get_comp_words_by_ref "$@" cur prev words cword +} + +__index_of_word() +{ + local w word=$1 + shift + index=0 + for w in "$@"; do + [[ $w = "$word" ]] && return + index=$((index+1)) + done + index=-1 +} + +__contains_word() +{ + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done + return 1 +} + +__handle_reply() +{ + __debug "${FUNCNAME[0]}" + case $cur in + -*) + if [[ $(type -t compopt) = "builtin" ]]; then + compopt -o nospace + fi + local allflags + if [ ${#must_have_one_flag[@]} -ne 0 ]; then + allflags=("${must_have_one_flag[@]}") + else + allflags=("${flags[*]} ${two_word_flags[*]}") + fi + COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") ) + if [[ $(type -t compopt) = "builtin" ]]; then + [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace + fi + + # complete after --flag=abc + if [[ $cur == *=* ]]; then + if [[ $(type -t compopt) = "builtin" ]]; then + compopt +o nospace + fi + + local index flag + flag="${cur%%=*}" + __index_of_word "${flag}" "${flags_with_completion[@]}" + if [[ ${index} -ge 0 ]]; then + COMPREPLY=() + PREFIX="" + cur="${cur#*=}" + ${flags_completion[${index}]} + if [ -n "${ZSH_VERSION}" ]; then + # zfs completion needs --flag= prefix + eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" + fi + fi + fi + return 0; + ;; + esac + + # check if we are handling a flag with special work handling + local index + __index_of_word "${prev}" "${flags_with_completion[@]}" + if [[ ${index} -ge 0 ]]; then + ${flags_completion[${index}]} + return + fi + + # we are parsing a flag and don't have a special handler, no completion + if [[ ${cur} != "${words[cword]}" ]]; then + return + fi + + local completions + completions=("${commands[@]}") + if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then + completions=("${must_have_one_noun[@]}") + fi + if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then + completions+=("${must_have_one_flag[@]}") + fi + COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) + + if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then + COMPREPLY=( $(compgen -W "${noun_aliases[*]}" -- "$cur") ) + fi + + if [[ ${#COMPREPLY[@]} -eq 0 ]]; then + declare -F __custom_func >/dev/null && __custom_func + fi + + __ltrim_colon_completions "$cur" +} + +# The arguments should be in the form "ext1|ext2|extn" +__handle_filename_extension_flag() +{ + local ext="$1" + _filedir "@(${ext})" +} + +__handle_subdirs_in_dir_flag() +{ + local dir="$1" + pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 +} + +__handle_flag() +{ + __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + # if a command required a flag, and we found it, unset must_have_one_flag() + local flagname=${words[c]} + local flagvalue + # if the word contained an = + if [[ ${words[c]} == *"="* ]]; then + flagvalue=${flagname#*=} # take in as flagvalue after the = + flagname=${flagname%%=*} # strip everything after the = + flagname="${flagname}=" # but put the = back + fi + __debug "${FUNCNAME[0]}: looking for ${flagname}" + if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then + must_have_one_flag=() + fi + + # if you set a flag which only applies to this command, don't show subcommands + if __contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then + commands=() + fi + + # keep flag value with flagname as flaghash + if [ -n "${flagvalue}" ] ; then + flaghash[${flagname}]=${flagvalue} + elif [ -n "${words[ $((c+1)) ]}" ] ; then + flaghash[${flagname}]=${words[ $((c+1)) ]} + else + flaghash[${flagname}]="true" # pad "true" for bool flag + fi + + # skip the argument to a two word flag + if __contains_word "${words[c]}" "${two_word_flags[@]}"; then + c=$((c+1)) + # if we are looking for a flags value, don't show commands + if [[ $c -eq $cword ]]; then + commands=() + fi + fi + + c=$((c+1)) + +} + +__handle_noun() +{ + __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then + must_have_one_noun=() + elif __contains_word "${words[c]}" "${noun_aliases[@]}"; then + must_have_one_noun=() + fi + + nouns+=("${words[c]}") + c=$((c+1)) +} + +__handle_command() +{ + __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + local next_command + if [[ -n ${last_command} ]]; then + next_command="_${last_command}_${words[c]//:/__}" + else + if [[ $c -eq 0 ]]; then + next_command="_$(basename "${words[c]//:/__}")" + else + next_command="_${words[c]//:/__}" + fi + fi + c=$((c+1)) + __debug "${FUNCNAME[0]}: looking for ${next_command}" + declare -F $next_command >/dev/null && $next_command +} + +__handle_word() +{ + if [[ $c -ge $cword ]]; then + __handle_reply + return + fi + __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + if [[ "${words[c]}" == -* ]]; then + __handle_flag + elif __contains_word "${words[c]}" "${commands[@]}"; then + __handle_command + elif [[ $c -eq 0 ]] && __contains_word "$(basename "${words[c]}")" "${commands[@]}"; then + __handle_command + else + __handle_noun + fi + __handle_word +} + +`) + return err +} + +func postscript(w io.Writer, name string) error { + name = strings.Replace(name, ":", "__", -1) + _, err := fmt.Fprintf(w, "__start_%s()\n", name) + if err != nil { + return err + } + _, err = fmt.Fprintf(w, `{ + local cur prev words cword + declare -A flaghash 2>/dev/null || : + if declare -F _init_completion >/dev/null 2>&1; then + _init_completion -s || return + else + __my_init_completion -n "=" || return + fi + + local c=0 + local flags=() + local two_word_flags=() + local local_nonpersistent_flags=() + local flags_with_completion=() + local flags_completion=() + local commands=("%s") + local must_have_one_flag=() + local must_have_one_noun=() + local last_command + local nouns=() + + __handle_word +} + +`, name) + if err != nil { + return err + } + _, err = fmt.Fprintf(w, `if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_%s %s +else + complete -o default -o nospace -F __start_%s %s +fi + +`, name, name, name, name) + if err != nil { + return err + } + _, err = fmt.Fprintf(w, "# ex: ts=4 sw=4 et filetype=sh\n") + return err +} + +func writeCommands(cmd *Command, w io.Writer) error { + if _, err := fmt.Fprintf(w, " commands=()\n"); err != nil { + return err + } + for _, c := range cmd.Commands() { + if !c.IsAvailableCommand() || c == cmd.helpCommand { + continue + } + if _, err := fmt.Fprintf(w, " commands+=(%q)\n", c.Name()); err != nil { + return err + } + } + _, err := fmt.Fprintf(w, "\n") + return err +} + +func writeFlagHandler(name string, annotations map[string][]string, w io.Writer) error { + for key, value := range annotations { + switch key { + case BashCompFilenameExt: + _, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name) + if err != nil { + return err + } + + if len(value) > 0 { + ext := "__handle_filename_extension_flag " + strings.Join(value, "|") + _, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext) + } else { + ext := "_filedir" + _, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext) + } + if err != nil { + return err + } + case BashCompCustom: + _, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name) + if err != nil { + return err + } + if len(value) > 0 { + handlers := strings.Join(value, "; ") + _, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", handlers) + } else { + _, err = fmt.Fprintf(w, " flags_completion+=(:)\n") + } + if err != nil { + return err + } + case BashCompSubdirsInDir: + _, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name) + + if len(value) == 1 { + ext := "__handle_subdirs_in_dir_flag " + value[0] + _, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext) + } else { + ext := "_filedir -d" + _, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext) + } + if err != nil { + return err + } + } + } + return nil +} + +func writeShortFlag(flag *pflag.Flag, w io.Writer) error { + b := (len(flag.NoOptDefVal) > 0) + name := flag.Shorthand + format := " " + if !b { + format += "two_word_" + } + format += "flags+=(\"-%s\")\n" + if _, err := fmt.Fprintf(w, format, name); err != nil { + return err + } + return writeFlagHandler("-"+name, flag.Annotations, w) +} + +func writeFlag(flag *pflag.Flag, w io.Writer) error { + b := (len(flag.NoOptDefVal) > 0) + name := flag.Name + format := " flags+=(\"--%s" + if !b { + format += "=" + } + format += "\")\n" + if _, err := fmt.Fprintf(w, format, name); err != nil { + return err + } + return writeFlagHandler("--"+name, flag.Annotations, w) +} + +func writeLocalNonPersistentFlag(flag *pflag.Flag, w io.Writer) error { + b := (len(flag.NoOptDefVal) > 0) + name := flag.Name + format := " local_nonpersistent_flags+=(\"--%s" + if !b { + format += "=" + } + format += "\")\n" + if _, err := fmt.Fprintf(w, format, name); err != nil { + return err + } + return nil +} + +func writeFlags(cmd *Command, w io.Writer) error { + _, err := fmt.Fprintf(w, ` flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + +`) + if err != nil { + return err + } + localNonPersistentFlags := cmd.LocalNonPersistentFlags() + var visitErr error + cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { + if err := writeFlag(flag, w); err != nil { + visitErr = err + return + } + if len(flag.Shorthand) > 0 { + if err := writeShortFlag(flag, w); err != nil { + visitErr = err + return + } + } + if localNonPersistentFlags.Lookup(flag.Name) != nil { + if err := writeLocalNonPersistentFlag(flag, w); err != nil { + visitErr = err + return + } + } + }) + if visitErr != nil { + return visitErr + } + cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { + if err := writeFlag(flag, w); err != nil { + visitErr = err + return + } + if len(flag.Shorthand) > 0 { + if err := writeShortFlag(flag, w); err != nil { + visitErr = err + return + } + } + }) + if visitErr != nil { + return visitErr + } + + _, err = fmt.Fprintf(w, "\n") + return err +} + +func writeRequiredFlag(cmd *Command, w io.Writer) error { + if _, err := fmt.Fprintf(w, " must_have_one_flag=()\n"); err != nil { + return err + } + flags := cmd.NonInheritedFlags() + var visitErr error + flags.VisitAll(func(flag *pflag.Flag) { + for key := range flag.Annotations { + switch key { + case BashCompOneRequiredFlag: + format := " must_have_one_flag+=(\"--%s" + b := (flag.Value.Type() == "bool") + if !b { + format += "=" + } + format += "\")\n" + if _, err := fmt.Fprintf(w, format, flag.Name); err != nil { + visitErr = err + return + } + + if len(flag.Shorthand) > 0 { + if _, err := fmt.Fprintf(w, " must_have_one_flag+=(\"-%s\")\n", flag.Shorthand); err != nil { + visitErr = err + return + } + } + } + } + }) + return visitErr +} + +func writeRequiredNouns(cmd *Command, w io.Writer) error { + if _, err := fmt.Fprintf(w, " must_have_one_noun=()\n"); err != nil { + return err + } + sort.Sort(sort.StringSlice(cmd.ValidArgs)) + for _, value := range cmd.ValidArgs { + if _, err := fmt.Fprintf(w, " must_have_one_noun+=(%q)\n", value); err != nil { + return err + } + } + return nil +} + +func writeArgAliases(cmd *Command, w io.Writer) error { + if _, err := fmt.Fprintf(w, " noun_aliases=()\n"); err != nil { + return err + } + sort.Sort(sort.StringSlice(cmd.ArgAliases)) + for _, value := range cmd.ArgAliases { + if _, err := fmt.Fprintf(w, " noun_aliases+=(%q)\n", value); err != nil { + return err + } + } + return nil +} + +func gen(cmd *Command, w io.Writer) error { + for _, c := range cmd.Commands() { + if !c.IsAvailableCommand() || c == cmd.helpCommand { + continue + } + if err := gen(c, w); err != nil { + return err + } + } + commandName := cmd.CommandPath() + commandName = strings.Replace(commandName, " ", "_", -1) + commandName = strings.Replace(commandName, ":", "__", -1) + if _, err := fmt.Fprintf(w, "_%s()\n{\n", commandName); err != nil { + return err + } + if _, err := fmt.Fprintf(w, " last_command=%q\n", commandName); err != nil { + return err + } + if err := writeCommands(cmd, w); err != nil { + return err + } + if err := writeFlags(cmd, w); err != nil { + return err + } + if err := writeRequiredFlag(cmd, w); err != nil { + return err + } + if err := writeRequiredNouns(cmd, w); err != nil { + return err + } + if err := writeArgAliases(cmd, w); err != nil { + return err + } + if _, err := fmt.Fprintf(w, "}\n\n"); err != nil { + return err + } + return nil +} + +func (cmd *Command) GenBashCompletion(w io.Writer) error { + if err := preamble(w, cmd.Name()); err != nil { + return err + } + if len(cmd.BashCompletionFunction) > 0 { + if _, err := fmt.Fprintf(w, "%s\n", cmd.BashCompletionFunction); err != nil { + return err + } + } + if err := gen(cmd, w); err != nil { + return err + } + return postscript(w, cmd.Name()) +} + +func (cmd *Command) GenBashCompletionFile(filename string) error { + outFile, err := os.Create(filename) + if err != nil { + return err + } + defer outFile.Close() + + return cmd.GenBashCompletion(outFile) +} + +// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag, if it exists. +func (cmd *Command) MarkFlagRequired(name string) error { + return MarkFlagRequired(cmd.Flags(), name) +} + +// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag, if it exists. +func (cmd *Command) MarkPersistentFlagRequired(name string) error { + return MarkFlagRequired(cmd.PersistentFlags(), name) +} + +// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag in the flag set, if it exists. +func MarkFlagRequired(flags *pflag.FlagSet, name string) error { + return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"}) +} + +// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists. +// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. +func (cmd *Command) MarkFlagFilename(name string, extensions ...string) error { + return MarkFlagFilename(cmd.Flags(), name, extensions...) +} + +// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists. +// Generated bash autocompletion will call the bash function f for the flag. +func (cmd *Command) MarkFlagCustom(name string, f string) error { + return MarkFlagCustom(cmd.Flags(), name, f) +} + +// MarkPersistentFlagFilename adds the BashCompFilenameExt annotation to the named persistent flag, if it exists. +// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. +func (cmd *Command) MarkPersistentFlagFilename(name string, extensions ...string) error { + return MarkFlagFilename(cmd.PersistentFlags(), name, extensions...) +} + +// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag in the flag set, if it exists. +// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. +func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error { + return flags.SetAnnotation(name, BashCompFilenameExt, extensions) +} + +// MarkFlagCustom adds the BashCompCustom annotation to the named flag in the flag set, if it exists. +// Generated bash autocompletion will call the bash function f for the flag. +func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error { + return flags.SetAnnotation(name, BashCompCustom, []string{f}) +} diff --git a/vendor/github.com/spf13/cobra/cobra.go b/vendor/github.com/spf13/cobra/cobra.go new file mode 100644 index 0000000..93a2c0f --- /dev/null +++ b/vendor/github.com/spf13/cobra/cobra.go @@ -0,0 +1,175 @@ +// Copyright © 2013 Steve Francia . +// +// 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. + +// Commands similar to git, go tools and other modern CLI tools +// inspired by go, go-Commander, gh and subcommand + +package cobra + +import ( + "fmt" + "io" + "reflect" + "strconv" + "strings" + "text/template" + "unicode" +) + +var templateFuncs = template.FuncMap{ + "trim": strings.TrimSpace, + "trimRightSpace": trimRightSpace, + "appendIfNotPresent": appendIfNotPresent, + "rpad": rpad, + "gt": Gt, + "eq": Eq, +} + +var initializers []func() + +// automatic prefix matching can be a dangerous thing to automatically enable in CLI tools. +// Set this to true to enable it +var EnablePrefixMatching = false + +//EnableCommandSorting controls sorting of the slice of commands, which is turned on by default. +//To disable sorting, set it to false. +var EnableCommandSorting = true + +//AddTemplateFunc adds a template function that's available to Usage and Help +//template generation. +func AddTemplateFunc(name string, tmplFunc interface{}) { + templateFuncs[name] = tmplFunc +} + +//AddTemplateFuncs adds multiple template functions availalble to Usage and +//Help template generation. +func AddTemplateFuncs(tmplFuncs template.FuncMap) { + for k, v := range tmplFuncs { + templateFuncs[k] = v + } +} + +//OnInitialize takes a series of func() arguments and appends them to a slice of func(). +func OnInitialize(y ...func()) { + for _, x := range y { + initializers = append(initializers, x) + } +} + +//Gt takes two types and checks whether the first type is greater than the second. In case of types Arrays, Chans, +//Maps and Slices, Gt will compare their lengths. Ints are compared directly while strings are first parsed as +//ints and then compared. +func Gt(a interface{}, b interface{}) bool { + var left, right int64 + av := reflect.ValueOf(a) + + switch av.Kind() { + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + left = int64(av.Len()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + left = av.Int() + case reflect.String: + left, _ = strconv.ParseInt(av.String(), 10, 64) + } + + bv := reflect.ValueOf(b) + + switch bv.Kind() { + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + right = int64(bv.Len()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + right = bv.Int() + case reflect.String: + right, _ = strconv.ParseInt(bv.String(), 10, 64) + } + + return left > right +} + +//Eq takes two types and checks whether they are equal. Supported types are int and string. Unsupported types will panic. +func Eq(a interface{}, b interface{}) bool { + av := reflect.ValueOf(a) + bv := reflect.ValueOf(b) + + switch av.Kind() { + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + panic("Eq called on unsupported type") + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return av.Int() == bv.Int() + case reflect.String: + return av.String() == bv.String() + } + return false +} + +func trimRightSpace(s string) string { + return strings.TrimRightFunc(s, unicode.IsSpace) +} + +// appendIfNotPresent will append stringToAppend to the end of s, but only if it's not yet present in s +func appendIfNotPresent(s, stringToAppend string) string { + if strings.Contains(s, stringToAppend) { + return s + } + return s + " " + stringToAppend +} + +//rpad adds padding to the right of a string +func rpad(s string, padding int) string { + template := fmt.Sprintf("%%-%ds", padding) + return fmt.Sprintf(template, s) +} + +// tmpl executes the given template text on data, writing the result to w. +func tmpl(w io.Writer, text string, data interface{}) error { + t := template.New("top") + t.Funcs(templateFuncs) + template.Must(t.Parse(text)) + return t.Execute(w, data) +} + +// ld compares two strings and returns the levenshtein distance between them +func ld(s, t string, ignoreCase bool) int { + if ignoreCase { + s = strings.ToLower(s) + t = strings.ToLower(t) + } + d := make([][]int, len(s)+1) + for i := range d { + d[i] = make([]int, len(t)+1) + } + for i := range d { + d[i][0] = i + } + for j := range d[0] { + d[0][j] = j + } + for j := 1; j <= len(t); j++ { + for i := 1; i <= len(s); i++ { + if s[i-1] == t[j-1] { + d[i][j] = d[i-1][j-1] + } else { + min := d[i-1][j] + if d[i][j-1] < min { + min = d[i][j-1] + } + if d[i-1][j-1] < min { + min = d[i-1][j-1] + } + d[i][j] = min + 1 + } + } + + } + return d[len(s)][len(t)] +} diff --git a/vendor/github.com/spf13/cobra/command.go b/vendor/github.com/spf13/cobra/command.go new file mode 100644 index 0000000..a8b0fa5 --- /dev/null +++ b/vendor/github.com/spf13/cobra/command.go @@ -0,0 +1,1256 @@ +// Copyright © 2013 Steve Francia . +// +// 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 cobra is a commander providing a simple interface to create powerful modern CLI interfaces. +//In addition to providing an interface, Cobra simultaneously provides a controller to organize your application code. +package cobra + +import ( + "bytes" + "fmt" + "io" + "os" + "path/filepath" + "sort" + "strings" + + flag "github.com/spf13/pflag" +) + +// Command is just that, a command for your application. +// eg. 'go run' ... 'run' is the command. Cobra requires +// you to define the usage and description as part of your command +// definition to ensure usability. +type Command struct { + // Name is the command name, usually the executable's name. + name string + // The one-line usage message. + Use string + // An array of aliases that can be used instead of the first word in Use. + Aliases []string + // An array of command names for which this command will be suggested - similar to aliases but only suggests. + SuggestFor []string + // The short description shown in the 'help' output. + Short string + // The long message shown in the 'help ' output. + Long string + // Examples of how to use the command + Example string + // List of all valid non-flag arguments that are accepted in bash completions + ValidArgs []string + // List of aliases for ValidArgs. These are not suggested to the user in the bash + // completion, but accepted if entered manually. + ArgAliases []string + // Custom functions used by the bash autocompletion generator + BashCompletionFunction string + // Is this command deprecated and should print this string when used? + Deprecated string + // Is this command hidden and should NOT show up in the list of available commands? + Hidden bool + // Full set of flags + flags *flag.FlagSet + // Set of flags childrens of this command will inherit + pflags *flag.FlagSet + // Flags that are declared specifically by this command (not inherited). + lflags *flag.FlagSet + // SilenceErrors is an option to quiet errors down stream + SilenceErrors bool + // Silence Usage is an option to silence usage when an error occurs. + SilenceUsage bool + // The *Run functions are executed in the following order: + // * PersistentPreRun() + // * PreRun() + // * Run() + // * PostRun() + // * PersistentPostRun() + // All functions get the same args, the arguments after the command name + // PersistentPreRun: children of this command will inherit and execute + PersistentPreRun func(cmd *Command, args []string) + // PersistentPreRunE: PersistentPreRun but returns an error + PersistentPreRunE func(cmd *Command, args []string) error + // PreRun: children of this command will not inherit. + PreRun func(cmd *Command, args []string) + // PreRunE: PreRun but returns an error + PreRunE func(cmd *Command, args []string) error + // Run: Typically the actual work function. Most commands will only implement this + Run func(cmd *Command, args []string) + // RunE: Run but returns an error + RunE func(cmd *Command, args []string) error + // PostRun: run after the Run command. + PostRun func(cmd *Command, args []string) + // PostRunE: PostRun but returns an error + PostRunE func(cmd *Command, args []string) error + // PersistentPostRun: children of this command will inherit and execute after PostRun + PersistentPostRun func(cmd *Command, args []string) + // PersistentPostRunE: PersistentPostRun but returns an error + PersistentPostRunE func(cmd *Command, args []string) error + // DisableAutoGenTag remove + DisableAutoGenTag bool + // Commands is the list of commands supported by this program. + commands []*Command + // Parent Command for this command + parent *Command + // max lengths of commands' string lengths for use in padding + commandsMaxUseLen int + commandsMaxCommandPathLen int + commandsMaxNameLen int + // is commands slice are sorted or not + commandsAreSorted bool + + flagErrorBuf *bytes.Buffer + + args []string // actual args parsed from flags + output *io.Writer // nil means stderr; use Out() method instead + usageFunc func(*Command) error // Usage can be defined by application + usageTemplate string // Can be defined by Application + helpTemplate string // Can be defined by Application + helpFunc func(*Command, []string) // Help can be defined by application + helpCommand *Command // The help command + // The global normalization function that we can use on every pFlag set and children commands + globNormFunc func(f *flag.FlagSet, name string) flag.NormalizedName + + // Disable the suggestions based on Levenshtein distance that go along with 'unknown command' messages + DisableSuggestions bool + // If displaying suggestions, allows to set the minimum levenshtein distance to display, must be > 0 + SuggestionsMinimumDistance int + + // Disable the flag parsing. If this is true all flags will be passed to the command as arguments. + DisableFlagParsing bool +} + +// os.Args[1:] by default, if desired, can be overridden +// particularly useful when testing. +func (c *Command) SetArgs(a []string) { + c.args = a +} + +func (c *Command) getOut(def io.Writer) io.Writer { + if c.output != nil { + return *c.output + } + + if c.HasParent() { + return c.parent.Out() + } + return def +} + +func (c *Command) Out() io.Writer { + return c.getOut(os.Stderr) +} + +func (c *Command) getOutOrStdout() io.Writer { + return c.getOut(os.Stdout) +} + +// SetOutput sets the destination for usage and error messages. +// If output is nil, os.Stderr is used. +func (c *Command) SetOutput(output io.Writer) { + c.output = &output +} + +// Usage can be defined by application +func (c *Command) SetUsageFunc(f func(*Command) error) { + c.usageFunc = f +} + +// Can be defined by Application +func (c *Command) SetUsageTemplate(s string) { + c.usageTemplate = s +} + +// Can be defined by Application +func (c *Command) SetHelpFunc(f func(*Command, []string)) { + c.helpFunc = f +} + +func (c *Command) SetHelpCommand(cmd *Command) { + c.helpCommand = cmd +} + +// Can be defined by Application +func (c *Command) SetHelpTemplate(s string) { + c.helpTemplate = s +} + +// SetGlobalNormalizationFunc sets a normalization function to all flag sets and also to child commands. +// The user should not have a cyclic dependency on commands. +func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string) flag.NormalizedName) { + c.Flags().SetNormalizeFunc(n) + c.PersistentFlags().SetNormalizeFunc(n) + c.globNormFunc = n + + for _, command := range c.commands { + command.SetGlobalNormalizationFunc(n) + } +} + +func (c *Command) UsageFunc() (f func(*Command) error) { + if c.usageFunc != nil { + return c.usageFunc + } + + if c.HasParent() { + return c.parent.UsageFunc() + } + return func(c *Command) error { + err := tmpl(c.Out(), c.UsageTemplate(), c) + if err != nil { + fmt.Print(err) + } + return err + } +} + +// HelpFunc returns either the function set by SetHelpFunc for this command +// or a parent, or it returns a function which calls c.Help() +func (c *Command) HelpFunc() func(*Command, []string) { + cmd := c + for cmd != nil { + if cmd.helpFunc != nil { + return cmd.helpFunc + } + cmd = cmd.parent + } + return func(*Command, []string) { + err := c.Help() + if err != nil { + c.Println(err) + } + } +} + +var minUsagePadding = 25 + +func (c *Command) UsagePadding() int { + if c.parent == nil || minUsagePadding > c.parent.commandsMaxUseLen { + return minUsagePadding + } + return c.parent.commandsMaxUseLen +} + +var minCommandPathPadding = 11 + +// +func (c *Command) CommandPathPadding() int { + if c.parent == nil || minCommandPathPadding > c.parent.commandsMaxCommandPathLen { + return minCommandPathPadding + } + return c.parent.commandsMaxCommandPathLen +} + +var minNamePadding = 11 + +func (c *Command) NamePadding() int { + if c.parent == nil || minNamePadding > c.parent.commandsMaxNameLen { + return minNamePadding + } + return c.parent.commandsMaxNameLen +} + +func (c *Command) UsageTemplate() string { + if c.usageTemplate != "" { + return c.usageTemplate + } + + if c.HasParent() { + return c.parent.UsageTemplate() + } + return `Usage:{{if .Runnable}} + {{if .HasAvailableFlags}}{{appendIfNotPresent .UseLine "[flags]"}}{{else}}{{.UseLine}}{{end}}{{end}}{{if .HasAvailableSubCommands}} + {{ .CommandPath}} [command]{{end}}{{if gt .Aliases 0}} + +Aliases: + {{.NameAndAliases}} +{{end}}{{if .HasExample}} + +Examples: +{{ .Example }}{{end}}{{ if .HasAvailableSubCommands}} + +Available Commands:{{range .Commands}}{{if .IsAvailableCommand}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasAvailableLocalFlags}} + +Flags: +{{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasAvailableInheritedFlags}} + +Global Flags: +{{.InheritedFlags.FlagUsages | trimRightSpace}}{{end}}{{if .HasHelpSubCommands}} + +Additional help topics:{{range .Commands}}{{if .IsHelpCommand}} + {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasAvailableSubCommands }} + +Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} +` +} + +func (c *Command) HelpTemplate() string { + if c.helpTemplate != "" { + return c.helpTemplate + } + + if c.HasParent() { + return c.parent.HelpTemplate() + } + return `{{with or .Long .Short }}{{. | trim}} + +{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` +} + +// Really only used when casting a command to a commander +func (c *Command) resetChildrensParents() { + for _, x := range c.commands { + x.parent = c + } +} + +// Test if the named flag is a boolean flag. +func isBooleanFlag(name string, f *flag.FlagSet) bool { + flag := f.Lookup(name) + if flag == nil { + return false + } + return flag.Value.Type() == "bool" +} + +// Test if the named flag is a boolean flag. +func isBooleanShortFlag(name string, f *flag.FlagSet) bool { + result := false + f.VisitAll(func(f *flag.Flag) { + if f.Shorthand == name && f.Value.Type() == "bool" { + result = true + } + }) + return result +} + +func stripFlags(args []string, c *Command) []string { + if len(args) < 1 { + return args + } + c.mergePersistentFlags() + + commands := []string{} + + inQuote := false + inFlag := false + for _, y := range args { + if !inQuote { + switch { + case strings.HasPrefix(y, "\""): + inQuote = true + case strings.Contains(y, "=\""): + inQuote = true + case strings.HasPrefix(y, "--") && !strings.Contains(y, "="): + // TODO: this isn't quite right, we should really check ahead for 'true' or 'false' + inFlag = !isBooleanFlag(y[2:], c.Flags()) + case strings.HasPrefix(y, "-") && !strings.Contains(y, "=") && len(y) == 2 && !isBooleanShortFlag(y[1:], c.Flags()): + inFlag = true + case inFlag: + inFlag = false + case y == "": + // strip empty commands, as the go tests expect this to be ok.... + case !strings.HasPrefix(y, "-"): + commands = append(commands, y) + inFlag = false + } + } + + if strings.HasSuffix(y, "\"") && !strings.HasSuffix(y, "\\\"") { + inQuote = false + } + } + + return commands +} + +// argsMinusFirstX removes only the first x from args. Otherwise, commands that look like +// openshift admin policy add-role-to-user admin my-user, lose the admin argument (arg[4]). +func argsMinusFirstX(args []string, x string) []string { + for i, y := range args { + if x == y { + ret := []string{} + ret = append(ret, args[:i]...) + ret = append(ret, args[i+1:]...) + return ret + } + } + return args +} + +// find the target command given the args and command tree +// Meant to be run on the highest node. Only searches down. +func (c *Command) Find(args []string) (*Command, []string, error) { + if c == nil { + return nil, nil, fmt.Errorf("Called find() on a nil Command") + } + + var innerfind func(*Command, []string) (*Command, []string) + + innerfind = func(c *Command, innerArgs []string) (*Command, []string) { + argsWOflags := stripFlags(innerArgs, c) + if len(argsWOflags) == 0 { + return c, innerArgs + } + nextSubCmd := argsWOflags[0] + matches := make([]*Command, 0) + for _, cmd := range c.commands { + if cmd.Name() == nextSubCmd || cmd.HasAlias(nextSubCmd) { // exact name or alias match + return innerfind(cmd, argsMinusFirstX(innerArgs, nextSubCmd)) + } + if EnablePrefixMatching { + if strings.HasPrefix(cmd.Name(), nextSubCmd) { // prefix match + matches = append(matches, cmd) + } + for _, x := range cmd.Aliases { + if strings.HasPrefix(x, nextSubCmd) { + matches = append(matches, cmd) + } + } + } + } + + // only accept a single prefix match - multiple matches would be ambiguous + if len(matches) == 1 { + return innerfind(matches[0], argsMinusFirstX(innerArgs, argsWOflags[0])) + } + + return c, innerArgs + } + + commandFound, a := innerfind(c, args) + argsWOflags := stripFlags(a, commandFound) + + // no subcommand, always take args + if !commandFound.HasSubCommands() { + return commandFound, a, nil + } + + // root command with subcommands, do subcommand checking + if commandFound == c && len(argsWOflags) > 0 { + suggestionsString := "" + if !c.DisableSuggestions { + if c.SuggestionsMinimumDistance <= 0 { + c.SuggestionsMinimumDistance = 2 + } + if suggestions := c.SuggestionsFor(argsWOflags[0]); len(suggestions) > 0 { + suggestionsString += "\n\nDid you mean this?\n" + for _, s := range suggestions { + suggestionsString += fmt.Sprintf("\t%v\n", s) + } + } + } + return commandFound, a, fmt.Errorf("unknown command %q for %q%s", argsWOflags[0], commandFound.CommandPath(), suggestionsString) + } + + return commandFound, a, nil +} + +func (c *Command) SuggestionsFor(typedName string) []string { + suggestions := []string{} + for _, cmd := range c.commands { + if cmd.IsAvailableCommand() { + levenshteinDistance := ld(typedName, cmd.Name(), true) + suggestByLevenshtein := levenshteinDistance <= c.SuggestionsMinimumDistance + suggestByPrefix := strings.HasPrefix(strings.ToLower(cmd.Name()), strings.ToLower(typedName)) + if suggestByLevenshtein || suggestByPrefix { + suggestions = append(suggestions, cmd.Name()) + } + for _, explicitSuggestion := range cmd.SuggestFor { + if strings.EqualFold(typedName, explicitSuggestion) { + suggestions = append(suggestions, cmd.Name()) + } + } + } + } + return suggestions +} + +func (c *Command) VisitParents(fn func(*Command)) { + var traverse func(*Command) *Command + + traverse = func(x *Command) *Command { + if x != c { + fn(x) + } + if x.HasParent() { + return traverse(x.parent) + } + return x + } + traverse(c) +} + +func (c *Command) Root() *Command { + var findRoot func(*Command) *Command + + findRoot = func(x *Command) *Command { + if x.HasParent() { + return findRoot(x.parent) + } + return x + } + + return findRoot(c) +} + +// ArgsLenAtDash will return the length of f.Args at the moment when a -- was +// found during arg parsing. This allows your program to know which args were +// before the -- and which came after. (Description from +// https://godoc.org/github.com/spf13/pflag#FlagSet.ArgsLenAtDash). +func (c *Command) ArgsLenAtDash() int { + return c.Flags().ArgsLenAtDash() +} + +func (c *Command) execute(a []string) (err error) { + if c == nil { + return fmt.Errorf("Called Execute() on a nil Command") + } + + if len(c.Deprecated) > 0 { + c.Printf("Command %q is deprecated, %s\n", c.Name(), c.Deprecated) + } + + // initialize help flag as the last point possible to allow for user + // overriding + c.initHelpFlag() + + err = c.ParseFlags(a) + if err != nil { + return err + } + // If help is called, regardless of other flags, return we want help + // Also say we need help if the command isn't runnable. + helpVal, err := c.Flags().GetBool("help") + if err != nil { + // should be impossible to get here as we always declare a help + // flag in initHelpFlag() + c.Println("\"help\" flag declared as non-bool. Please correct your code") + return err + } + if helpVal || !c.Runnable() { + return flag.ErrHelp + } + + c.preRun() + + argWoFlags := c.Flags().Args() + if c.DisableFlagParsing { + argWoFlags = a + } + + for p := c; p != nil; p = p.Parent() { + if p.PersistentPreRunE != nil { + if err := p.PersistentPreRunE(c, argWoFlags); err != nil { + return err + } + break + } else if p.PersistentPreRun != nil { + p.PersistentPreRun(c, argWoFlags) + break + } + } + if c.PreRunE != nil { + if err := c.PreRunE(c, argWoFlags); err != nil { + return err + } + } else if c.PreRun != nil { + c.PreRun(c, argWoFlags) + } + + if c.RunE != nil { + if err := c.RunE(c, argWoFlags); err != nil { + return err + } + } else { + c.Run(c, argWoFlags) + } + if c.PostRunE != nil { + if err := c.PostRunE(c, argWoFlags); err != nil { + return err + } + } else if c.PostRun != nil { + c.PostRun(c, argWoFlags) + } + for p := c; p != nil; p = p.Parent() { + if p.PersistentPostRunE != nil { + if err := p.PersistentPostRunE(c, argWoFlags); err != nil { + return err + } + break + } else if p.PersistentPostRun != nil { + p.PersistentPostRun(c, argWoFlags) + break + } + } + + return nil +} + +func (c *Command) preRun() { + for _, x := range initializers { + x() + } +} + +func (c *Command) errorMsgFromParse() string { + s := c.flagErrorBuf.String() + + x := strings.Split(s, "\n") + + if len(x) > 0 { + return x[0] + } + return "" +} + +// Call execute to use the args (os.Args[1:] by default) +// and run through the command tree finding appropriate matches +// for commands and then corresponding flags. +func (c *Command) Execute() error { + _, err := c.ExecuteC() + return err +} + +func (c *Command) ExecuteC() (cmd *Command, err error) { + + // Regardless of what command execute is called on, run on Root only + if c.HasParent() { + return c.Root().ExecuteC() + } + + // windows hook + if preExecHookFn != nil { + preExecHookFn(c) + } + + // initialize help as the last point possible to allow for user + // overriding + c.initHelpCmd() + + var args []string + + // Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155 + if c.args == nil && filepath.Base(os.Args[0]) != "cobra.test" { + args = os.Args[1:] + } else { + args = c.args + } + + cmd, flags, err := c.Find(args) + if err != nil { + // If found parse to a subcommand and then failed, talk about the subcommand + if cmd != nil { + c = cmd + } + if !c.SilenceErrors { + c.Println("Error:", err.Error()) + c.Printf("Run '%v --help' for usage.\n", c.CommandPath()) + } + return c, err + } + err = cmd.execute(flags) + if err != nil { + // Always show help if requested, even if SilenceErrors is in + // effect + if err == flag.ErrHelp { + cmd.HelpFunc()(cmd, args) + return cmd, nil + } + + // If root command has SilentErrors flagged, + // all subcommands should respect it + if !cmd.SilenceErrors && !c.SilenceErrors { + c.Println("Error:", err.Error()) + } + + // If root command has SilentUsage flagged, + // all subcommands should respect it + if !cmd.SilenceUsage && !c.SilenceUsage { + c.Println(cmd.UsageString()) + } + return cmd, err + } + return cmd, nil +} + +func (c *Command) initHelpFlag() { + if c.Flags().Lookup("help") == nil { + c.Flags().BoolP("help", "h", false, "help for "+c.Name()) + } +} + +func (c *Command) initHelpCmd() { + if c.helpCommand == nil { + if !c.HasSubCommands() { + return + } + + c.helpCommand = &Command{ + Use: "help [command]", + Short: "Help about any command", + Long: `Help provides help for any command in the application. + Simply type ` + c.Name() + ` help [path to command] for full details.`, + PersistentPreRun: func(cmd *Command, args []string) {}, + PersistentPostRun: func(cmd *Command, args []string) {}, + + Run: func(c *Command, args []string) { + cmd, _, e := c.Root().Find(args) + if cmd == nil || e != nil { + c.Printf("Unknown help topic %#q.", args) + c.Root().Usage() + } else { + helpFunc := cmd.HelpFunc() + helpFunc(cmd, args) + } + }, + } + } + c.AddCommand(c.helpCommand) +} + +// Used for testing +func (c *Command) ResetCommands() { + c.commands = nil + c.helpCommand = nil +} + +// Sorts commands by their names +type commandSorterByName []*Command + +func (c commandSorterByName) Len() int { return len(c) } +func (c commandSorterByName) Swap(i, j int) { c[i], c[j] = c[j], c[i] } +func (c commandSorterByName) Less(i, j int) bool { return c[i].Name() < c[j].Name() } + +// Commands returns a sorted slice of child commands. +func (c *Command) Commands() []*Command { + // do not sort commands if it already sorted or sorting was disabled + if EnableCommandSorting && !c.commandsAreSorted{ + sort.Sort(commandSorterByName(c.commands)) + c.commandsAreSorted = true + } + return c.commands +} + +// AddCommand adds one or more commands to this parent command. +func (c *Command) AddCommand(cmds ...*Command) { + for i, x := range cmds { + if cmds[i] == c { + panic("Command can't be a child of itself") + } + cmds[i].parent = c + // update max lengths + usageLen := len(x.Use) + if usageLen > c.commandsMaxUseLen { + c.commandsMaxUseLen = usageLen + } + commandPathLen := len(x.CommandPath()) + if commandPathLen > c.commandsMaxCommandPathLen { + c.commandsMaxCommandPathLen = commandPathLen + } + nameLen := len(x.Name()) + if nameLen > c.commandsMaxNameLen { + c.commandsMaxNameLen = nameLen + } + // If global normalization function exists, update all children + if c.globNormFunc != nil { + x.SetGlobalNormalizationFunc(c.globNormFunc) + } + c.commands = append(c.commands, x) + c.commandsAreSorted = false + } +} + +// RemoveCommand removes one or more commands from a parent command. +func (c *Command) RemoveCommand(cmds ...*Command) { + commands := []*Command{} +main: + for _, command := range c.commands { + for _, cmd := range cmds { + if command == cmd { + command.parent = nil + continue main + } + } + commands = append(commands, command) + } + c.commands = commands + // recompute all lengths + c.commandsMaxUseLen = 0 + c.commandsMaxCommandPathLen = 0 + c.commandsMaxNameLen = 0 + for _, command := range c.commands { + usageLen := len(command.Use) + if usageLen > c.commandsMaxUseLen { + c.commandsMaxUseLen = usageLen + } + commandPathLen := len(command.CommandPath()) + if commandPathLen > c.commandsMaxCommandPathLen { + c.commandsMaxCommandPathLen = commandPathLen + } + nameLen := len(command.Name()) + if nameLen > c.commandsMaxNameLen { + c.commandsMaxNameLen = nameLen + } + } +} + +// Print is a convenience method to Print to the defined output +func (c *Command) Print(i ...interface{}) { + fmt.Fprint(c.Out(), i...) +} + +// Println is a convenience method to Println to the defined output +func (c *Command) Println(i ...interface{}) { + str := fmt.Sprintln(i...) + c.Print(str) +} + +// Printf is a convenience method to Printf to the defined output +func (c *Command) Printf(format string, i ...interface{}) { + str := fmt.Sprintf(format, i...) + c.Print(str) +} + +// Output the usage for the command +// Used when a user provides invalid input +// Can be defined by user by overriding UsageFunc +func (c *Command) Usage() error { + c.mergePersistentFlags() + err := c.UsageFunc()(c) + return err +} + +// Output the help for the command +// Used when a user calls help [command] +// by the default HelpFunc in the commander +func (c *Command) Help() error { + c.mergePersistentFlags() + err := tmpl(c.getOutOrStdout(), c.HelpTemplate(), c) + return err +} + +func (c *Command) UsageString() string { + tmpOutput := c.output + bb := new(bytes.Buffer) + c.SetOutput(bb) + c.Usage() + c.output = tmpOutput + return bb.String() +} + +// CommandPath returns the full path to this command. +func (c *Command) CommandPath() string { + str := c.Name() + x := c + for x.HasParent() { + str = x.parent.Name() + " " + str + x = x.parent + } + return str +} + +//The full usage for a given command (including parents) +func (c *Command) UseLine() string { + str := "" + if c.HasParent() { + str = c.parent.CommandPath() + " " + } + return str + c.Use +} + +// For use in determining which flags have been assigned to which commands +// and which persist +func (c *Command) DebugFlags() { + c.Println("DebugFlags called on", c.Name()) + var debugflags func(*Command) + + debugflags = func(x *Command) { + if x.HasFlags() || x.HasPersistentFlags() { + c.Println(x.Name()) + } + if x.HasFlags() { + x.flags.VisitAll(func(f *flag.Flag) { + if x.HasPersistentFlags() { + if x.persistentFlag(f.Name) == nil { + c.Println(" -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, " [L]") + } else { + c.Println(" -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, " [LP]") + } + } else { + c.Println(" -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, " [L]") + } + }) + } + if x.HasPersistentFlags() { + x.pflags.VisitAll(func(f *flag.Flag) { + if x.HasFlags() { + if x.flags.Lookup(f.Name) == nil { + c.Println(" -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, " [P]") + } + } else { + c.Println(" -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, " [P]") + } + }) + } + c.Println(x.flagErrorBuf) + if x.HasSubCommands() { + for _, y := range x.commands { + debugflags(y) + } + } + } + + debugflags(c) +} + +// Name returns the command's name: the first word in the use line. +func (c *Command) Name() string { + if c.name != "" { + return c.name + } + name := c.Use + i := strings.Index(name, " ") + if i >= 0 { + name = name[:i] + } + return name +} + +// HasAlias determines if a given string is an alias of the command. +func (c *Command) HasAlias(s string) bool { + for _, a := range c.Aliases { + if a == s { + return true + } + } + return false +} + +func (c *Command) NameAndAliases() string { + return strings.Join(append([]string{c.Name()}, c.Aliases...), ", ") +} + +func (c *Command) HasExample() bool { + return len(c.Example) > 0 +} + +// Runnable determines if the command is itself runnable +func (c *Command) Runnable() bool { + return c.Run != nil || c.RunE != nil +} + +// HasSubCommands determines if the command has children commands +func (c *Command) HasSubCommands() bool { + return len(c.commands) > 0 +} + +// IsAvailableCommand determines if a command is available as a non-help command +// (this includes all non deprecated/hidden commands) +func (c *Command) IsAvailableCommand() bool { + if len(c.Deprecated) != 0 || c.Hidden { + return false + } + + if c.HasParent() && c.Parent().helpCommand == c { + return false + } + + if c.Runnable() || c.HasAvailableSubCommands() { + return true + } + + return false +} + +// IsHelpCommand determines if a command is a 'help' command; a help command is +// determined by the fact that it is NOT runnable/hidden/deprecated, and has no +// sub commands that are runnable/hidden/deprecated +func (c *Command) IsHelpCommand() bool { + + // if a command is runnable, deprecated, or hidden it is not a 'help' command + if c.Runnable() || len(c.Deprecated) != 0 || c.Hidden { + return false + } + + // if any non-help sub commands are found, the command is not a 'help' command + for _, sub := range c.commands { + if !sub.IsHelpCommand() { + return false + } + } + + // the command either has no sub commands, or no non-help sub commands + return true +} + +// HasHelpSubCommands determines if a command has any avilable 'help' sub commands +// that need to be shown in the usage/help default template under 'additional help +// topics' +func (c *Command) HasHelpSubCommands() bool { + + // return true on the first found available 'help' sub command + for _, sub := range c.commands { + if sub.IsHelpCommand() { + return true + } + } + + // the command either has no sub commands, or no available 'help' sub commands + return false +} + +// HasAvailableSubCommands determines if a command has available sub commands that +// need to be shown in the usage/help default template under 'available commands' +func (c *Command) HasAvailableSubCommands() bool { + + // return true on the first found available (non deprecated/help/hidden) + // sub command + for _, sub := range c.commands { + if sub.IsAvailableCommand() { + return true + } + } + + // the command either has no sub comamnds, or no available (non deprecated/help/hidden) + // sub commands + return false +} + +// Determine if the command is a child command +func (c *Command) HasParent() bool { + return c.parent != nil +} + +// GlobalNormalizationFunc returns the global normalization function or nil if doesn't exists +func (c *Command) GlobalNormalizationFunc() func(f *flag.FlagSet, name string) flag.NormalizedName { + return c.globNormFunc +} + +// Get the complete FlagSet that applies to this command (local and persistent declared here and by all parents) +func (c *Command) Flags() *flag.FlagSet { + if c.flags == nil { + c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) + if c.flagErrorBuf == nil { + c.flagErrorBuf = new(bytes.Buffer) + } + c.flags.SetOutput(c.flagErrorBuf) + } + return c.flags +} + +// LocalNonPersistentFlags are flags specific to this command which will NOT persist to subcommands +func (c *Command) LocalNonPersistentFlags() *flag.FlagSet { + persistentFlags := c.PersistentFlags() + + out := flag.NewFlagSet(c.Name(), flag.ContinueOnError) + c.LocalFlags().VisitAll(func(f *flag.Flag) { + if persistentFlags.Lookup(f.Name) == nil { + out.AddFlag(f) + } + }) + return out +} + +// Get the local FlagSet specifically set in the current command +func (c *Command) LocalFlags() *flag.FlagSet { + c.mergePersistentFlags() + + local := flag.NewFlagSet(c.Name(), flag.ContinueOnError) + c.lflags.VisitAll(func(f *flag.Flag) { + local.AddFlag(f) + }) + if !c.HasParent() { + flag.CommandLine.VisitAll(func(f *flag.Flag) { + if local.Lookup(f.Name) == nil { + local.AddFlag(f) + } + }) + } + return local +} + +// All Flags which were inherited from parents commands +func (c *Command) InheritedFlags() *flag.FlagSet { + c.mergePersistentFlags() + + inherited := flag.NewFlagSet(c.Name(), flag.ContinueOnError) + local := c.LocalFlags() + + var rmerge func(x *Command) + + rmerge = func(x *Command) { + if x.HasPersistentFlags() { + x.PersistentFlags().VisitAll(func(f *flag.Flag) { + if inherited.Lookup(f.Name) == nil && local.Lookup(f.Name) == nil { + inherited.AddFlag(f) + } + }) + } + if x.HasParent() { + rmerge(x.parent) + } + } + + if c.HasParent() { + rmerge(c.parent) + } + + return inherited +} + +// All Flags which were not inherited from parent commands +func (c *Command) NonInheritedFlags() *flag.FlagSet { + return c.LocalFlags() +} + +// Get the Persistent FlagSet specifically set in the current command +func (c *Command) PersistentFlags() *flag.FlagSet { + if c.pflags == nil { + c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) + if c.flagErrorBuf == nil { + c.flagErrorBuf = new(bytes.Buffer) + } + c.pflags.SetOutput(c.flagErrorBuf) + } + return c.pflags +} + +// For use in testing +func (c *Command) ResetFlags() { + c.flagErrorBuf = new(bytes.Buffer) + c.flagErrorBuf.Reset() + c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) + c.flags.SetOutput(c.flagErrorBuf) + c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) + c.pflags.SetOutput(c.flagErrorBuf) +} + +// Does the command contain any flags (local plus persistent from the entire structure) +func (c *Command) HasFlags() bool { + return c.Flags().HasFlags() +} + +// Does the command contain persistent flags +func (c *Command) HasPersistentFlags() bool { + return c.PersistentFlags().HasFlags() +} + +// Does the command has flags specifically declared locally +func (c *Command) HasLocalFlags() bool { + return c.LocalFlags().HasFlags() +} + +// Does the command have flags inherited from its parent command +func (c *Command) HasInheritedFlags() bool { + return c.InheritedFlags().HasFlags() +} + +// Does the command contain any flags (local plus persistent from the entire +// structure) which are not hidden or deprecated +func (c *Command) HasAvailableFlags() bool { + return c.Flags().HasAvailableFlags() +} + +// Does the command contain persistent flags which are not hidden or deprecated +func (c *Command) HasAvailablePersistentFlags() bool { + return c.PersistentFlags().HasAvailableFlags() +} + +// Does the command has flags specifically declared locally which are not hidden +// or deprecated +func (c *Command) HasAvailableLocalFlags() bool { + return c.LocalFlags().HasAvailableFlags() +} + +// Does the command have flags inherited from its parent command which are +// not hidden or deprecated +func (c *Command) HasAvailableInheritedFlags() bool { + return c.InheritedFlags().HasAvailableFlags() +} + +// Flag climbs up the command tree looking for matching flag +func (c *Command) Flag(name string) (flag *flag.Flag) { + flag = c.Flags().Lookup(name) + + if flag == nil { + flag = c.persistentFlag(name) + } + + return +} + +// recursively find matching persistent flag +func (c *Command) persistentFlag(name string) (flag *flag.Flag) { + if c.HasPersistentFlags() { + flag = c.PersistentFlags().Lookup(name) + } + + if flag == nil && c.HasParent() { + flag = c.parent.persistentFlag(name) + } + return +} + +// ParseFlags parses persistent flag tree & local flags +func (c *Command) ParseFlags(args []string) (err error) { + if c.DisableFlagParsing { + return nil + } + c.mergePersistentFlags() + err = c.Flags().Parse(args) + return +} + +// Parent returns a commands parent command +func (c *Command) Parent() *Command { + return c.parent +} + +func (c *Command) mergePersistentFlags() { + var rmerge func(x *Command) + + // Save the set of local flags + if c.lflags == nil { + c.lflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) + if c.flagErrorBuf == nil { + c.flagErrorBuf = new(bytes.Buffer) + } + c.lflags.SetOutput(c.flagErrorBuf) + addtolocal := func(f *flag.Flag) { + c.lflags.AddFlag(f) + } + c.Flags().VisitAll(addtolocal) + c.PersistentFlags().VisitAll(addtolocal) + } + rmerge = func(x *Command) { + if !x.HasParent() { + flag.CommandLine.VisitAll(func(f *flag.Flag) { + if x.PersistentFlags().Lookup(f.Name) == nil { + x.PersistentFlags().AddFlag(f) + } + }) + } + if x.HasPersistentFlags() { + x.PersistentFlags().VisitAll(func(f *flag.Flag) { + if c.Flags().Lookup(f.Name) == nil { + c.Flags().AddFlag(f) + } + }) + } + if x.HasParent() { + rmerge(x.parent) + } + } + + rmerge(c) +} diff --git a/vendor/github.com/spf13/cobra/command_notwin.go b/vendor/github.com/spf13/cobra/command_notwin.go new file mode 100644 index 0000000..6159c1c --- /dev/null +++ b/vendor/github.com/spf13/cobra/command_notwin.go @@ -0,0 +1,5 @@ +// +build !windows + +package cobra + +var preExecHookFn func(*Command) diff --git a/vendor/github.com/spf13/cobra/command_win.go b/vendor/github.com/spf13/cobra/command_win.go new file mode 100644 index 0000000..4b0eaa1 --- /dev/null +++ b/vendor/github.com/spf13/cobra/command_win.go @@ -0,0 +1,26 @@ +// +build windows + +package cobra + +import ( + "os" + "time" + + "github.com/inconshreveable/mousetrap" +) + +var preExecHookFn = preExecHook + +// enables an information splash screen on Windows if the CLI is started from explorer.exe. +var MousetrapHelpText string = `This is a command line tool + +You need to open cmd.exe and run it from there. +` + +func preExecHook(c *Command) { + if mousetrap.StartedByExplorer() { + c.Print(MousetrapHelpText) + time.Sleep(5 * time.Second) + os.Exit(1) + } +} diff --git a/vendor/github.com/spf13/pflag/LICENSE b/vendor/github.com/spf13/pflag/LICENSE new file mode 100644 index 0000000..63ed1cf --- /dev/null +++ b/vendor/github.com/spf13/pflag/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2012 Alex Ogier. All rights reserved. +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +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 +OWNER 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/spf13/pflag/bool.go b/vendor/github.com/spf13/pflag/bool.go new file mode 100644 index 0000000..d272e40 --- /dev/null +++ b/vendor/github.com/spf13/pflag/bool.go @@ -0,0 +1,97 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// optional interface to indicate boolean flags that can be +// supplied without "=value" text +type boolFlag interface { + Value + IsBoolFlag() bool +} + +// -- bool Value +type boolValue bool + +func newBoolValue(val bool, p *bool) *boolValue { + *p = val + return (*boolValue)(p) +} + +func (b *boolValue) Set(s string) error { + v, err := strconv.ParseBool(s) + *b = boolValue(v) + return err +} + +func (b *boolValue) Type() string { + return "bool" +} + +func (b *boolValue) String() string { return fmt.Sprintf("%v", *b) } + +func (b *boolValue) IsBoolFlag() bool { return true } + +func boolConv(sval string) (interface{}, error) { + return strconv.ParseBool(sval) +} + +// GetBool return the bool value of a flag with the given name +func (f *FlagSet) GetBool(name string) (bool, error) { + val, err := f.getFlagType(name, "bool", boolConv) + if err != nil { + return false, err + } + return val.(bool), nil +} + +// BoolVar defines a bool flag with specified name, default value, and usage string. +// The argument p points to a bool variable in which to store the value of the flag. +func (f *FlagSet) BoolVar(p *bool, name string, value bool, usage string) { + f.BoolVarP(p, name, "", value, usage) +} + +// BoolVarP is like BoolVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) BoolVarP(p *bool, name, shorthand string, value bool, usage string) { + flag := f.VarPF(newBoolValue(value, p), name, shorthand, usage) + flag.NoOptDefVal = "true" +} + +// BoolVar defines a bool flag with specified name, default value, and usage string. +// The argument p points to a bool variable in which to store the value of the flag. +func BoolVar(p *bool, name string, value bool, usage string) { + BoolVarP(p, name, "", value, usage) +} + +// BoolVarP is like BoolVar, but accepts a shorthand letter that can be used after a single dash. +func BoolVarP(p *bool, name, shorthand string, value bool, usage string) { + flag := CommandLine.VarPF(newBoolValue(value, p), name, shorthand, usage) + flag.NoOptDefVal = "true" +} + +// Bool defines a bool flag with specified name, default value, and usage string. +// The return value is the address of a bool variable that stores the value of the flag. +func (f *FlagSet) Bool(name string, value bool, usage string) *bool { + return f.BoolP(name, "", value, usage) +} + +// BoolP is like Bool, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) BoolP(name, shorthand string, value bool, usage string) *bool { + p := new(bool) + f.BoolVarP(p, name, shorthand, value, usage) + return p +} + +// Bool defines a bool flag with specified name, default value, and usage string. +// The return value is the address of a bool variable that stores the value of the flag. +func Bool(name string, value bool, usage string) *bool { + return BoolP(name, "", value, usage) +} + +// BoolP is like Bool, but accepts a shorthand letter that can be used after a single dash. +func BoolP(name, shorthand string, value bool, usage string) *bool { + b := CommandLine.BoolP(name, shorthand, value, usage) + return b +} diff --git a/vendor/github.com/spf13/pflag/count.go b/vendor/github.com/spf13/pflag/count.go new file mode 100644 index 0000000..7b1f142 --- /dev/null +++ b/vendor/github.com/spf13/pflag/count.go @@ -0,0 +1,97 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- count Value +type countValue int + +func newCountValue(val int, p *int) *countValue { + *p = val + return (*countValue)(p) +} + +func (i *countValue) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 64) + // -1 means that no specific value was passed, so increment + if v == -1 { + *i = countValue(*i + 1) + } else { + *i = countValue(v) + } + return err +} + +func (i *countValue) Type() string { + return "count" +} + +func (i *countValue) String() string { return fmt.Sprintf("%v", *i) } + +func countConv(sval string) (interface{}, error) { + i, err := strconv.Atoi(sval) + if err != nil { + return nil, err + } + return i, nil +} + +// GetCount return the int value of a flag with the given name +func (f *FlagSet) GetCount(name string) (int, error) { + val, err := f.getFlagType(name, "count", countConv) + if err != nil { + return 0, err + } + return val.(int), nil +} + +// CountVar defines a count flag with specified name, default value, and usage string. +// The argument p points to an int variable in which to store the value of the flag. +// A count flag will add 1 to its value evey time it is found on the command line +func (f *FlagSet) CountVar(p *int, name string, usage string) { + f.CountVarP(p, name, "", usage) +} + +// CountVarP is like CountVar only take a shorthand for the flag name. +func (f *FlagSet) CountVarP(p *int, name, shorthand string, usage string) { + flag := f.VarPF(newCountValue(0, p), name, shorthand, usage) + flag.NoOptDefVal = "-1" +} + +// CountVar like CountVar only the flag is placed on the CommandLine instead of a given flag set +func CountVar(p *int, name string, usage string) { + CommandLine.CountVar(p, name, usage) +} + +// CountVarP is like CountVar only take a shorthand for the flag name. +func CountVarP(p *int, name, shorthand string, usage string) { + CommandLine.CountVarP(p, name, shorthand, usage) +} + +// Count defines a count flag with specified name, default value, and usage string. +// The return value is the address of an int variable that stores the value of the flag. +// A count flag will add 1 to its value evey time it is found on the command line +func (f *FlagSet) Count(name string, usage string) *int { + p := new(int) + f.CountVarP(p, name, "", usage) + return p +} + +// CountP is like Count only takes a shorthand for the flag name. +func (f *FlagSet) CountP(name, shorthand string, usage string) *int { + p := new(int) + f.CountVarP(p, name, shorthand, usage) + return p +} + +// Count like Count only the flag is placed on the CommandLine isntead of a given flag set +func Count(name string, usage string) *int { + return CommandLine.CountP(name, "", usage) +} + +// CountP is like Count only takes a shorthand for the flag name. +func CountP(name, shorthand string, usage string) *int { + return CommandLine.CountP(name, shorthand, usage) +} diff --git a/vendor/github.com/spf13/pflag/duration.go b/vendor/github.com/spf13/pflag/duration.go new file mode 100644 index 0000000..e9debef --- /dev/null +++ b/vendor/github.com/spf13/pflag/duration.go @@ -0,0 +1,86 @@ +package pflag + +import ( + "time" +) + +// -- time.Duration Value +type durationValue time.Duration + +func newDurationValue(val time.Duration, p *time.Duration) *durationValue { + *p = val + return (*durationValue)(p) +} + +func (d *durationValue) Set(s string) error { + v, err := time.ParseDuration(s) + *d = durationValue(v) + return err +} + +func (d *durationValue) Type() string { + return "duration" +} + +func (d *durationValue) String() string { return (*time.Duration)(d).String() } + +func durationConv(sval string) (interface{}, error) { + return time.ParseDuration(sval) +} + +// GetDuration return the duration value of a flag with the given name +func (f *FlagSet) GetDuration(name string) (time.Duration, error) { + val, err := f.getFlagType(name, "duration", durationConv) + if err != nil { + return 0, err + } + return val.(time.Duration), nil +} + +// DurationVar defines a time.Duration flag with specified name, default value, and usage string. +// The argument p points to a time.Duration variable in which to store the value of the flag. +func (f *FlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string) { + f.VarP(newDurationValue(value, p), name, "", usage) +} + +// DurationVarP is like DurationVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) DurationVarP(p *time.Duration, name, shorthand string, value time.Duration, usage string) { + f.VarP(newDurationValue(value, p), name, shorthand, usage) +} + +// DurationVar defines a time.Duration flag with specified name, default value, and usage string. +// The argument p points to a time.Duration variable in which to store the value of the flag. +func DurationVar(p *time.Duration, name string, value time.Duration, usage string) { + CommandLine.VarP(newDurationValue(value, p), name, "", usage) +} + +// DurationVarP is like DurationVar, but accepts a shorthand letter that can be used after a single dash. +func DurationVarP(p *time.Duration, name, shorthand string, value time.Duration, usage string) { + CommandLine.VarP(newDurationValue(value, p), name, shorthand, usage) +} + +// Duration defines a time.Duration flag with specified name, default value, and usage string. +// The return value is the address of a time.Duration variable that stores the value of the flag. +func (f *FlagSet) Duration(name string, value time.Duration, usage string) *time.Duration { + p := new(time.Duration) + f.DurationVarP(p, name, "", value, usage) + return p +} + +// DurationP is like Duration, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) DurationP(name, shorthand string, value time.Duration, usage string) *time.Duration { + p := new(time.Duration) + f.DurationVarP(p, name, shorthand, value, usage) + return p +} + +// Duration defines a time.Duration flag with specified name, default value, and usage string. +// The return value is the address of a time.Duration variable that stores the value of the flag. +func Duration(name string, value time.Duration, usage string) *time.Duration { + return CommandLine.DurationP(name, "", value, usage) +} + +// DurationP is like Duration, but accepts a shorthand letter that can be used after a single dash. +func DurationP(name, shorthand string, value time.Duration, usage string) *time.Duration { + return CommandLine.DurationP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/flag.go b/vendor/github.com/spf13/pflag/flag.go new file mode 100644 index 0000000..965df13 --- /dev/null +++ b/vendor/github.com/spf13/pflag/flag.go @@ -0,0 +1,934 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package pflag is a drop-in replacement for Go's flag package, implementing +POSIX/GNU-style --flags. + +pflag is compatible with the GNU extensions to the POSIX recommendations +for command-line options. See +http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html + +Usage: + +pflag is a drop-in replacement of Go's native flag package. If you import +pflag under the name "flag" then all code should continue to function +with no changes. + + import flag "github.com/ogier/pflag" + + There is one exception to this: if you directly instantiate the Flag struct +there is one more field "Shorthand" that you will need to set. +Most code never instantiates this struct directly, and instead uses +functions such as String(), BoolVar(), and Var(), and is therefore +unaffected. + +Define flags using flag.String(), Bool(), Int(), etc. + +This declares an integer flag, -flagname, stored in the pointer ip, with type *int. + var ip = flag.Int("flagname", 1234, "help message for flagname") +If you like, you can bind the flag to a variable using the Var() functions. + var flagvar int + func init() { + flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname") + } +Or you can create custom flags that satisfy the Value interface (with +pointer receivers) and couple them to flag parsing by + flag.Var(&flagVal, "name", "help message for flagname") +For such flags, the default value is just the initial value of the variable. + +After all flags are defined, call + flag.Parse() +to parse the command line into the defined flags. + +Flags may then be used directly. If you're using the flags themselves, +they are all pointers; if you bind to variables, they're values. + fmt.Println("ip has value ", *ip) + fmt.Println("flagvar has value ", flagvar) + +After parsing, the arguments after the flag are available as the +slice flag.Args() or individually as flag.Arg(i). +The arguments are indexed from 0 through flag.NArg()-1. + +The pflag package also defines some new functions that are not in flag, +that give one-letter shorthands for flags. You can use these by appending +'P' to the name of any function that defines a flag. + var ip = flag.IntP("flagname", "f", 1234, "help message") + var flagvar bool + func init() { + flag.BoolVarP("boolname", "b", true, "help message") + } + flag.VarP(&flagVar, "varname", "v", 1234, "help message") +Shorthand letters can be used with single dashes on the command line. +Boolean shorthand flags can be combined with other shorthand flags. + +Command line flag syntax: + --flag // boolean flags only + --flag=x + +Unlike the flag package, a single dash before an option means something +different than a double dash. Single dashes signify a series of shorthand +letters for flags. All but the last shorthand letter must be boolean flags. + // boolean flags + -f + -abc + // non-boolean flags + -n 1234 + -Ifile + // mixed + -abcs "hello" + -abcn1234 + +Flag parsing stops after the terminator "--". Unlike the flag package, +flags can be interspersed with arguments anywhere on the command line +before this terminator. + +Integer flags accept 1234, 0664, 0x1234 and may be negative. +Boolean flags (in their long form) accept 1, 0, t, f, true, false, +TRUE, FALSE, True, False. +Duration flags accept any input valid for time.ParseDuration. + +The default set of command-line flags is controlled by +top-level functions. The FlagSet type allows one to define +independent sets of flags, such as to implement subcommands +in a command-line interface. The methods of FlagSet are +analogous to the top-level functions for the command-line +flag set. +*/ +package pflag + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "sort" + "strings" +) + +// ErrHelp is the error returned if the flag -help is invoked but no such flag is defined. +var ErrHelp = errors.New("pflag: help requested") + +// ErrorHandling defines how to handle flag parsing errors. +type ErrorHandling int + +const ( + // ContinueOnError will return an err from Parse() if an error is found + ContinueOnError ErrorHandling = iota + // ExitOnError will call os.Exit(2) if an error is found when parsing + ExitOnError + // PanicOnError will panic() if an error is found when parsing flags + PanicOnError +) + +// NormalizedName is a flag name that has been normalized according to rules +// for the FlagSet (e.g. making '-' and '_' equivalent). +type NormalizedName string + +// A FlagSet represents a set of defined flags. +type FlagSet struct { + // Usage is the function called when an error occurs while parsing flags. + // The field is a function (not a method) that may be changed to point to + // a custom error handler. + Usage func() + + name string + parsed bool + actual map[NormalizedName]*Flag + formal map[NormalizedName]*Flag + shorthands map[byte]*Flag + args []string // arguments after flags + argsLenAtDash int // len(args) when a '--' was located when parsing, or -1 if no -- + exitOnError bool // does the program exit if there's an error? + errorHandling ErrorHandling + output io.Writer // nil means stderr; use out() accessor + interspersed bool // allow interspersed option/non-option args + normalizeNameFunc func(f *FlagSet, name string) NormalizedName +} + +// A Flag represents the state of a flag. +type Flag struct { + Name string // name as it appears on command line + Shorthand string // one-letter abbreviated flag + Usage string // help message + Value Value // value as set + DefValue string // default value (as text); for usage message + Changed bool // If the user set the value (or if left to default) + NoOptDefVal string //default value (as text); if the flag is on the command line without any options + Deprecated string // If this flag is deprecated, this string is the new or now thing to use + Hidden bool // used by cobra.Command to allow flags to be hidden from help/usage text + ShorthandDeprecated string // If the shorthand of this flag is deprecated, this string is the new or now thing to use + Annotations map[string][]string // used by cobra.Command bash autocomple code +} + +// Value is the interface to the dynamic value stored in a flag. +// (The default value is represented as a string.) +type Value interface { + String() string + Set(string) error + Type() string +} + +// sortFlags returns the flags as a slice in lexicographical sorted order. +func sortFlags(flags map[NormalizedName]*Flag) []*Flag { + list := make(sort.StringSlice, len(flags)) + i := 0 + for k := range flags { + list[i] = string(k) + i++ + } + list.Sort() + result := make([]*Flag, len(list)) + for i, name := range list { + result[i] = flags[NormalizedName(name)] + } + return result +} + +// SetNormalizeFunc allows you to add a function which can translate flag names. +// Flags added to the FlagSet will be translated and then when anything tries to +// look up the flag that will also be translated. So it would be possible to create +// a flag named "getURL" and have it translated to "geturl". A user could then pass +// "--getUrl" which may also be translated to "geturl" and everything will work. +func (f *FlagSet) SetNormalizeFunc(n func(f *FlagSet, name string) NormalizedName) { + f.normalizeNameFunc = n + for k, v := range f.formal { + delete(f.formal, k) + nname := f.normalizeFlagName(string(k)) + f.formal[nname] = v + v.Name = string(nname) + } +} + +// GetNormalizeFunc returns the previously set NormalizeFunc of a function which +// does no translation, if not set previously. +func (f *FlagSet) GetNormalizeFunc() func(f *FlagSet, name string) NormalizedName { + if f.normalizeNameFunc != nil { + return f.normalizeNameFunc + } + return func(f *FlagSet, name string) NormalizedName { return NormalizedName(name) } +} + +func (f *FlagSet) normalizeFlagName(name string) NormalizedName { + n := f.GetNormalizeFunc() + return n(f, name) +} + +func (f *FlagSet) out() io.Writer { + if f.output == nil { + return os.Stderr + } + return f.output +} + +// SetOutput sets the destination for usage and error messages. +// If output is nil, os.Stderr is used. +func (f *FlagSet) SetOutput(output io.Writer) { + f.output = output +} + +// VisitAll visits the flags in lexicographical order, calling fn for each. +// It visits all flags, even those not set. +func (f *FlagSet) VisitAll(fn func(*Flag)) { + for _, flag := range sortFlags(f.formal) { + fn(flag) + } +} + +// HasFlags returns a bool to indicate if the FlagSet has any flags definied. +func (f *FlagSet) HasFlags() bool { + return len(f.formal) > 0 +} + +// HasAvailableFlags returns a bool to indicate if the FlagSet has any flags +// definied that are not hidden or deprecated. +func (f *FlagSet) HasAvailableFlags() bool { + for _, flag := range f.formal { + if !flag.Hidden && len(flag.Deprecated) == 0 { + return true + } + } + return false +} + +// VisitAll visits the command-line flags in lexicographical order, calling +// fn for each. It visits all flags, even those not set. +func VisitAll(fn func(*Flag)) { + CommandLine.VisitAll(fn) +} + +// Visit visits the flags in lexicographical order, calling fn for each. +// It visits only those flags that have been set. +func (f *FlagSet) Visit(fn func(*Flag)) { + for _, flag := range sortFlags(f.actual) { + fn(flag) + } +} + +// Visit visits the command-line flags in lexicographical order, calling fn +// for each. It visits only those flags that have been set. +func Visit(fn func(*Flag)) { + CommandLine.Visit(fn) +} + +// Lookup returns the Flag structure of the named flag, returning nil if none exists. +func (f *FlagSet) Lookup(name string) *Flag { + return f.lookup(f.normalizeFlagName(name)) +} + +// lookup returns the Flag structure of the named flag, returning nil if none exists. +func (f *FlagSet) lookup(name NormalizedName) *Flag { + return f.formal[name] +} + +// func to return a given type for a given flag name +func (f *FlagSet) getFlagType(name string, ftype string, convFunc func(sval string) (interface{}, error)) (interface{}, error) { + flag := f.Lookup(name) + if flag == nil { + err := fmt.Errorf("flag accessed but not defined: %s", name) + return nil, err + } + + if flag.Value.Type() != ftype { + err := fmt.Errorf("trying to get %s value of flag of type %s", ftype, flag.Value.Type()) + return nil, err + } + + sval := flag.Value.String() + result, err := convFunc(sval) + if err != nil { + return nil, err + } + return result, nil +} + +// ArgsLenAtDash will return the length of f.Args at the moment when a -- was +// found during arg parsing. This allows your program to know which args were +// before the -- and which came after. +func (f *FlagSet) ArgsLenAtDash() int { + return f.argsLenAtDash +} + +// MarkDeprecated indicated that a flag is deprecated in your program. It will +// continue to function but will not show up in help or usage messages. Using +// this flag will also print the given usageMessage. +func (f *FlagSet) MarkDeprecated(name string, usageMessage string) error { + flag := f.Lookup(name) + if flag == nil { + return fmt.Errorf("flag %q does not exist", name) + } + if len(usageMessage) == 0 { + return fmt.Errorf("deprecated message for flag %q must be set", name) + } + flag.Deprecated = usageMessage + return nil +} + +// MarkShorthandDeprecated will mark the shorthand of a flag deprecated in your +// program. It will continue to function but will not show up in help or usage +// messages. Using this flag will also print the given usageMessage. +func (f *FlagSet) MarkShorthandDeprecated(name string, usageMessage string) error { + flag := f.Lookup(name) + if flag == nil { + return fmt.Errorf("flag %q does not exist", name) + } + if len(usageMessage) == 0 { + return fmt.Errorf("deprecated message for flag %q must be set", name) + } + flag.ShorthandDeprecated = usageMessage + return nil +} + +// MarkHidden sets a flag to 'hidden' in your program. It will continue to +// function but will not show up in help or usage messages. +func (f *FlagSet) MarkHidden(name string) error { + flag := f.Lookup(name) + if flag == nil { + return fmt.Errorf("flag %q does not exist", name) + } + flag.Hidden = true + return nil +} + +// Lookup returns the Flag structure of the named command-line flag, +// returning nil if none exists. +func Lookup(name string) *Flag { + return CommandLine.Lookup(name) +} + +// Set sets the value of the named flag. +func (f *FlagSet) Set(name, value string) error { + normalName := f.normalizeFlagName(name) + flag, ok := f.formal[normalName] + if !ok { + return fmt.Errorf("no such flag -%v", name) + } + err := flag.Value.Set(value) + if err != nil { + return err + } + if f.actual == nil { + f.actual = make(map[NormalizedName]*Flag) + } + f.actual[normalName] = flag + flag.Changed = true + if len(flag.Deprecated) > 0 { + fmt.Fprintf(os.Stderr, "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated) + } + return nil +} + +// SetAnnotation allows one to set arbitrary annotations on a flag in the FlagSet. +// This is sometimes used by spf13/cobra programs which want to generate additional +// bash completion information. +func (f *FlagSet) SetAnnotation(name, key string, values []string) error { + normalName := f.normalizeFlagName(name) + flag, ok := f.formal[normalName] + if !ok { + return fmt.Errorf("no such flag -%v", name) + } + if flag.Annotations == nil { + flag.Annotations = map[string][]string{} + } + flag.Annotations[key] = values + return nil +} + +// Changed returns true if the flag was explicitly set during Parse() and false +// otherwise +func (f *FlagSet) Changed(name string) bool { + flag := f.Lookup(name) + // If a flag doesn't exist, it wasn't changed.... + if flag == nil { + return false + } + return flag.Changed +} + +// Set sets the value of the named command-line flag. +func Set(name, value string) error { + return CommandLine.Set(name, value) +} + +// PrintDefaults prints, to standard error unless configured +// otherwise, the default values of all defined flags in the set. +func (f *FlagSet) PrintDefaults() { + usages := f.FlagUsages() + fmt.Fprintf(f.out(), "%s", usages) +} + +// isZeroValue guesses whether the string represents the zero +// value for a flag. It is not accurate but in practice works OK. +func isZeroValue(value string) bool { + switch value { + case "false": + return true + case "": + return true + case "": + return true + case "0": + return true + } + return false +} + +// UnquoteUsage extracts a back-quoted name from the usage +// string for a flag and returns it and the un-quoted usage. +// Given "a `name` to show" it returns ("name", "a name to show"). +// If there are no back quotes, the name is an educated guess of the +// type of the flag's value, or the empty string if the flag is boolean. +func UnquoteUsage(flag *Flag) (name string, usage string) { + // Look for a back-quoted name, but avoid the strings package. + usage = flag.Usage + for i := 0; i < len(usage); i++ { + if usage[i] == '`' { + for j := i + 1; j < len(usage); j++ { + if usage[j] == '`' { + name = usage[i+1 : j] + usage = usage[:i] + name + usage[j+1:] + return name, usage + } + } + break // Only one back quote; use type name. + } + } + // No explicit name, so use type if we can find one. + name = "value" + switch flag.Value.(type) { + case boolFlag: + name = "" + case *durationValue: + name = "duration" + case *float64Value: + name = "float" + case *intValue, *int64Value: + name = "int" + case *stringValue: + name = "string" + case *uintValue, *uint64Value: + name = "uint" + } + return +} + +// FlagUsages Returns a string containing the usage information for all flags in +// the FlagSet +func (f *FlagSet) FlagUsages() string { + x := new(bytes.Buffer) + + lines := make([]string, 0, len(f.formal)) + + maxlen := 0 + f.VisitAll(func(flag *Flag) { + if len(flag.Deprecated) > 0 || flag.Hidden { + return + } + + line := "" + if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 { + line = fmt.Sprintf(" -%s, --%s", flag.Shorthand, flag.Name) + } else { + line = fmt.Sprintf(" --%s", flag.Name) + } + + varname, usage := UnquoteUsage(flag) + if len(varname) > 0 { + line += " " + varname + } + if len(flag.NoOptDefVal) > 0 { + switch flag.Value.Type() { + case "string": + line += fmt.Sprintf("[=%q]", flag.NoOptDefVal) + case "bool": + if flag.NoOptDefVal != "true" { + line += fmt.Sprintf("[=%s]", flag.NoOptDefVal) + } + default: + line += fmt.Sprintf("[=%s]", flag.NoOptDefVal) + } + } + + // This special character will be replaced with spacing once the + // correct alignment is calculated + line += "\x00" + if len(line) > maxlen { + maxlen = len(line) + } + + line += usage + if !isZeroValue(flag.DefValue) { + if flag.Value.Type() == "string" { + line += fmt.Sprintf(" (default %q)", flag.DefValue) + } else { + line += fmt.Sprintf(" (default %s)", flag.DefValue) + } + } + + lines = append(lines, line) + }) + + for _, line := range lines { + sidx := strings.Index(line, "\x00") + spacing := strings.Repeat(" ", maxlen-sidx) + fmt.Fprintln(x, line[:sidx], spacing, line[sidx+1:]) + } + + return x.String() +} + +// PrintDefaults prints to standard error the default values of all defined command-line flags. +func PrintDefaults() { + CommandLine.PrintDefaults() +} + +// defaultUsage is the default function to print a usage message. +func defaultUsage(f *FlagSet) { + fmt.Fprintf(f.out(), "Usage of %s:\n", f.name) + f.PrintDefaults() +} + +// NOTE: Usage is not just defaultUsage(CommandLine) +// because it serves (via godoc flag Usage) as the example +// for how to write your own usage function. + +// Usage prints to standard error a usage message documenting all defined command-line flags. +// The function is a variable that may be changed to point to a custom function. +// By default it prints a simple header and calls PrintDefaults; for details about the +// format of the output and how to control it, see the documentation for PrintDefaults. +var Usage = func() { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + PrintDefaults() +} + +// NFlag returns the number of flags that have been set. +func (f *FlagSet) NFlag() int { return len(f.actual) } + +// NFlag returns the number of command-line flags that have been set. +func NFlag() int { return len(CommandLine.actual) } + +// Arg returns the i'th argument. Arg(0) is the first remaining argument +// after flags have been processed. +func (f *FlagSet) Arg(i int) string { + if i < 0 || i >= len(f.args) { + return "" + } + return f.args[i] +} + +// Arg returns the i'th command-line argument. Arg(0) is the first remaining argument +// after flags have been processed. +func Arg(i int) string { + return CommandLine.Arg(i) +} + +// NArg is the number of arguments remaining after flags have been processed. +func (f *FlagSet) NArg() int { return len(f.args) } + +// NArg is the number of arguments remaining after flags have been processed. +func NArg() int { return len(CommandLine.args) } + +// Args returns the non-flag arguments. +func (f *FlagSet) Args() []string { return f.args } + +// Args returns the non-flag command-line arguments. +func Args() []string { return CommandLine.args } + +// Var defines a flag with the specified name and usage string. The type and +// value of the flag are represented by the first argument, of type Value, which +// typically holds a user-defined implementation of Value. For instance, the +// caller could create a flag that turns a comma-separated string into a slice +// of strings by giving the slice the methods of Value; in particular, Set would +// decompose the comma-separated string into the slice. +func (f *FlagSet) Var(value Value, name string, usage string) { + f.VarP(value, name, "", usage) +} + +// VarPF is like VarP, but returns the flag created +func (f *FlagSet) VarPF(value Value, name, shorthand, usage string) *Flag { + // Remember the default value as a string; it won't change. + flag := &Flag{ + Name: name, + Shorthand: shorthand, + Usage: usage, + Value: value, + DefValue: value.String(), + } + f.AddFlag(flag) + return flag +} + +// VarP is like Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) VarP(value Value, name, shorthand, usage string) { + _ = f.VarPF(value, name, shorthand, usage) +} + +// AddFlag will add the flag to the FlagSet +func (f *FlagSet) AddFlag(flag *Flag) { + // Call normalizeFlagName function only once + normalizedFlagName := f.normalizeFlagName(flag.Name) + + _, alreadythere := f.formal[normalizedFlagName] + if alreadythere { + msg := fmt.Sprintf("%s flag redefined: %s", f.name, flag.Name) + fmt.Fprintln(f.out(), msg) + panic(msg) // Happens only if flags are declared with identical names + } + if f.formal == nil { + f.formal = make(map[NormalizedName]*Flag) + } + + flag.Name = string(normalizedFlagName) + f.formal[normalizedFlagName] = flag + + if len(flag.Shorthand) == 0 { + return + } + if len(flag.Shorthand) > 1 { + fmt.Fprintf(f.out(), "%s shorthand more than ASCII character: %s\n", f.name, flag.Shorthand) + panic("shorthand is more than one character") + } + if f.shorthands == nil { + f.shorthands = make(map[byte]*Flag) + } + c := flag.Shorthand[0] + old, alreadythere := f.shorthands[c] + if alreadythere { + fmt.Fprintf(f.out(), "%s shorthand reused: %q for %s already used for %s\n", f.name, c, flag.Name, old.Name) + panic("shorthand redefinition") + } + f.shorthands[c] = flag +} + +// AddFlagSet adds one FlagSet to another. If a flag is already present in f +// the flag from newSet will be ignored +func (f *FlagSet) AddFlagSet(newSet *FlagSet) { + if newSet == nil { + return + } + newSet.VisitAll(func(flag *Flag) { + if f.Lookup(flag.Name) == nil { + f.AddFlag(flag) + } + }) +} + +// Var defines a flag with the specified name and usage string. The type and +// value of the flag are represented by the first argument, of type Value, which +// typically holds a user-defined implementation of Value. For instance, the +// caller could create a flag that turns a comma-separated string into a slice +// of strings by giving the slice the methods of Value; in particular, Set would +// decompose the comma-separated string into the slice. +func Var(value Value, name string, usage string) { + CommandLine.VarP(value, name, "", usage) +} + +// VarP is like Var, but accepts a shorthand letter that can be used after a single dash. +func VarP(value Value, name, shorthand, usage string) { + CommandLine.VarP(value, name, shorthand, usage) +} + +// failf prints to standard error a formatted error and usage message and +// returns the error. +func (f *FlagSet) failf(format string, a ...interface{}) error { + err := fmt.Errorf(format, a...) + fmt.Fprintln(f.out(), err) + f.usage() + return err +} + +// usage calls the Usage method for the flag set, or the usage function if +// the flag set is CommandLine. +func (f *FlagSet) usage() { + if f == CommandLine { + Usage() + } else if f.Usage == nil { + defaultUsage(f) + } else { + f.Usage() + } +} + +func (f *FlagSet) setFlag(flag *Flag, value string, origArg string) error { + if err := flag.Value.Set(value); err != nil { + return f.failf("invalid argument %q for %s: %v", value, origArg, err) + } + // mark as visited for Visit() + if f.actual == nil { + f.actual = make(map[NormalizedName]*Flag) + } + f.actual[f.normalizeFlagName(flag.Name)] = flag + flag.Changed = true + if len(flag.Deprecated) > 0 { + fmt.Fprintf(os.Stderr, "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated) + } + if len(flag.ShorthandDeprecated) > 0 && containsShorthand(origArg, flag.Shorthand) { + fmt.Fprintf(os.Stderr, "Flag shorthand -%s has been deprecated, %s\n", flag.Shorthand, flag.ShorthandDeprecated) + } + return nil +} + +func containsShorthand(arg, shorthand string) bool { + // filter out flags -- + if strings.HasPrefix(arg, "-") { + return false + } + arg = strings.SplitN(arg, "=", 2)[0] + return strings.Contains(arg, shorthand) +} + +func (f *FlagSet) parseLongArg(s string, args []string) (a []string, err error) { + a = args + name := s[2:] + if len(name) == 0 || name[0] == '-' || name[0] == '=' { + err = f.failf("bad flag syntax: %s", s) + return + } + split := strings.SplitN(name, "=", 2) + name = split[0] + flag, alreadythere := f.formal[f.normalizeFlagName(name)] + if !alreadythere { + if name == "help" { // special case for nice help message. + f.usage() + return a, ErrHelp + } + err = f.failf("unknown flag: --%s", name) + return + } + var value string + if len(split) == 2 { + // '--flag=arg' + value = split[1] + } else if len(flag.NoOptDefVal) > 0 { + // '--flag' (arg was optional) + value = flag.NoOptDefVal + } else if len(a) > 0 { + // '--flag arg' + value = a[0] + a = a[1:] + } else { + // '--flag' (arg was required) + err = f.failf("flag needs an argument: %s", s) + return + } + err = f.setFlag(flag, value, s) + return +} + +func (f *FlagSet) parseSingleShortArg(shorthands string, args []string) (outShorts string, outArgs []string, err error) { + if strings.HasPrefix(shorthands, "test.") { + return + } + outArgs = args + outShorts = shorthands[1:] + c := shorthands[0] + + flag, alreadythere := f.shorthands[c] + if !alreadythere { + if c == 'h' { // special case for nice help message. + f.usage() + err = ErrHelp + return + } + //TODO continue on error + err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands) + return + } + var value string + if len(shorthands) > 2 && shorthands[1] == '=' { + value = shorthands[2:] + outShorts = "" + } else if len(flag.NoOptDefVal) > 0 { + value = flag.NoOptDefVal + } else if len(shorthands) > 1 { + value = shorthands[1:] + outShorts = "" + } else if len(args) > 0 { + value = args[0] + outArgs = args[1:] + } else { + err = f.failf("flag needs an argument: %q in -%s", c, shorthands) + return + } + err = f.setFlag(flag, value, shorthands) + return +} + +func (f *FlagSet) parseShortArg(s string, args []string) (a []string, err error) { + a = args + shorthands := s[1:] + + for len(shorthands) > 0 { + shorthands, a, err = f.parseSingleShortArg(shorthands, args) + if err != nil { + return + } + } + + return +} + +func (f *FlagSet) parseArgs(args []string) (err error) { + for len(args) > 0 { + s := args[0] + args = args[1:] + if len(s) == 0 || s[0] != '-' || len(s) == 1 { + if !f.interspersed { + f.args = append(f.args, s) + f.args = append(f.args, args...) + return nil + } + f.args = append(f.args, s) + continue + } + + if s[1] == '-' { + if len(s) == 2 { // "--" terminates the flags + f.argsLenAtDash = len(f.args) + f.args = append(f.args, args...) + break + } + args, err = f.parseLongArg(s, args) + } else { + args, err = f.parseShortArg(s, args) + } + if err != nil { + return + } + } + return +} + +// Parse parses flag definitions from the argument list, which should not +// include the command name. Must be called after all flags in the FlagSet +// are defined and before flags are accessed by the program. +// The return value will be ErrHelp if -help was set but not defined. +func (f *FlagSet) Parse(arguments []string) error { + f.parsed = true + f.args = make([]string, 0, len(arguments)) + err := f.parseArgs(arguments) + if err != nil { + switch f.errorHandling { + case ContinueOnError: + return err + case ExitOnError: + os.Exit(2) + case PanicOnError: + panic(err) + } + } + return nil +} + +// Parsed reports whether f.Parse has been called. +func (f *FlagSet) Parsed() bool { + return f.parsed +} + +// Parse parses the command-line flags from os.Args[1:]. Must be called +// after all flags are defined and before flags are accessed by the program. +func Parse() { + // Ignore errors; CommandLine is set for ExitOnError. + CommandLine.Parse(os.Args[1:]) +} + +// SetInterspersed sets whether to support interspersed option/non-option arguments. +func SetInterspersed(interspersed bool) { + CommandLine.SetInterspersed(interspersed) +} + +// Parsed returns true if the command-line flags have been parsed. +func Parsed() bool { + return CommandLine.Parsed() +} + +// CommandLine is the default set of command-line flags, parsed from os.Args. +var CommandLine = NewFlagSet(os.Args[0], ExitOnError) + +// NewFlagSet returns a new, empty flag set with the specified name and +// error handling property. +func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet { + f := &FlagSet{ + name: name, + errorHandling: errorHandling, + argsLenAtDash: -1, + interspersed: true, + } + return f +} + +// SetInterspersed sets whether to support interspersed option/non-option arguments. +func (f *FlagSet) SetInterspersed(interspersed bool) { + f.interspersed = interspersed +} + +// Init sets the name and error handling property for a flag set. +// By default, the zero FlagSet uses an empty name and the +// ContinueOnError error handling policy. +func (f *FlagSet) Init(name string, errorHandling ErrorHandling) { + f.name = name + f.errorHandling = errorHandling + f.argsLenAtDash = -1 +} diff --git a/vendor/github.com/spf13/pflag/float32.go b/vendor/github.com/spf13/pflag/float32.go new file mode 100644 index 0000000..7683fae --- /dev/null +++ b/vendor/github.com/spf13/pflag/float32.go @@ -0,0 +1,91 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- float32 Value +type float32Value float32 + +func newFloat32Value(val float32, p *float32) *float32Value { + *p = val + return (*float32Value)(p) +} + +func (f *float32Value) Set(s string) error { + v, err := strconv.ParseFloat(s, 32) + *f = float32Value(v) + return err +} + +func (f *float32Value) Type() string { + return "float32" +} + +func (f *float32Value) String() string { return fmt.Sprintf("%v", *f) } + +func float32Conv(sval string) (interface{}, error) { + v, err := strconv.ParseFloat(sval, 32) + if err != nil { + return 0, err + } + return float32(v), nil +} + +// GetFloat32 return the float32 value of a flag with the given name +func (f *FlagSet) GetFloat32(name string) (float32, error) { + val, err := f.getFlagType(name, "float32", float32Conv) + if err != nil { + return 0, err + } + return val.(float32), nil +} + +// Float32Var defines a float32 flag with specified name, default value, and usage string. +// The argument p points to a float32 variable in which to store the value of the flag. +func (f *FlagSet) Float32Var(p *float32, name string, value float32, usage string) { + f.VarP(newFloat32Value(value, p), name, "", usage) +} + +// Float32VarP is like Float32Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Float32VarP(p *float32, name, shorthand string, value float32, usage string) { + f.VarP(newFloat32Value(value, p), name, shorthand, usage) +} + +// Float32Var defines a float32 flag with specified name, default value, and usage string. +// The argument p points to a float32 variable in which to store the value of the flag. +func Float32Var(p *float32, name string, value float32, usage string) { + CommandLine.VarP(newFloat32Value(value, p), name, "", usage) +} + +// Float32VarP is like Float32Var, but accepts a shorthand letter that can be used after a single dash. +func Float32VarP(p *float32, name, shorthand string, value float32, usage string) { + CommandLine.VarP(newFloat32Value(value, p), name, shorthand, usage) +} + +// Float32 defines a float32 flag with specified name, default value, and usage string. +// The return value is the address of a float32 variable that stores the value of the flag. +func (f *FlagSet) Float32(name string, value float32, usage string) *float32 { + p := new(float32) + f.Float32VarP(p, name, "", value, usage) + return p +} + +// Float32P is like Float32, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Float32P(name, shorthand string, value float32, usage string) *float32 { + p := new(float32) + f.Float32VarP(p, name, shorthand, value, usage) + return p +} + +// Float32 defines a float32 flag with specified name, default value, and usage string. +// The return value is the address of a float32 variable that stores the value of the flag. +func Float32(name string, value float32, usage string) *float32 { + return CommandLine.Float32P(name, "", value, usage) +} + +// Float32P is like Float32, but accepts a shorthand letter that can be used after a single dash. +func Float32P(name, shorthand string, value float32, usage string) *float32 { + return CommandLine.Float32P(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/float64.go b/vendor/github.com/spf13/pflag/float64.go new file mode 100644 index 0000000..50fbf8c --- /dev/null +++ b/vendor/github.com/spf13/pflag/float64.go @@ -0,0 +1,87 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- float64 Value +type float64Value float64 + +func newFloat64Value(val float64, p *float64) *float64Value { + *p = val + return (*float64Value)(p) +} + +func (f *float64Value) Set(s string) error { + v, err := strconv.ParseFloat(s, 64) + *f = float64Value(v) + return err +} + +func (f *float64Value) Type() string { + return "float64" +} + +func (f *float64Value) String() string { return fmt.Sprintf("%v", *f) } + +func float64Conv(sval string) (interface{}, error) { + return strconv.ParseFloat(sval, 64) +} + +// GetFloat64 return the float64 value of a flag with the given name +func (f *FlagSet) GetFloat64(name string) (float64, error) { + val, err := f.getFlagType(name, "float64", float64Conv) + if err != nil { + return 0, err + } + return val.(float64), nil +} + +// Float64Var defines a float64 flag with specified name, default value, and usage string. +// The argument p points to a float64 variable in which to store the value of the flag. +func (f *FlagSet) Float64Var(p *float64, name string, value float64, usage string) { + f.VarP(newFloat64Value(value, p), name, "", usage) +} + +// Float64VarP is like Float64Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Float64VarP(p *float64, name, shorthand string, value float64, usage string) { + f.VarP(newFloat64Value(value, p), name, shorthand, usage) +} + +// Float64Var defines a float64 flag with specified name, default value, and usage string. +// The argument p points to a float64 variable in which to store the value of the flag. +func Float64Var(p *float64, name string, value float64, usage string) { + CommandLine.VarP(newFloat64Value(value, p), name, "", usage) +} + +// Float64VarP is like Float64Var, but accepts a shorthand letter that can be used after a single dash. +func Float64VarP(p *float64, name, shorthand string, value float64, usage string) { + CommandLine.VarP(newFloat64Value(value, p), name, shorthand, usage) +} + +// Float64 defines a float64 flag with specified name, default value, and usage string. +// The return value is the address of a float64 variable that stores the value of the flag. +func (f *FlagSet) Float64(name string, value float64, usage string) *float64 { + p := new(float64) + f.Float64VarP(p, name, "", value, usage) + return p +} + +// Float64P is like Float64, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Float64P(name, shorthand string, value float64, usage string) *float64 { + p := new(float64) + f.Float64VarP(p, name, shorthand, value, usage) + return p +} + +// Float64 defines a float64 flag with specified name, default value, and usage string. +// The return value is the address of a float64 variable that stores the value of the flag. +func Float64(name string, value float64, usage string) *float64 { + return CommandLine.Float64P(name, "", value, usage) +} + +// Float64P is like Float64, but accepts a shorthand letter that can be used after a single dash. +func Float64P(name, shorthand string, value float64, usage string) *float64 { + return CommandLine.Float64P(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/golangflag.go b/vendor/github.com/spf13/pflag/golangflag.go new file mode 100644 index 0000000..b056147 --- /dev/null +++ b/vendor/github.com/spf13/pflag/golangflag.go @@ -0,0 +1,104 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pflag + +import ( + goflag "flag" + "fmt" + "reflect" + "strings" +) + +var _ = fmt.Print + +// flagValueWrapper implements pflag.Value around a flag.Value. The main +// difference here is the addition of the Type method that returns a string +// name of the type. As this is generally unknown, we approximate that with +// reflection. +type flagValueWrapper struct { + inner goflag.Value + flagType string +} + +// We are just copying the boolFlag interface out of goflag as that is what +// they use to decide if a flag should get "true" when no arg is given. +type goBoolFlag interface { + goflag.Value + IsBoolFlag() bool +} + +func wrapFlagValue(v goflag.Value) Value { + // If the flag.Value happens to also be a pflag.Value, just use it directly. + if pv, ok := v.(Value); ok { + return pv + } + + pv := &flagValueWrapper{ + inner: v, + } + + t := reflect.TypeOf(v) + if t.Kind() == reflect.Interface || t.Kind() == reflect.Ptr { + t = t.Elem() + } + + pv.flagType = strings.TrimSuffix(t.Name(), "Value") + return pv +} + +func (v *flagValueWrapper) String() string { + return v.inner.String() +} + +func (v *flagValueWrapper) Set(s string) error { + return v.inner.Set(s) +} + +func (v *flagValueWrapper) Type() string { + return v.flagType +} + +// PFlagFromGoFlag will return a *pflag.Flag given a *flag.Flag +// If the *flag.Flag.Name was a single character (ex: `v`) it will be accessiblei +// with both `-v` and `--v` in flags. If the golang flag was more than a single +// character (ex: `verbose`) it will only be accessible via `--verbose` +func PFlagFromGoFlag(goflag *goflag.Flag) *Flag { + // Remember the default value as a string; it won't change. + flag := &Flag{ + Name: goflag.Name, + Usage: goflag.Usage, + Value: wrapFlagValue(goflag.Value), + // Looks like golang flags don't set DefValue correctly :-( + //DefValue: goflag.DefValue, + DefValue: goflag.Value.String(), + } + // Ex: if the golang flag was -v, allow both -v and --v to work + if len(flag.Name) == 1 { + flag.Shorthand = flag.Name + } + if fv, ok := goflag.Value.(goBoolFlag); ok && fv.IsBoolFlag() { + flag.NoOptDefVal = "true" + } + return flag +} + +// AddGoFlag will add the given *flag.Flag to the pflag.FlagSet +func (f *FlagSet) AddGoFlag(goflag *goflag.Flag) { + if f.Lookup(goflag.Name) != nil { + return + } + newflag := PFlagFromGoFlag(goflag) + f.AddFlag(newflag) +} + +// AddGoFlagSet will add the given *flag.FlagSet to the pflag.FlagSet +func (f *FlagSet) AddGoFlagSet(newSet *goflag.FlagSet) { + if newSet == nil { + return + } + newSet.VisitAll(func(goflag *goflag.Flag) { + f.AddGoFlag(goflag) + }) +} diff --git a/vendor/github.com/spf13/pflag/int.go b/vendor/github.com/spf13/pflag/int.go new file mode 100644 index 0000000..b656036 --- /dev/null +++ b/vendor/github.com/spf13/pflag/int.go @@ -0,0 +1,87 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- int Value +type intValue int + +func newIntValue(val int, p *int) *intValue { + *p = val + return (*intValue)(p) +} + +func (i *intValue) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 64) + *i = intValue(v) + return err +} + +func (i *intValue) Type() string { + return "int" +} + +func (i *intValue) String() string { return fmt.Sprintf("%v", *i) } + +func intConv(sval string) (interface{}, error) { + return strconv.Atoi(sval) +} + +// GetInt return the int value of a flag with the given name +func (f *FlagSet) GetInt(name string) (int, error) { + val, err := f.getFlagType(name, "int", intConv) + if err != nil { + return 0, err + } + return val.(int), nil +} + +// IntVar defines an int flag with specified name, default value, and usage string. +// The argument p points to an int variable in which to store the value of the flag. +func (f *FlagSet) IntVar(p *int, name string, value int, usage string) { + f.VarP(newIntValue(value, p), name, "", usage) +} + +// IntVarP is like IntVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IntVarP(p *int, name, shorthand string, value int, usage string) { + f.VarP(newIntValue(value, p), name, shorthand, usage) +} + +// IntVar defines an int flag with specified name, default value, and usage string. +// The argument p points to an int variable in which to store the value of the flag. +func IntVar(p *int, name string, value int, usage string) { + CommandLine.VarP(newIntValue(value, p), name, "", usage) +} + +// IntVarP is like IntVar, but accepts a shorthand letter that can be used after a single dash. +func IntVarP(p *int, name, shorthand string, value int, usage string) { + CommandLine.VarP(newIntValue(value, p), name, shorthand, usage) +} + +// Int defines an int flag with specified name, default value, and usage string. +// The return value is the address of an int variable that stores the value of the flag. +func (f *FlagSet) Int(name string, value int, usage string) *int { + p := new(int) + f.IntVarP(p, name, "", value, usage) + return p +} + +// IntP is like Int, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IntP(name, shorthand string, value int, usage string) *int { + p := new(int) + f.IntVarP(p, name, shorthand, value, usage) + return p +} + +// Int defines an int flag with specified name, default value, and usage string. +// The return value is the address of an int variable that stores the value of the flag. +func Int(name string, value int, usage string) *int { + return CommandLine.IntP(name, "", value, usage) +} + +// IntP is like Int, but accepts a shorthand letter that can be used after a single dash. +func IntP(name, shorthand string, value int, usage string) *int { + return CommandLine.IntP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/int32.go b/vendor/github.com/spf13/pflag/int32.go new file mode 100644 index 0000000..41659a9 --- /dev/null +++ b/vendor/github.com/spf13/pflag/int32.go @@ -0,0 +1,91 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- int32 Value +type int32Value int32 + +func newInt32Value(val int32, p *int32) *int32Value { + *p = val + return (*int32Value)(p) +} + +func (i *int32Value) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 32) + *i = int32Value(v) + return err +} + +func (i *int32Value) Type() string { + return "int32" +} + +func (i *int32Value) String() string { return fmt.Sprintf("%v", *i) } + +func int32Conv(sval string) (interface{}, error) { + v, err := strconv.ParseInt(sval, 0, 32) + if err != nil { + return 0, err + } + return int32(v), nil +} + +// GetInt32 return the int32 value of a flag with the given name +func (f *FlagSet) GetInt32(name string) (int32, error) { + val, err := f.getFlagType(name, "int32", int32Conv) + if err != nil { + return 0, err + } + return val.(int32), nil +} + +// Int32Var defines an int32 flag with specified name, default value, and usage string. +// The argument p points to an int32 variable in which to store the value of the flag. +func (f *FlagSet) Int32Var(p *int32, name string, value int32, usage string) { + f.VarP(newInt32Value(value, p), name, "", usage) +} + +// Int32VarP is like Int32Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int32VarP(p *int32, name, shorthand string, value int32, usage string) { + f.VarP(newInt32Value(value, p), name, shorthand, usage) +} + +// Int32Var defines an int32 flag with specified name, default value, and usage string. +// The argument p points to an int32 variable in which to store the value of the flag. +func Int32Var(p *int32, name string, value int32, usage string) { + CommandLine.VarP(newInt32Value(value, p), name, "", usage) +} + +// Int32VarP is like Int32Var, but accepts a shorthand letter that can be used after a single dash. +func Int32VarP(p *int32, name, shorthand string, value int32, usage string) { + CommandLine.VarP(newInt32Value(value, p), name, shorthand, usage) +} + +// Int32 defines an int32 flag with specified name, default value, and usage string. +// The return value is the address of an int32 variable that stores the value of the flag. +func (f *FlagSet) Int32(name string, value int32, usage string) *int32 { + p := new(int32) + f.Int32VarP(p, name, "", value, usage) + return p +} + +// Int32P is like Int32, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int32P(name, shorthand string, value int32, usage string) *int32 { + p := new(int32) + f.Int32VarP(p, name, shorthand, value, usage) + return p +} + +// Int32 defines an int32 flag with specified name, default value, and usage string. +// The return value is the address of an int32 variable that stores the value of the flag. +func Int32(name string, value int32, usage string) *int32 { + return CommandLine.Int32P(name, "", value, usage) +} + +// Int32P is like Int32, but accepts a shorthand letter that can be used after a single dash. +func Int32P(name, shorthand string, value int32, usage string) *int32 { + return CommandLine.Int32P(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/int64.go b/vendor/github.com/spf13/pflag/int64.go new file mode 100644 index 0000000..6e67e38 --- /dev/null +++ b/vendor/github.com/spf13/pflag/int64.go @@ -0,0 +1,87 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- int64 Value +type int64Value int64 + +func newInt64Value(val int64, p *int64) *int64Value { + *p = val + return (*int64Value)(p) +} + +func (i *int64Value) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 64) + *i = int64Value(v) + return err +} + +func (i *int64Value) Type() string { + return "int64" +} + +func (i *int64Value) String() string { return fmt.Sprintf("%v", *i) } + +func int64Conv(sval string) (interface{}, error) { + return strconv.ParseInt(sval, 0, 64) +} + +// GetInt64 return the int64 value of a flag with the given name +func (f *FlagSet) GetInt64(name string) (int64, error) { + val, err := f.getFlagType(name, "int64", int64Conv) + if err != nil { + return 0, err + } + return val.(int64), nil +} + +// Int64Var defines an int64 flag with specified name, default value, and usage string. +// The argument p points to an int64 variable in which to store the value of the flag. +func (f *FlagSet) Int64Var(p *int64, name string, value int64, usage string) { + f.VarP(newInt64Value(value, p), name, "", usage) +} + +// Int64VarP is like Int64Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int64VarP(p *int64, name, shorthand string, value int64, usage string) { + f.VarP(newInt64Value(value, p), name, shorthand, usage) +} + +// Int64Var defines an int64 flag with specified name, default value, and usage string. +// The argument p points to an int64 variable in which to store the value of the flag. +func Int64Var(p *int64, name string, value int64, usage string) { + CommandLine.VarP(newInt64Value(value, p), name, "", usage) +} + +// Int64VarP is like Int64Var, but accepts a shorthand letter that can be used after a single dash. +func Int64VarP(p *int64, name, shorthand string, value int64, usage string) { + CommandLine.VarP(newInt64Value(value, p), name, shorthand, usage) +} + +// Int64 defines an int64 flag with specified name, default value, and usage string. +// The return value is the address of an int64 variable that stores the value of the flag. +func (f *FlagSet) Int64(name string, value int64, usage string) *int64 { + p := new(int64) + f.Int64VarP(p, name, "", value, usage) + return p +} + +// Int64P is like Int64, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int64P(name, shorthand string, value int64, usage string) *int64 { + p := new(int64) + f.Int64VarP(p, name, shorthand, value, usage) + return p +} + +// Int64 defines an int64 flag with specified name, default value, and usage string. +// The return value is the address of an int64 variable that stores the value of the flag. +func Int64(name string, value int64, usage string) *int64 { + return CommandLine.Int64P(name, "", value, usage) +} + +// Int64P is like Int64, but accepts a shorthand letter that can be used after a single dash. +func Int64P(name, shorthand string, value int64, usage string) *int64 { + return CommandLine.Int64P(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/int8.go b/vendor/github.com/spf13/pflag/int8.go new file mode 100644 index 0000000..400db21 --- /dev/null +++ b/vendor/github.com/spf13/pflag/int8.go @@ -0,0 +1,91 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- int8 Value +type int8Value int8 + +func newInt8Value(val int8, p *int8) *int8Value { + *p = val + return (*int8Value)(p) +} + +func (i *int8Value) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 8) + *i = int8Value(v) + return err +} + +func (i *int8Value) Type() string { + return "int8" +} + +func (i *int8Value) String() string { return fmt.Sprintf("%v", *i) } + +func int8Conv(sval string) (interface{}, error) { + v, err := strconv.ParseInt(sval, 0, 8) + if err != nil { + return 0, err + } + return int8(v), nil +} + +// GetInt8 return the int8 value of a flag with the given name +func (f *FlagSet) GetInt8(name string) (int8, error) { + val, err := f.getFlagType(name, "int8", int8Conv) + if err != nil { + return 0, err + } + return val.(int8), nil +} + +// Int8Var defines an int8 flag with specified name, default value, and usage string. +// The argument p points to an int8 variable in which to store the value of the flag. +func (f *FlagSet) Int8Var(p *int8, name string, value int8, usage string) { + f.VarP(newInt8Value(value, p), name, "", usage) +} + +// Int8VarP is like Int8Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int8VarP(p *int8, name, shorthand string, value int8, usage string) { + f.VarP(newInt8Value(value, p), name, shorthand, usage) +} + +// Int8Var defines an int8 flag with specified name, default value, and usage string. +// The argument p points to an int8 variable in which to store the value of the flag. +func Int8Var(p *int8, name string, value int8, usage string) { + CommandLine.VarP(newInt8Value(value, p), name, "", usage) +} + +// Int8VarP is like Int8Var, but accepts a shorthand letter that can be used after a single dash. +func Int8VarP(p *int8, name, shorthand string, value int8, usage string) { + CommandLine.VarP(newInt8Value(value, p), name, shorthand, usage) +} + +// Int8 defines an int8 flag with specified name, default value, and usage string. +// The return value is the address of an int8 variable that stores the value of the flag. +func (f *FlagSet) Int8(name string, value int8, usage string) *int8 { + p := new(int8) + f.Int8VarP(p, name, "", value, usage) + return p +} + +// Int8P is like Int8, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int8P(name, shorthand string, value int8, usage string) *int8 { + p := new(int8) + f.Int8VarP(p, name, shorthand, value, usage) + return p +} + +// Int8 defines an int8 flag with specified name, default value, and usage string. +// The return value is the address of an int8 variable that stores the value of the flag. +func Int8(name string, value int8, usage string) *int8 { + return CommandLine.Int8P(name, "", value, usage) +} + +// Int8P is like Int8, but accepts a shorthand letter that can be used after a single dash. +func Int8P(name, shorthand string, value int8, usage string) *int8 { + return CommandLine.Int8P(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/int_slice.go b/vendor/github.com/spf13/pflag/int_slice.go new file mode 100644 index 0000000..1e7c9ed --- /dev/null +++ b/vendor/github.com/spf13/pflag/int_slice.go @@ -0,0 +1,128 @@ +package pflag + +import ( + "fmt" + "strconv" + "strings" +) + +// -- intSlice Value +type intSliceValue struct { + value *[]int + changed bool +} + +func newIntSliceValue(val []int, p *[]int) *intSliceValue { + isv := new(intSliceValue) + isv.value = p + *isv.value = val + return isv +} + +func (s *intSliceValue) Set(val string) error { + ss := strings.Split(val, ",") + out := make([]int, len(ss)) + for i, d := range ss { + var err error + out[i], err = strconv.Atoi(d) + if err != nil { + return err + } + + } + if !s.changed { + *s.value = out + } else { + *s.value = append(*s.value, out...) + } + s.changed = true + return nil +} + +func (s *intSliceValue) Type() string { + return "intSlice" +} + +func (s *intSliceValue) String() string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = fmt.Sprintf("%d", d) + } + return "[" + strings.Join(out, ",") + "]" +} + +func intSliceConv(val string) (interface{}, error) { + val = strings.Trim(val, "[]") + // Empty string would cause a slice with one (empty) entry + if len(val) == 0 { + return []int{}, nil + } + ss := strings.Split(val, ",") + out := make([]int, len(ss)) + for i, d := range ss { + var err error + out[i], err = strconv.Atoi(d) + if err != nil { + return nil, err + } + + } + return out, nil +} + +// GetIntSlice return the []int value of a flag with the given name +func (f *FlagSet) GetIntSlice(name string) ([]int, error) { + val, err := f.getFlagType(name, "intSlice", intSliceConv) + if err != nil { + return []int{}, err + } + return val.([]int), nil +} + +// IntSliceVar defines a intSlice flag with specified name, default value, and usage string. +// The argument p points to a []int variable in which to store the value of the flag. +func (f *FlagSet) IntSliceVar(p *[]int, name string, value []int, usage string) { + f.VarP(newIntSliceValue(value, p), name, "", usage) +} + +// IntSliceVarP is like IntSliceVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IntSliceVarP(p *[]int, name, shorthand string, value []int, usage string) { + f.VarP(newIntSliceValue(value, p), name, shorthand, usage) +} + +// IntSliceVar defines a int[] flag with specified name, default value, and usage string. +// The argument p points to a int[] variable in which to store the value of the flag. +func IntSliceVar(p *[]int, name string, value []int, usage string) { + CommandLine.VarP(newIntSliceValue(value, p), name, "", usage) +} + +// IntSliceVarP is like IntSliceVar, but accepts a shorthand letter that can be used after a single dash. +func IntSliceVarP(p *[]int, name, shorthand string, value []int, usage string) { + CommandLine.VarP(newIntSliceValue(value, p), name, shorthand, usage) +} + +// IntSlice defines a []int flag with specified name, default value, and usage string. +// The return value is the address of a []int variable that stores the value of the flag. +func (f *FlagSet) IntSlice(name string, value []int, usage string) *[]int { + p := []int{} + f.IntSliceVarP(&p, name, "", value, usage) + return &p +} + +// IntSliceP is like IntSlice, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IntSliceP(name, shorthand string, value []int, usage string) *[]int { + p := []int{} + f.IntSliceVarP(&p, name, shorthand, value, usage) + return &p +} + +// IntSlice defines a []int flag with specified name, default value, and usage string. +// The return value is the address of a []int variable that stores the value of the flag. +func IntSlice(name string, value []int, usage string) *[]int { + return CommandLine.IntSliceP(name, "", value, usage) +} + +// IntSliceP is like IntSlice, but accepts a shorthand letter that can be used after a single dash. +func IntSliceP(name, shorthand string, value []int, usage string) *[]int { + return CommandLine.IntSliceP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/ip.go b/vendor/github.com/spf13/pflag/ip.go new file mode 100644 index 0000000..88a1743 --- /dev/null +++ b/vendor/github.com/spf13/pflag/ip.go @@ -0,0 +1,96 @@ +package pflag + +import ( + "fmt" + "net" + "strings" +) + +var _ = strings.TrimSpace + +// -- net.IP value +type ipValue net.IP + +func newIPValue(val net.IP, p *net.IP) *ipValue { + *p = val + return (*ipValue)(p) +} + +func (i *ipValue) String() string { return net.IP(*i).String() } +func (i *ipValue) Set(s string) error { + ip := net.ParseIP(strings.TrimSpace(s)) + if ip == nil { + return fmt.Errorf("failed to parse IP: %q", s) + } + *i = ipValue(ip) + return nil +} + +func (i *ipValue) Type() string { + return "ip" +} + +func ipConv(sval string) (interface{}, error) { + ip := net.ParseIP(sval) + if ip != nil { + return ip, nil + } + return nil, fmt.Errorf("invalid string being converted to IP address: %s", sval) +} + +// GetIP return the net.IP value of a flag with the given name +func (f *FlagSet) GetIP(name string) (net.IP, error) { + val, err := f.getFlagType(name, "ip", ipConv) + if err != nil { + return nil, err + } + return val.(net.IP), nil +} + +// IPVar defines an net.IP flag with specified name, default value, and usage string. +// The argument p points to an net.IP variable in which to store the value of the flag. +func (f *FlagSet) IPVar(p *net.IP, name string, value net.IP, usage string) { + f.VarP(newIPValue(value, p), name, "", usage) +} + +// IPVarP is like IPVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IPVarP(p *net.IP, name, shorthand string, value net.IP, usage string) { + f.VarP(newIPValue(value, p), name, shorthand, usage) +} + +// IPVar defines an net.IP flag with specified name, default value, and usage string. +// The argument p points to an net.IP variable in which to store the value of the flag. +func IPVar(p *net.IP, name string, value net.IP, usage string) { + CommandLine.VarP(newIPValue(value, p), name, "", usage) +} + +// IPVarP is like IPVar, but accepts a shorthand letter that can be used after a single dash. +func IPVarP(p *net.IP, name, shorthand string, value net.IP, usage string) { + CommandLine.VarP(newIPValue(value, p), name, shorthand, usage) +} + +// IP defines an net.IP flag with specified name, default value, and usage string. +// The return value is the address of an net.IP variable that stores the value of the flag. +func (f *FlagSet) IP(name string, value net.IP, usage string) *net.IP { + p := new(net.IP) + f.IPVarP(p, name, "", value, usage) + return p +} + +// IPP is like IP, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IPP(name, shorthand string, value net.IP, usage string) *net.IP { + p := new(net.IP) + f.IPVarP(p, name, shorthand, value, usage) + return p +} + +// IP defines an net.IP flag with specified name, default value, and usage string. +// The return value is the address of an net.IP variable that stores the value of the flag. +func IP(name string, value net.IP, usage string) *net.IP { + return CommandLine.IPP(name, "", value, usage) +} + +// IPP is like IP, but accepts a shorthand letter that can be used after a single dash. +func IPP(name, shorthand string, value net.IP, usage string) *net.IP { + return CommandLine.IPP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/ipmask.go b/vendor/github.com/spf13/pflag/ipmask.go new file mode 100644 index 0000000..5bd44bd --- /dev/null +++ b/vendor/github.com/spf13/pflag/ipmask.go @@ -0,0 +1,122 @@ +package pflag + +import ( + "fmt" + "net" + "strconv" +) + +// -- net.IPMask value +type ipMaskValue net.IPMask + +func newIPMaskValue(val net.IPMask, p *net.IPMask) *ipMaskValue { + *p = val + return (*ipMaskValue)(p) +} + +func (i *ipMaskValue) String() string { return net.IPMask(*i).String() } +func (i *ipMaskValue) Set(s string) error { + ip := ParseIPv4Mask(s) + if ip == nil { + return fmt.Errorf("failed to parse IP mask: %q", s) + } + *i = ipMaskValue(ip) + return nil +} + +func (i *ipMaskValue) Type() string { + return "ipMask" +} + +// ParseIPv4Mask written in IP form (e.g. 255.255.255.0). +// This function should really belong to the net package. +func ParseIPv4Mask(s string) net.IPMask { + mask := net.ParseIP(s) + if mask == nil { + if len(s) != 8 { + return nil + } + // net.IPMask.String() actually outputs things like ffffff00 + // so write a horrible parser for that as well :-( + m := []int{} + for i := 0; i < 4; i++ { + b := "0x" + s[2*i:2*i+2] + d, err := strconv.ParseInt(b, 0, 0) + if err != nil { + return nil + } + m = append(m, int(d)) + } + s := fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3]) + mask = net.ParseIP(s) + if mask == nil { + return nil + } + } + return net.IPv4Mask(mask[12], mask[13], mask[14], mask[15]) +} + +func parseIPv4Mask(sval string) (interface{}, error) { + mask := ParseIPv4Mask(sval) + if mask == nil { + return nil, fmt.Errorf("unable to parse %s as net.IPMask", sval) + } + return mask, nil +} + +// GetIPv4Mask return the net.IPv4Mask value of a flag with the given name +func (f *FlagSet) GetIPv4Mask(name string) (net.IPMask, error) { + val, err := f.getFlagType(name, "ipMask", parseIPv4Mask) + if err != nil { + return nil, err + } + return val.(net.IPMask), nil +} + +// IPMaskVar defines an net.IPMask flag with specified name, default value, and usage string. +// The argument p points to an net.IPMask variable in which to store the value of the flag. +func (f *FlagSet) IPMaskVar(p *net.IPMask, name string, value net.IPMask, usage string) { + f.VarP(newIPMaskValue(value, p), name, "", usage) +} + +// IPMaskVarP is like IPMaskVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IPMaskVarP(p *net.IPMask, name, shorthand string, value net.IPMask, usage string) { + f.VarP(newIPMaskValue(value, p), name, shorthand, usage) +} + +// IPMaskVar defines an net.IPMask flag with specified name, default value, and usage string. +// The argument p points to an net.IPMask variable in which to store the value of the flag. +func IPMaskVar(p *net.IPMask, name string, value net.IPMask, usage string) { + CommandLine.VarP(newIPMaskValue(value, p), name, "", usage) +} + +// IPMaskVarP is like IPMaskVar, but accepts a shorthand letter that can be used after a single dash. +func IPMaskVarP(p *net.IPMask, name, shorthand string, value net.IPMask, usage string) { + CommandLine.VarP(newIPMaskValue(value, p), name, shorthand, usage) +} + +// IPMask defines an net.IPMask flag with specified name, default value, and usage string. +// The return value is the address of an net.IPMask variable that stores the value of the flag. +func (f *FlagSet) IPMask(name string, value net.IPMask, usage string) *net.IPMask { + p := new(net.IPMask) + f.IPMaskVarP(p, name, "", value, usage) + return p +} + +// IPMaskP is like IPMask, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IPMaskP(name, shorthand string, value net.IPMask, usage string) *net.IPMask { + p := new(net.IPMask) + f.IPMaskVarP(p, name, shorthand, value, usage) + return p +} + +// IPMask defines an net.IPMask flag with specified name, default value, and usage string. +// The return value is the address of an net.IPMask variable that stores the value of the flag. +func IPMask(name string, value net.IPMask, usage string) *net.IPMask { + return CommandLine.IPMaskP(name, "", value, usage) +} + +// IPMaskP is like IP, but accepts a shorthand letter that can be used after a single dash. +func IPMaskP(name, shorthand string, value net.IPMask, usage string) *net.IPMask { + return CommandLine.IPMaskP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/ipnet.go b/vendor/github.com/spf13/pflag/ipnet.go new file mode 100644 index 0000000..149b764 --- /dev/null +++ b/vendor/github.com/spf13/pflag/ipnet.go @@ -0,0 +1,100 @@ +package pflag + +import ( + "fmt" + "net" + "strings" +) + +// IPNet adapts net.IPNet for use as a flag. +type ipNetValue net.IPNet + +func (ipnet ipNetValue) String() string { + n := net.IPNet(ipnet) + return n.String() +} + +func (ipnet *ipNetValue) Set(value string) error { + _, n, err := net.ParseCIDR(strings.TrimSpace(value)) + if err != nil { + return err + } + *ipnet = ipNetValue(*n) + return nil +} + +func (*ipNetValue) Type() string { + return "ipNet" +} + +var _ = strings.TrimSpace + +func newIPNetValue(val net.IPNet, p *net.IPNet) *ipNetValue { + *p = val + return (*ipNetValue)(p) +} + +func ipNetConv(sval string) (interface{}, error) { + _, n, err := net.ParseCIDR(strings.TrimSpace(sval)) + if err == nil { + return *n, nil + } + return nil, fmt.Errorf("invalid string being converted to IPNet: %s", sval) +} + +// GetIPNet return the net.IPNet value of a flag with the given name +func (f *FlagSet) GetIPNet(name string) (net.IPNet, error) { + val, err := f.getFlagType(name, "ipNet", ipNetConv) + if err != nil { + return net.IPNet{}, err + } + return val.(net.IPNet), nil +} + +// IPNetVar defines an net.IPNet flag with specified name, default value, and usage string. +// The argument p points to an net.IPNet variable in which to store the value of the flag. +func (f *FlagSet) IPNetVar(p *net.IPNet, name string, value net.IPNet, usage string) { + f.VarP(newIPNetValue(value, p), name, "", usage) +} + +// IPNetVarP is like IPNetVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IPNetVarP(p *net.IPNet, name, shorthand string, value net.IPNet, usage string) { + f.VarP(newIPNetValue(value, p), name, shorthand, usage) +} + +// IPNetVar defines an net.IPNet flag with specified name, default value, and usage string. +// The argument p points to an net.IPNet variable in which to store the value of the flag. +func IPNetVar(p *net.IPNet, name string, value net.IPNet, usage string) { + CommandLine.VarP(newIPNetValue(value, p), name, "", usage) +} + +// IPNetVarP is like IPNetVar, but accepts a shorthand letter that can be used after a single dash. +func IPNetVarP(p *net.IPNet, name, shorthand string, value net.IPNet, usage string) { + CommandLine.VarP(newIPNetValue(value, p), name, shorthand, usage) +} + +// IPNet defines an net.IPNet flag with specified name, default value, and usage string. +// The return value is the address of an net.IPNet variable that stores the value of the flag. +func (f *FlagSet) IPNet(name string, value net.IPNet, usage string) *net.IPNet { + p := new(net.IPNet) + f.IPNetVarP(p, name, "", value, usage) + return p +} + +// IPNetP is like IPNet, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) IPNetP(name, shorthand string, value net.IPNet, usage string) *net.IPNet { + p := new(net.IPNet) + f.IPNetVarP(p, name, shorthand, value, usage) + return p +} + +// IPNet defines an net.IPNet flag with specified name, default value, and usage string. +// The return value is the address of an net.IPNet variable that stores the value of the flag. +func IPNet(name string, value net.IPNet, usage string) *net.IPNet { + return CommandLine.IPNetP(name, "", value, usage) +} + +// IPNetP is like IPNet, but accepts a shorthand letter that can be used after a single dash. +func IPNetP(name, shorthand string, value net.IPNet, usage string) *net.IPNet { + return CommandLine.IPNetP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/string.go b/vendor/github.com/spf13/pflag/string.go new file mode 100644 index 0000000..e296136 --- /dev/null +++ b/vendor/github.com/spf13/pflag/string.go @@ -0,0 +1,82 @@ +package pflag + +import "fmt" + +// -- string Value +type stringValue string + +func newStringValue(val string, p *string) *stringValue { + *p = val + return (*stringValue)(p) +} + +func (s *stringValue) Set(val string) error { + *s = stringValue(val) + return nil +} +func (s *stringValue) Type() string { + return "string" +} + +func (s *stringValue) String() string { return fmt.Sprintf("%s", *s) } + +func stringConv(sval string) (interface{}, error) { + return sval, nil +} + +// GetString return the string value of a flag with the given name +func (f *FlagSet) GetString(name string) (string, error) { + val, err := f.getFlagType(name, "string", stringConv) + if err != nil { + return "", err + } + return val.(string), nil +} + +// StringVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a string variable in which to store the value of the flag. +func (f *FlagSet) StringVar(p *string, name string, value string, usage string) { + f.VarP(newStringValue(value, p), name, "", usage) +} + +// StringVarP is like StringVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) StringVarP(p *string, name, shorthand string, value string, usage string) { + f.VarP(newStringValue(value, p), name, shorthand, usage) +} + +// StringVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a string variable in which to store the value of the flag. +func StringVar(p *string, name string, value string, usage string) { + CommandLine.VarP(newStringValue(value, p), name, "", usage) +} + +// StringVarP is like StringVar, but accepts a shorthand letter that can be used after a single dash. +func StringVarP(p *string, name, shorthand string, value string, usage string) { + CommandLine.VarP(newStringValue(value, p), name, shorthand, usage) +} + +// String defines a string flag with specified name, default value, and usage string. +// The return value is the address of a string variable that stores the value of the flag. +func (f *FlagSet) String(name string, value string, usage string) *string { + p := new(string) + f.StringVarP(p, name, "", value, usage) + return p +} + +// StringP is like String, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) StringP(name, shorthand string, value string, usage string) *string { + p := new(string) + f.StringVarP(p, name, shorthand, value, usage) + return p +} + +// String defines a string flag with specified name, default value, and usage string. +// The return value is the address of a string variable that stores the value of the flag. +func String(name string, value string, usage string) *string { + return CommandLine.StringP(name, "", value, usage) +} + +// StringP is like String, but accepts a shorthand letter that can be used after a single dash. +func StringP(name, shorthand string, value string, usage string) *string { + return CommandLine.StringP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/string_slice.go b/vendor/github.com/spf13/pflag/string_slice.go new file mode 100644 index 0000000..b53648b --- /dev/null +++ b/vendor/github.com/spf13/pflag/string_slice.go @@ -0,0 +1,111 @@ +package pflag + +import ( + "encoding/csv" + "fmt" + "strings" +) + +var _ = fmt.Fprint + +// -- stringSlice Value +type stringSliceValue struct { + value *[]string + changed bool +} + +func newStringSliceValue(val []string, p *[]string) *stringSliceValue { + ssv := new(stringSliceValue) + ssv.value = p + *ssv.value = val + return ssv +} + +func (s *stringSliceValue) Set(val string) error { + stringReader := strings.NewReader(val) + csvReader := csv.NewReader(stringReader) + v, err := csvReader.Read() + if err != nil { + return err + } + if !s.changed { + *s.value = v + } else { + *s.value = append(*s.value, v...) + } + s.changed = true + return nil +} + +func (s *stringSliceValue) Type() string { + return "stringSlice" +} + +func (s *stringSliceValue) String() string { return "[" + strings.Join(*s.value, ",") + "]" } + +func stringSliceConv(sval string) (interface{}, error) { + sval = strings.Trim(sval, "[]") + // An empty string would cause a slice with one (empty) string + if len(sval) == 0 { + return []string{}, nil + } + v := strings.Split(sval, ",") + return v, nil +} + +// GetStringSlice return the []string value of a flag with the given name +func (f *FlagSet) GetStringSlice(name string) ([]string, error) { + val, err := f.getFlagType(name, "stringSlice", stringSliceConv) + if err != nil { + return []string{}, err + } + return val.([]string), nil +} + +// StringSliceVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a []string variable in which to store the value of the flag. +func (f *FlagSet) StringSliceVar(p *[]string, name string, value []string, usage string) { + f.VarP(newStringSliceValue(value, p), name, "", usage) +} + +// StringSliceVarP is like StringSliceVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) StringSliceVarP(p *[]string, name, shorthand string, value []string, usage string) { + f.VarP(newStringSliceValue(value, p), name, shorthand, usage) +} + +// StringSliceVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a []string variable in which to store the value of the flag. +func StringSliceVar(p *[]string, name string, value []string, usage string) { + CommandLine.VarP(newStringSliceValue(value, p), name, "", usage) +} + +// StringSliceVarP is like StringSliceVar, but accepts a shorthand letter that can be used after a single dash. +func StringSliceVarP(p *[]string, name, shorthand string, value []string, usage string) { + CommandLine.VarP(newStringSliceValue(value, p), name, shorthand, usage) +} + +// StringSlice defines a string flag with specified name, default value, and usage string. +// The return value is the address of a []string variable that stores the value of the flag. +func (f *FlagSet) StringSlice(name string, value []string, usage string) *[]string { + p := []string{} + f.StringSliceVarP(&p, name, "", value, usage) + return &p +} + +// StringSliceP is like StringSlice, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) StringSliceP(name, shorthand string, value []string, usage string) *[]string { + p := []string{} + f.StringSliceVarP(&p, name, shorthand, value, usage) + return &p +} + +// StringSlice defines a string flag with specified name, default value, and usage string. +// The return value is the address of a []string variable that stores the value of the flag. +func StringSlice(name string, value []string, usage string) *[]string { + return CommandLine.StringSliceP(name, "", value, usage) +} + +// StringSliceP is like StringSlice, but accepts a shorthand letter that can be used after a single dash. +func StringSliceP(name, shorthand string, value []string, usage string) *[]string { + return CommandLine.StringSliceP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/uint.go b/vendor/github.com/spf13/pflag/uint.go new file mode 100644 index 0000000..e142b49 --- /dev/null +++ b/vendor/github.com/spf13/pflag/uint.go @@ -0,0 +1,91 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- uint Value +type uintValue uint + +func newUintValue(val uint, p *uint) *uintValue { + *p = val + return (*uintValue)(p) +} + +func (i *uintValue) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 64) + *i = uintValue(v) + return err +} + +func (i *uintValue) Type() string { + return "uint" +} + +func (i *uintValue) String() string { return fmt.Sprintf("%v", *i) } + +func uintConv(sval string) (interface{}, error) { + v, err := strconv.ParseUint(sval, 0, 0) + if err != nil { + return 0, err + } + return uint(v), nil +} + +// GetUint return the uint value of a flag with the given name +func (f *FlagSet) GetUint(name string) (uint, error) { + val, err := f.getFlagType(name, "uint", uintConv) + if err != nil { + return 0, err + } + return val.(uint), nil +} + +// UintVar defines a uint flag with specified name, default value, and usage string. +// The argument p points to a uint variable in which to store the value of the flag. +func (f *FlagSet) UintVar(p *uint, name string, value uint, usage string) { + f.VarP(newUintValue(value, p), name, "", usage) +} + +// UintVarP is like UintVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) UintVarP(p *uint, name, shorthand string, value uint, usage string) { + f.VarP(newUintValue(value, p), name, shorthand, usage) +} + +// UintVar defines a uint flag with specified name, default value, and usage string. +// The argument p points to a uint variable in which to store the value of the flag. +func UintVar(p *uint, name string, value uint, usage string) { + CommandLine.VarP(newUintValue(value, p), name, "", usage) +} + +// UintVarP is like UintVar, but accepts a shorthand letter that can be used after a single dash. +func UintVarP(p *uint, name, shorthand string, value uint, usage string) { + CommandLine.VarP(newUintValue(value, p), name, shorthand, usage) +} + +// Uint defines a uint flag with specified name, default value, and usage string. +// The return value is the address of a uint variable that stores the value of the flag. +func (f *FlagSet) Uint(name string, value uint, usage string) *uint { + p := new(uint) + f.UintVarP(p, name, "", value, usage) + return p +} + +// UintP is like Uint, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) UintP(name, shorthand string, value uint, usage string) *uint { + p := new(uint) + f.UintVarP(p, name, shorthand, value, usage) + return p +} + +// Uint defines a uint flag with specified name, default value, and usage string. +// The return value is the address of a uint variable that stores the value of the flag. +func Uint(name string, value uint, usage string) *uint { + return CommandLine.UintP(name, "", value, usage) +} + +// UintP is like Uint, but accepts a shorthand letter that can be used after a single dash. +func UintP(name, shorthand string, value uint, usage string) *uint { + return CommandLine.UintP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/uint16.go b/vendor/github.com/spf13/pflag/uint16.go new file mode 100644 index 0000000..5c96c19 --- /dev/null +++ b/vendor/github.com/spf13/pflag/uint16.go @@ -0,0 +1,89 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- uint16 value +type uint16Value uint16 + +func newUint16Value(val uint16, p *uint16) *uint16Value { + *p = val + return (*uint16Value)(p) +} +func (i *uint16Value) String() string { return fmt.Sprintf("%d", *i) } +func (i *uint16Value) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 16) + *i = uint16Value(v) + return err +} + +func (i *uint16Value) Type() string { + return "uint16" +} + +func uint16Conv(sval string) (interface{}, error) { + v, err := strconv.ParseUint(sval, 0, 16) + if err != nil { + return 0, err + } + return uint16(v), nil +} + +// GetUint16 return the uint16 value of a flag with the given name +func (f *FlagSet) GetUint16(name string) (uint16, error) { + val, err := f.getFlagType(name, "uint16", uint16Conv) + if err != nil { + return 0, err + } + return val.(uint16), nil +} + +// Uint16Var defines a uint flag with specified name, default value, and usage string. +// The argument p points to a uint variable in which to store the value of the flag. +func (f *FlagSet) Uint16Var(p *uint16, name string, value uint16, usage string) { + f.VarP(newUint16Value(value, p), name, "", usage) +} + +// Uint16VarP is like Uint16Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Uint16VarP(p *uint16, name, shorthand string, value uint16, usage string) { + f.VarP(newUint16Value(value, p), name, shorthand, usage) +} + +// Uint16Var defines a uint flag with specified name, default value, and usage string. +// The argument p points to a uint variable in which to store the value of the flag. +func Uint16Var(p *uint16, name string, value uint16, usage string) { + CommandLine.VarP(newUint16Value(value, p), name, "", usage) +} + +// Uint16VarP is like Uint16Var, but accepts a shorthand letter that can be used after a single dash. +func Uint16VarP(p *uint16, name, shorthand string, value uint16, usage string) { + CommandLine.VarP(newUint16Value(value, p), name, shorthand, usage) +} + +// Uint16 defines a uint flag with specified name, default value, and usage string. +// The return value is the address of a uint variable that stores the value of the flag. +func (f *FlagSet) Uint16(name string, value uint16, usage string) *uint16 { + p := new(uint16) + f.Uint16VarP(p, name, "", value, usage) + return p +} + +// Uint16P is like Uint16, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Uint16P(name, shorthand string, value uint16, usage string) *uint16 { + p := new(uint16) + f.Uint16VarP(p, name, shorthand, value, usage) + return p +} + +// Uint16 defines a uint flag with specified name, default value, and usage string. +// The return value is the address of a uint variable that stores the value of the flag. +func Uint16(name string, value uint16, usage string) *uint16 { + return CommandLine.Uint16P(name, "", value, usage) +} + +// Uint16P is like Uint16, but accepts a shorthand letter that can be used after a single dash. +func Uint16P(name, shorthand string, value uint16, usage string) *uint16 { + return CommandLine.Uint16P(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/uint32.go b/vendor/github.com/spf13/pflag/uint32.go new file mode 100644 index 0000000..294fcaa --- /dev/null +++ b/vendor/github.com/spf13/pflag/uint32.go @@ -0,0 +1,89 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- uint16 value +type uint32Value uint32 + +func newUint32Value(val uint32, p *uint32) *uint32Value { + *p = val + return (*uint32Value)(p) +} +func (i *uint32Value) String() string { return fmt.Sprintf("%d", *i) } +func (i *uint32Value) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 32) + *i = uint32Value(v) + return err +} + +func (i *uint32Value) Type() string { + return "uint32" +} + +func uint32Conv(sval string) (interface{}, error) { + v, err := strconv.ParseUint(sval, 0, 32) + if err != nil { + return 0, err + } + return uint32(v), nil +} + +// GetUint32 return the uint32 value of a flag with the given name +func (f *FlagSet) GetUint32(name string) (uint32, error) { + val, err := f.getFlagType(name, "uint32", uint32Conv) + if err != nil { + return 0, err + } + return val.(uint32), nil +} + +// Uint32Var defines a uint32 flag with specified name, default value, and usage string. +// The argument p points to a uint32 variable in which to store the value of the flag. +func (f *FlagSet) Uint32Var(p *uint32, name string, value uint32, usage string) { + f.VarP(newUint32Value(value, p), name, "", usage) +} + +// Uint32VarP is like Uint32Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Uint32VarP(p *uint32, name, shorthand string, value uint32, usage string) { + f.VarP(newUint32Value(value, p), name, shorthand, usage) +} + +// Uint32Var defines a uint32 flag with specified name, default value, and usage string. +// The argument p points to a uint32 variable in which to store the value of the flag. +func Uint32Var(p *uint32, name string, value uint32, usage string) { + CommandLine.VarP(newUint32Value(value, p), name, "", usage) +} + +// Uint32VarP is like Uint32Var, but accepts a shorthand letter that can be used after a single dash. +func Uint32VarP(p *uint32, name, shorthand string, value uint32, usage string) { + CommandLine.VarP(newUint32Value(value, p), name, shorthand, usage) +} + +// Uint32 defines a uint32 flag with specified name, default value, and usage string. +// The return value is the address of a uint32 variable that stores the value of the flag. +func (f *FlagSet) Uint32(name string, value uint32, usage string) *uint32 { + p := new(uint32) + f.Uint32VarP(p, name, "", value, usage) + return p +} + +// Uint32P is like Uint32, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Uint32P(name, shorthand string, value uint32, usage string) *uint32 { + p := new(uint32) + f.Uint32VarP(p, name, shorthand, value, usage) + return p +} + +// Uint32 defines a uint32 flag with specified name, default value, and usage string. +// The return value is the address of a uint32 variable that stores the value of the flag. +func Uint32(name string, value uint32, usage string) *uint32 { + return CommandLine.Uint32P(name, "", value, usage) +} + +// Uint32P is like Uint32, but accepts a shorthand letter that can be used after a single dash. +func Uint32P(name, shorthand string, value uint32, usage string) *uint32 { + return CommandLine.Uint32P(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/uint64.go b/vendor/github.com/spf13/pflag/uint64.go new file mode 100644 index 0000000..c681885 --- /dev/null +++ b/vendor/github.com/spf13/pflag/uint64.go @@ -0,0 +1,91 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- uint64 Value +type uint64Value uint64 + +func newUint64Value(val uint64, p *uint64) *uint64Value { + *p = val + return (*uint64Value)(p) +} + +func (i *uint64Value) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 64) + *i = uint64Value(v) + return err +} + +func (i *uint64Value) Type() string { + return "uint64" +} + +func (i *uint64Value) String() string { return fmt.Sprintf("%v", *i) } + +func uint64Conv(sval string) (interface{}, error) { + v, err := strconv.ParseUint(sval, 0, 64) + if err != nil { + return 0, err + } + return uint64(v), nil +} + +// GetUint64 return the uint64 value of a flag with the given name +func (f *FlagSet) GetUint64(name string) (uint64, error) { + val, err := f.getFlagType(name, "uint64", uint64Conv) + if err != nil { + return 0, err + } + return val.(uint64), nil +} + +// Uint64Var defines a uint64 flag with specified name, default value, and usage string. +// The argument p points to a uint64 variable in which to store the value of the flag. +func (f *FlagSet) Uint64Var(p *uint64, name string, value uint64, usage string) { + f.VarP(newUint64Value(value, p), name, "", usage) +} + +// Uint64VarP is like Uint64Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Uint64VarP(p *uint64, name, shorthand string, value uint64, usage string) { + f.VarP(newUint64Value(value, p), name, shorthand, usage) +} + +// Uint64Var defines a uint64 flag with specified name, default value, and usage string. +// The argument p points to a uint64 variable in which to store the value of the flag. +func Uint64Var(p *uint64, name string, value uint64, usage string) { + CommandLine.VarP(newUint64Value(value, p), name, "", usage) +} + +// Uint64VarP is like Uint64Var, but accepts a shorthand letter that can be used after a single dash. +func Uint64VarP(p *uint64, name, shorthand string, value uint64, usage string) { + CommandLine.VarP(newUint64Value(value, p), name, shorthand, usage) +} + +// Uint64 defines a uint64 flag with specified name, default value, and usage string. +// The return value is the address of a uint64 variable that stores the value of the flag. +func (f *FlagSet) Uint64(name string, value uint64, usage string) *uint64 { + p := new(uint64) + f.Uint64VarP(p, name, "", value, usage) + return p +} + +// Uint64P is like Uint64, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Uint64P(name, shorthand string, value uint64, usage string) *uint64 { + p := new(uint64) + f.Uint64VarP(p, name, shorthand, value, usage) + return p +} + +// Uint64 defines a uint64 flag with specified name, default value, and usage string. +// The return value is the address of a uint64 variable that stores the value of the flag. +func Uint64(name string, value uint64, usage string) *uint64 { + return CommandLine.Uint64P(name, "", value, usage) +} + +// Uint64P is like Uint64, but accepts a shorthand letter that can be used after a single dash. +func Uint64P(name, shorthand string, value uint64, usage string) *uint64 { + return CommandLine.Uint64P(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/uint8.go b/vendor/github.com/spf13/pflag/uint8.go new file mode 100644 index 0000000..26db418 --- /dev/null +++ b/vendor/github.com/spf13/pflag/uint8.go @@ -0,0 +1,91 @@ +package pflag + +import ( + "fmt" + "strconv" +) + +// -- uint8 Value +type uint8Value uint8 + +func newUint8Value(val uint8, p *uint8) *uint8Value { + *p = val + return (*uint8Value)(p) +} + +func (i *uint8Value) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 8) + *i = uint8Value(v) + return err +} + +func (i *uint8Value) Type() string { + return "uint8" +} + +func (i *uint8Value) String() string { return fmt.Sprintf("%v", *i) } + +func uint8Conv(sval string) (interface{}, error) { + v, err := strconv.ParseUint(sval, 0, 8) + if err != nil { + return 0, err + } + return uint8(v), nil +} + +// GetUint8 return the uint8 value of a flag with the given name +func (f *FlagSet) GetUint8(name string) (uint8, error) { + val, err := f.getFlagType(name, "uint8", uint8Conv) + if err != nil { + return 0, err + } + return val.(uint8), nil +} + +// Uint8Var defines a uint8 flag with specified name, default value, and usage string. +// The argument p points to a uint8 variable in which to store the value of the flag. +func (f *FlagSet) Uint8Var(p *uint8, name string, value uint8, usage string) { + f.VarP(newUint8Value(value, p), name, "", usage) +} + +// Uint8VarP is like Uint8Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Uint8VarP(p *uint8, name, shorthand string, value uint8, usage string) { + f.VarP(newUint8Value(value, p), name, shorthand, usage) +} + +// Uint8Var defines a uint8 flag with specified name, default value, and usage string. +// The argument p points to a uint8 variable in which to store the value of the flag. +func Uint8Var(p *uint8, name string, value uint8, usage string) { + CommandLine.VarP(newUint8Value(value, p), name, "", usage) +} + +// Uint8VarP is like Uint8Var, but accepts a shorthand letter that can be used after a single dash. +func Uint8VarP(p *uint8, name, shorthand string, value uint8, usage string) { + CommandLine.VarP(newUint8Value(value, p), name, shorthand, usage) +} + +// Uint8 defines a uint8 flag with specified name, default value, and usage string. +// The return value is the address of a uint8 variable that stores the value of the flag. +func (f *FlagSet) Uint8(name string, value uint8, usage string) *uint8 { + p := new(uint8) + f.Uint8VarP(p, name, "", value, usage) + return p +} + +// Uint8P is like Uint8, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Uint8P(name, shorthand string, value uint8, usage string) *uint8 { + p := new(uint8) + f.Uint8VarP(p, name, shorthand, value, usage) + return p +} + +// Uint8 defines a uint8 flag with specified name, default value, and usage string. +// The return value is the address of a uint8 variable that stores the value of the flag. +func Uint8(name string, value uint8, usage string) *uint8 { + return CommandLine.Uint8P(name, "", value, usage) +} + +// Uint8P is like Uint8, but accepts a shorthand letter that can be used after a single dash. +func Uint8P(name, shorthand string, value uint8, usage string) *uint8 { + return CommandLine.Uint8P(name, shorthand, value, usage) +} diff --git a/vendor/github.com/xeipuuv/gojsonpointer/LICENSE-APACHE-2.0.txt b/vendor/github.com/xeipuuv/gojsonpointer/LICENSE-APACHE-2.0.txt new file mode 100644 index 0000000..55ede8a --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonpointer/LICENSE-APACHE-2.0.txt @@ -0,0 +1,202 @@ + + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015 xeipuuv + + 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/vendor/github.com/xeipuuv/gojsonpointer/pointer.go b/vendor/github.com/xeipuuv/gojsonpointer/pointer.go new file mode 100644 index 0000000..6ca317a --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonpointer/pointer.go @@ -0,0 +1,217 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// 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. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonpointer +// repository-desc An implementation of JSON Pointer - Go language +// +// description Main and unique file. +// +// created 25-02-2013 + +package gojsonpointer + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" +) + +const ( + const_empty_pointer = `` + const_pointer_separator = `/` + + const_invalid_start = `JSON pointer must be empty or start with a "` + const_pointer_separator + `"` +) + +type implStruct struct { + mode string // "SET" or "GET" + + inDocument interface{} + + setInValue interface{} + + getOutNode interface{} + getOutKind reflect.Kind + outError error +} + +func NewJsonPointer(jsonPointerString string) (JsonPointer, error) { + + var p JsonPointer + err := p.parse(jsonPointerString) + return p, err + +} + +type JsonPointer struct { + referenceTokens []string +} + +// "Constructor", parses the given string JSON pointer +func (p *JsonPointer) parse(jsonPointerString string) error { + + var err error + + if jsonPointerString != const_empty_pointer { + if !strings.HasPrefix(jsonPointerString, const_pointer_separator) { + err = errors.New(const_invalid_start) + } else { + referenceTokens := strings.Split(jsonPointerString, const_pointer_separator) + for _, referenceToken := range referenceTokens[1:] { + p.referenceTokens = append(p.referenceTokens, referenceToken) + } + } + } + + return err +} + +// Uses the pointer to retrieve a value from a JSON document +func (p *JsonPointer) Get(document interface{}) (interface{}, reflect.Kind, error) { + + is := &implStruct{mode: "GET", inDocument: document} + p.implementation(is) + return is.getOutNode, is.getOutKind, is.outError + +} + +// Uses the pointer to update a value from a JSON document +func (p *JsonPointer) Set(document interface{}, value interface{}) (interface{}, error) { + + is := &implStruct{mode: "SET", inDocument: document, setInValue: value} + p.implementation(is) + return document, is.outError + +} + +// Both Get and Set functions use the same implementation to avoid code duplication +func (p *JsonPointer) implementation(i *implStruct) { + + kind := reflect.Invalid + + // Full document when empty + if len(p.referenceTokens) == 0 { + i.getOutNode = i.inDocument + i.outError = nil + i.getOutKind = kind + i.outError = nil + return + } + + node := i.inDocument + + for ti, token := range p.referenceTokens { + + decodedToken := decodeReferenceToken(token) + isLastToken := ti == len(p.referenceTokens)-1 + + rValue := reflect.ValueOf(node) + kind = rValue.Kind() + + switch kind { + + case reflect.Map: + m := node.(map[string]interface{}) + if _, ok := m[decodedToken]; ok { + node = m[decodedToken] + if isLastToken && i.mode == "SET" { + m[decodedToken] = i.setInValue + } + } else { + i.outError = errors.New(fmt.Sprintf("Object has no key '%s'", token)) + i.getOutKind = kind + i.getOutNode = nil + return + } + + case reflect.Slice: + s := node.([]interface{}) + tokenIndex, err := strconv.Atoi(token) + if err != nil { + i.outError = errors.New(fmt.Sprintf("Invalid array index '%s'", token)) + i.getOutKind = kind + i.getOutNode = nil + return + } + sLength := len(s) + if tokenIndex < 0 || tokenIndex >= sLength { + i.outError = errors.New(fmt.Sprintf("Out of bound array[0,%d] index '%d'", sLength, tokenIndex)) + i.getOutKind = kind + i.getOutNode = nil + return + } + + node = s[tokenIndex] + if isLastToken && i.mode == "SET" { + s[tokenIndex] = i.setInValue + } + + default: + i.outError = errors.New(fmt.Sprintf("Invalid token reference '%s'", token)) + i.getOutKind = kind + i.getOutNode = nil + return + } + + } + + rValue := reflect.ValueOf(node) + kind = rValue.Kind() + + i.getOutNode = node + i.getOutKind = kind + i.outError = nil +} + +// Pointer to string representation function +func (p *JsonPointer) String() string { + + if len(p.referenceTokens) == 0 { + return const_empty_pointer + } + + pointerString := const_pointer_separator + strings.Join(p.referenceTokens, const_pointer_separator) + + return pointerString +} + +// Specific JSON pointer encoding here +// ~0 => ~ +// ~1 => / +// ... and vice versa + +const ( + const_encoded_reference_token_0 = `~0` + const_encoded_reference_token_1 = `~1` + const_decoded_reference_token_0 = `~` + const_decoded_reference_token_1 = `/` +) + +func decodeReferenceToken(token string) string { + step1 := strings.Replace(token, const_encoded_reference_token_1, const_decoded_reference_token_1, -1) + step2 := strings.Replace(step1, const_encoded_reference_token_0, const_decoded_reference_token_0, -1) + return step2 +} + +func encodeReferenceToken(token string) string { + step1 := strings.Replace(token, const_decoded_reference_token_1, const_encoded_reference_token_1, -1) + step2 := strings.Replace(step1, const_decoded_reference_token_0, const_encoded_reference_token_0, -1) + return step2 +} diff --git a/vendor/github.com/xeipuuv/gojsonreference/LICENSE-APACHE-2.0.txt b/vendor/github.com/xeipuuv/gojsonreference/LICENSE-APACHE-2.0.txt new file mode 100644 index 0000000..55ede8a --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonreference/LICENSE-APACHE-2.0.txt @@ -0,0 +1,202 @@ + + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015 xeipuuv + + 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/vendor/github.com/xeipuuv/gojsonreference/reference.go b/vendor/github.com/xeipuuv/gojsonreference/reference.go new file mode 100644 index 0000000..d4d2eca --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonreference/reference.go @@ -0,0 +1,141 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// 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. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonreference +// repository-desc An implementation of JSON Reference - Go language +// +// description Main and unique file. +// +// created 26-02-2013 + +package gojsonreference + +import ( + "errors" + "github.com/xeipuuv/gojsonpointer" + "net/url" + "path/filepath" + "runtime" + "strings" +) + +const ( + const_fragment_char = `#` +) + +func NewJsonReference(jsonReferenceString string) (JsonReference, error) { + + var r JsonReference + err := r.parse(jsonReferenceString) + return r, err + +} + +type JsonReference struct { + referenceUrl *url.URL + referencePointer gojsonpointer.JsonPointer + + HasFullUrl bool + HasUrlPathOnly bool + HasFragmentOnly bool + HasFileScheme bool + HasFullFilePath bool +} + +func (r *JsonReference) GetUrl() *url.URL { + return r.referenceUrl +} + +func (r *JsonReference) GetPointer() *gojsonpointer.JsonPointer { + return &r.referencePointer +} + +func (r *JsonReference) String() string { + + if r.referenceUrl != nil { + return r.referenceUrl.String() + } + + if r.HasFragmentOnly { + return const_fragment_char + r.referencePointer.String() + } + + return r.referencePointer.String() +} + +func (r *JsonReference) IsCanonical() bool { + return (r.HasFileScheme && r.HasFullFilePath) || (!r.HasFileScheme && r.HasFullUrl) +} + +// "Constructor", parses the given string JSON reference +func (r *JsonReference) parse(jsonReferenceString string) (err error) { + + r.referenceUrl, err = url.Parse(jsonReferenceString) + if err != nil { + return + } + refUrl := r.referenceUrl + + if refUrl.Scheme != "" && refUrl.Host != "" { + r.HasFullUrl = true + } else { + if refUrl.Path != "" { + r.HasUrlPathOnly = true + } else if refUrl.RawQuery == "" && refUrl.Fragment != "" { + r.HasFragmentOnly = true + } + } + + r.HasFileScheme = refUrl.Scheme == "file" + if runtime.GOOS == "windows" { + // on Windows, a file URL may have an extra leading slash, and if it + // doesn't then its first component will be treated as the host by the + // Go runtime + if refUrl.Host == "" && strings.HasPrefix(refUrl.Path, "/") { + r.HasFullFilePath = filepath.IsAbs(refUrl.Path[1:]) + } else { + r.HasFullFilePath = filepath.IsAbs(refUrl.Host + refUrl.Path) + } + } else { + r.HasFullFilePath = filepath.IsAbs(refUrl.Path) + } + + // invalid json-pointer error means url has no json-pointer fragment. simply ignore error + r.referencePointer, _ = gojsonpointer.NewJsonPointer(refUrl.Fragment) + + return +} + +// Creates a new reference from a parent and a child +// If the child cannot inherit from the parent, an error is returned +func (r *JsonReference) Inherits(child JsonReference) (*JsonReference, error) { + childUrl := child.GetUrl() + parentUrl := r.GetUrl() + if childUrl == nil { + return nil, errors.New("childUrl is nil!") + } + if parentUrl == nil { + return nil, errors.New("parentUrl is nil!") + } + + ref, err := NewJsonReference(parentUrl.ResolveReference(childUrl).String()) + if err != nil { + return nil, err + } + return &ref, err +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/LICENSE-APACHE-2.0.txt b/vendor/github.com/xeipuuv/gojsonschema/LICENSE-APACHE-2.0.txt new file mode 100644 index 0000000..55ede8a --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/LICENSE-APACHE-2.0.txt @@ -0,0 +1,202 @@ + + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015 xeipuuv + + 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/vendor/github.com/xeipuuv/gojsonschema/errors.go b/vendor/github.com/xeipuuv/gojsonschema/errors.go new file mode 100644 index 0000000..5146cbb --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/errors.go @@ -0,0 +1,242 @@ +package gojsonschema + +import ( + "fmt" + "strings" +) + +type ( + // RequiredError. ErrorDetails: property string + RequiredError struct { + ResultErrorFields + } + + // InvalidTypeError. ErrorDetails: expected, given + InvalidTypeError struct { + ResultErrorFields + } + + // NumberAnyOfError. ErrorDetails: - + NumberAnyOfError struct { + ResultErrorFields + } + + // NumberOneOfError. ErrorDetails: - + NumberOneOfError struct { + ResultErrorFields + } + + // NumberAllOfError. ErrorDetails: - + NumberAllOfError struct { + ResultErrorFields + } + + // NumberNotError. ErrorDetails: - + NumberNotError struct { + ResultErrorFields + } + + // MissingDependencyError. ErrorDetails: dependency + MissingDependencyError struct { + ResultErrorFields + } + + // InternalError. ErrorDetails: error + InternalError struct { + ResultErrorFields + } + + // EnumError. ErrorDetails: allowed + EnumError struct { + ResultErrorFields + } + + // ArrayNoAdditionalItemsError. ErrorDetails: - + ArrayNoAdditionalItemsError struct { + ResultErrorFields + } + + // ArrayMinItemsError. ErrorDetails: min + ArrayMinItemsError struct { + ResultErrorFields + } + + // ArrayMaxItemsError. ErrorDetails: max + ArrayMaxItemsError struct { + ResultErrorFields + } + + // ItemsMustBeUniqueError. ErrorDetails: type + ItemsMustBeUniqueError struct { + ResultErrorFields + } + + // ArrayMinPropertiesError. ErrorDetails: min + ArrayMinPropertiesError struct { + ResultErrorFields + } + + // ArrayMaxPropertiesError. ErrorDetails: max + ArrayMaxPropertiesError struct { + ResultErrorFields + } + + // AdditionalPropertyNotAllowedError. ErrorDetails: property + AdditionalPropertyNotAllowedError struct { + ResultErrorFields + } + + // InvalidPropertyPatternError. ErrorDetails: property, pattern + InvalidPropertyPatternError struct { + ResultErrorFields + } + + // StringLengthGTEError. ErrorDetails: min + StringLengthGTEError struct { + ResultErrorFields + } + + // StringLengthLTEError. ErrorDetails: max + StringLengthLTEError struct { + ResultErrorFields + } + + // DoesNotMatchPatternError. ErrorDetails: pattern + DoesNotMatchPatternError struct { + ResultErrorFields + } + + // DoesNotMatchFormatError. ErrorDetails: format + DoesNotMatchFormatError struct { + ResultErrorFields + } + + // MultipleOfError. ErrorDetails: multiple + MultipleOfError struct { + ResultErrorFields + } + + // NumberGTEError. ErrorDetails: min + NumberGTEError struct { + ResultErrorFields + } + + // NumberGTError. ErrorDetails: min + NumberGTError struct { + ResultErrorFields + } + + // NumberLTEError. ErrorDetails: max + NumberLTEError struct { + ResultErrorFields + } + + // NumberLTError. ErrorDetails: max + NumberLTError struct { + ResultErrorFields + } +) + +// newError takes a ResultError type and sets the type, context, description, details, value, and field +func newError(err ResultError, context *jsonContext, value interface{}, locale locale, details ErrorDetails) { + var t string + var d string + switch err.(type) { + case *RequiredError: + t = "required" + d = locale.Required() + case *InvalidTypeError: + t = "invalid_type" + d = locale.InvalidType() + case *NumberAnyOfError: + t = "number_any_of" + d = locale.NumberAnyOf() + case *NumberOneOfError: + t = "number_one_of" + d = locale.NumberOneOf() + case *NumberAllOfError: + t = "number_all_of" + d = locale.NumberAllOf() + case *NumberNotError: + t = "number_not" + d = locale.NumberNot() + case *MissingDependencyError: + t = "missing_dependency" + d = locale.MissingDependency() + case *InternalError: + t = "internal" + d = locale.Internal() + case *EnumError: + t = "enum" + d = locale.Enum() + case *ArrayNoAdditionalItemsError: + t = "array_no_additional_items" + d = locale.ArrayNoAdditionalItems() + case *ArrayMinItemsError: + t = "array_min_items" + d = locale.ArrayMinItems() + case *ArrayMaxItemsError: + t = "array_max_items" + d = locale.ArrayMaxItems() + case *ItemsMustBeUniqueError: + t = "unique" + d = locale.Unique() + case *ArrayMinPropertiesError: + t = "array_min_properties" + d = locale.ArrayMinProperties() + case *ArrayMaxPropertiesError: + t = "array_max_properties" + d = locale.ArrayMaxProperties() + case *AdditionalPropertyNotAllowedError: + t = "additional_property_not_allowed" + d = locale.AdditionalPropertyNotAllowed() + case *InvalidPropertyPatternError: + t = "invalid_property_pattern" + d = locale.InvalidPropertyPattern() + case *StringLengthGTEError: + t = "string_gte" + d = locale.StringGTE() + case *StringLengthLTEError: + t = "string_lte" + d = locale.StringLTE() + case *DoesNotMatchPatternError: + t = "pattern" + d = locale.DoesNotMatchPattern() + case *DoesNotMatchFormatError: + t = "format" + d = locale.DoesNotMatchFormat() + case *MultipleOfError: + t = "multiple_of" + d = locale.MultipleOf() + case *NumberGTEError: + t = "number_gte" + d = locale.NumberGTE() + case *NumberGTError: + t = "number_gt" + d = locale.NumberGT() + case *NumberLTEError: + t = "number_lte" + d = locale.NumberLTE() + case *NumberLTError: + t = "number_lt" + d = locale.NumberLT() + } + + err.SetType(t) + err.SetContext(context) + err.SetValue(value) + err.SetDetails(details) + details["field"] = err.Field() + err.SetDescription(formatErrorDescription(d, details)) +} + +// formatErrorDescription takes a string in this format: %field% is required +// and converts it to a string with replacements. The fields come from +// the ErrorDetails struct and vary for each type of error. +func formatErrorDescription(s string, details ErrorDetails) string { + for name, val := range details { + s = strings.Replace(s, "%"+strings.ToLower(name)+"%", fmt.Sprintf("%v", val), -1) + } + + return s +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go b/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go new file mode 100644 index 0000000..b91c360 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/format_checkers.go @@ -0,0 +1,178 @@ +package gojsonschema + +import ( + "net" + "net/url" + "reflect" + "regexp" + "strings" + "time" +) + +type ( + // FormatChecker is the interface all formatters added to FormatCheckerChain must implement + FormatChecker interface { + IsFormat(input string) bool + } + + // FormatCheckerChain holds the formatters + FormatCheckerChain struct { + formatters map[string]FormatChecker + } + + // EmailFormatter verifies email address formats + EmailFormatChecker struct{} + + // IPV4FormatChecker verifies IP addresses in the ipv4 format + IPV4FormatChecker struct{} + + // IPV6FormatChecker verifies IP addresses in the ipv6 format + IPV6FormatChecker struct{} + + // DateTimeFormatChecker verifies date/time formats per RFC3339 5.6 + // + // Valid formats: + // Partial Time: HH:MM:SS + // Full Date: YYYY-MM-DD + // Full Time: HH:MM:SSZ-07:00 + // Date Time: YYYY-MM-DDTHH:MM:SSZ-0700 + // + // Where + // YYYY = 4DIGIT year + // MM = 2DIGIT month ; 01-12 + // DD = 2DIGIT day-month ; 01-28, 01-29, 01-30, 01-31 based on month/year + // HH = 2DIGIT hour ; 00-23 + // MM = 2DIGIT ; 00-59 + // SS = 2DIGIT ; 00-58, 00-60 based on leap second rules + // T = Literal + // Z = Literal + // + // Note: Nanoseconds are also suported in all formats + // + // http://tools.ietf.org/html/rfc3339#section-5.6 + DateTimeFormatChecker struct{} + + // URIFormatCheckers validates a URI with a valid Scheme per RFC3986 + URIFormatChecker struct{} + + // HostnameFormatChecker validates a hostname is in the correct format + HostnameFormatChecker struct{} + + // UUIDFormatChecker validates a UUID is in the correct format + UUIDFormatChecker struct{} +) + +var ( + // Formatters holds the valid formatters, and is a public variable + // so library users can add custom formatters + FormatCheckers = FormatCheckerChain{ + formatters: map[string]FormatChecker{ + "date-time": DateTimeFormatChecker{}, + "hostname": HostnameFormatChecker{}, + "email": EmailFormatChecker{}, + "ipv4": IPV4FormatChecker{}, + "ipv6": IPV6FormatChecker{}, + "uri": URIFormatChecker{}, + "uuid": UUIDFormatChecker{}, + }, + } + + // Regex credit: https://github.com/asaskevich/govalidator + rxEmail = regexp.MustCompile("^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$") + + // Regex credit: https://www.socketloop.com/tutorials/golang-validate-hostname + rxHostname = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`) + + rxUUID = regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$") +) + +// Add adds a FormatChecker to the FormatCheckerChain +// The name used will be the value used for the format key in your json schema +func (c *FormatCheckerChain) Add(name string, f FormatChecker) *FormatCheckerChain { + c.formatters[name] = f + + return c +} + +// Remove deletes a FormatChecker from the FormatCheckerChain (if it exists) +func (c *FormatCheckerChain) Remove(name string) *FormatCheckerChain { + delete(c.formatters, name) + + return c +} + +// Has checks to see if the FormatCheckerChain holds a FormatChecker with the given name +func (c *FormatCheckerChain) Has(name string) bool { + _, ok := c.formatters[name] + + return ok +} + +// IsFormat will check an input against a FormatChecker with the given name +// to see if it is the correct format +func (c *FormatCheckerChain) IsFormat(name string, input interface{}) bool { + f, ok := c.formatters[name] + + if !ok { + return false + } + + if !isKind(input, reflect.String) { + return false + } + + inputString := input.(string) + + return f.IsFormat(inputString) +} + +func (f EmailFormatChecker) IsFormat(input string) bool { + return rxEmail.MatchString(input) +} + +// Credit: https://github.com/asaskevich/govalidator +func (f IPV4FormatChecker) IsFormat(input string) bool { + ip := net.ParseIP(input) + return ip != nil && strings.Contains(input, ".") +} + +// Credit: https://github.com/asaskevich/govalidator +func (f IPV6FormatChecker) IsFormat(input string) bool { + ip := net.ParseIP(input) + return ip != nil && strings.Contains(input, ":") +} + +func (f DateTimeFormatChecker) IsFormat(input string) bool { + formats := []string{ + "15:04:05", + "15:04:05Z07:00", + "2006-01-02", + time.RFC3339, + time.RFC3339Nano, + } + + for _, format := range formats { + if _, err := time.Parse(format, input); err == nil { + return true + } + } + + return false +} + +func (f URIFormatChecker) IsFormat(input string) bool { + u, err := url.Parse(input) + if err != nil || u.Scheme == "" { + return false + } + + return true +} + +func (f HostnameFormatChecker) IsFormat(input string) bool { + return rxHostname.MatchString(input) && len(input) < 256 +} + +func (f UUIDFormatChecker) IsFormat(input string) bool { + return rxUUID.MatchString(input) +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/internalLog.go b/vendor/github.com/xeipuuv/gojsonschema/internalLog.go new file mode 100644 index 0000000..4ef7a8d --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/internalLog.go @@ -0,0 +1,37 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// 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. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Very simple log wrapper. +// Used for debugging/testing purposes. +// +// created 01-01-2015 + +package gojsonschema + +import ( + "log" +) + +const internalLogEnabled = false + +func internalLog(format string, v ...interface{}) { + log.Printf(format, v...) +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/jsonContext.go b/vendor/github.com/xeipuuv/gojsonschema/jsonContext.go new file mode 100644 index 0000000..fcc8d9d --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/jsonContext.go @@ -0,0 +1,72 @@ +// Copyright 2013 MongoDB, Inc. +// +// 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. + +// author tolsen +// author-github https://github.com/tolsen +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Implements a persistent (immutable w/ shared structure) singly-linked list of strings for the purpose of storing a json context +// +// created 04-09-2013 + +package gojsonschema + +import "bytes" + +// jsonContext implements a persistent linked-list of strings +type jsonContext struct { + head string + tail *jsonContext +} + +func newJsonContext(head string, tail *jsonContext) *jsonContext { + return &jsonContext{head, tail} +} + +// String displays the context in reverse. +// This plays well with the data structure's persistent nature with +// Cons and a json document's tree structure. +func (c *jsonContext) String(del ...string) string { + byteArr := make([]byte, 0, c.stringLen()) + buf := bytes.NewBuffer(byteArr) + c.writeStringToBuffer(buf, del) + + return buf.String() +} + +func (c *jsonContext) stringLen() int { + length := 0 + if c.tail != nil { + length = c.tail.stringLen() + 1 // add 1 for "." + } + + length += len(c.head) + return length +} + +func (c *jsonContext) writeStringToBuffer(buf *bytes.Buffer, del []string) { + if c.tail != nil { + c.tail.writeStringToBuffer(buf, del) + + if len(del) > 0 { + buf.WriteString(del[0]) + } else { + buf.WriteString(".") + } + } + + buf.WriteString(c.head) +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/jsonLoader.go b/vendor/github.com/xeipuuv/gojsonschema/jsonLoader.go new file mode 100644 index 0000000..0b40574 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/jsonLoader.go @@ -0,0 +1,314 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// 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. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Different strategies to load JSON files. +// Includes References (file and HTTP), JSON strings and Go types. +// +// created 01-02-2015 + +package gojsonschema + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/xeipuuv/gojsonreference" +) + +var osFS = osFileSystem(os.Open) + +// JSON loader interface + +type JSONLoader interface { + jsonSource() interface{} + loadJSON() (interface{}, error) + loadSchema() (*Schema, error) +} + +// osFileSystem is a functional wrapper for os.Open that implements http.FileSystem. +type osFileSystem func(string) (*os.File, error) + +func (o osFileSystem) Open(name string) (http.File, error) { + return o(name) +} + +// JSON Reference loader +// references are used to load JSONs from files and HTTP + +type jsonReferenceLoader struct { + fs http.FileSystem + source string +} + +func (l *jsonReferenceLoader) jsonSource() interface{} { + return l.source +} + +// NewReferenceLoader returns a JSON reference loader using the given source and the local OS file system. +func NewReferenceLoader(source string) *jsonReferenceLoader { + return &jsonReferenceLoader{ + fs: osFS, + source: source, + } +} + +// NewReferenceLoaderFileSystem returns a JSON reference loader using the given source and file system. +func NewReferenceLoaderFileSystem(source string, fs http.FileSystem) *jsonReferenceLoader { + return &jsonReferenceLoader{ + fs: fs, + source: source, + } +} + +func (l *jsonReferenceLoader) loadJSON() (interface{}, error) { + + var err error + + reference, err := gojsonreference.NewJsonReference(l.jsonSource().(string)) + if err != nil { + return nil, err + } + + refToUrl := reference + refToUrl.GetUrl().Fragment = "" + + var document interface{} + + if reference.HasFileScheme { + + filename := strings.Replace(refToUrl.String(), "file://", "", -1) + if runtime.GOOS == "windows" { + // on Windows, a file URL may have an extra leading slash, use slashes + // instead of backslashes, and have spaces escaped + if strings.HasPrefix(filename, "/") { + filename = filename[1:] + } + filename = filepath.FromSlash(filename) + filename = strings.Replace(filename, "%20", " ", -1) + } + + document, err = l.loadFromFile(filename) + if err != nil { + return nil, err + } + + } else { + + document, err = l.loadFromHTTP(refToUrl.String()) + if err != nil { + return nil, err + } + + } + + return document, nil + +} + +func (l *jsonReferenceLoader) loadSchema() (*Schema, error) { + + var err error + + d := Schema{} + d.pool = newSchemaPool(l.fs) + d.referencePool = newSchemaReferencePool() + + d.documentReference, err = gojsonreference.NewJsonReference(l.jsonSource().(string)) + if err != nil { + return nil, err + } + + spd, err := d.pool.GetDocument(d.documentReference) + if err != nil { + return nil, err + } + + err = d.parse(spd.Document) + if err != nil { + return nil, err + } + + return &d, nil + +} + +func (l *jsonReferenceLoader) loadFromHTTP(address string) (interface{}, error) { + + resp, err := http.Get(address) + if err != nil { + return nil, err + } + + // must return HTTP Status 200 OK + if resp.StatusCode != http.StatusOK { + return nil, errors.New(formatErrorDescription(Locale.httpBadStatus(), ErrorDetails{"status": resp.Status})) + } + + bodyBuff, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + return decodeJsonUsingNumber(bytes.NewReader(bodyBuff)) + +} + +func (l *jsonReferenceLoader) loadFromFile(path string) (interface{}, error) { + f, err := l.fs.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + bodyBuff, err := ioutil.ReadAll(f) + if err != nil { + return nil, err + } + + return decodeJsonUsingNumber(bytes.NewReader(bodyBuff)) + +} + +// JSON string loader + +type jsonStringLoader struct { + source string +} + +func (l *jsonStringLoader) jsonSource() interface{} { + return l.source +} + +func NewStringLoader(source string) *jsonStringLoader { + return &jsonStringLoader{source: source} +} + +func (l *jsonStringLoader) loadJSON() (interface{}, error) { + + return decodeJsonUsingNumber(strings.NewReader(l.jsonSource().(string))) + +} + +func (l *jsonStringLoader) loadSchema() (*Schema, error) { + + var err error + + document, err := l.loadJSON() + if err != nil { + return nil, err + } + + d := Schema{} + d.pool = newSchemaPool(osFS) + d.referencePool = newSchemaReferencePool() + d.documentReference, err = gojsonreference.NewJsonReference("#") + d.pool.SetStandaloneDocument(document) + if err != nil { + return nil, err + } + + err = d.parse(document) + if err != nil { + return nil, err + } + + return &d, nil + +} + +// JSON Go (types) loader +// used to load JSONs from the code as maps, interface{}, structs ... + +type jsonGoLoader struct { + source interface{} +} + +func (l *jsonGoLoader) jsonSource() interface{} { + return l.source +} + +func NewGoLoader(source interface{}) *jsonGoLoader { + return &jsonGoLoader{source: source} +} + +func (l *jsonGoLoader) loadJSON() (interface{}, error) { + + // convert it to a compliant JSON first to avoid types "mismatches" + + jsonBytes, err := json.Marshal(l.jsonSource()) + if err != nil { + return nil, err + } + + return decodeJsonUsingNumber(bytes.NewReader(jsonBytes)) + +} + +func (l *jsonGoLoader) loadSchema() (*Schema, error) { + + var err error + + document, err := l.loadJSON() + if err != nil { + return nil, err + } + + d := Schema{} + d.pool = newSchemaPool(osFS) + d.referencePool = newSchemaReferencePool() + d.documentReference, err = gojsonreference.NewJsonReference("#") + d.pool.SetStandaloneDocument(document) + if err != nil { + return nil, err + } + + err = d.parse(document) + if err != nil { + return nil, err + } + + return &d, nil + +} + +func decodeJsonUsingNumber(r io.Reader) (interface{}, error) { + + var document interface{} + + decoder := json.NewDecoder(r) + decoder.UseNumber() + + err := decoder.Decode(&document) + if err != nil { + return nil, err + } + + return document, nil + +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/locales.go b/vendor/github.com/xeipuuv/gojsonschema/locales.go new file mode 100644 index 0000000..de05d60 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/locales.go @@ -0,0 +1,275 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// 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. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Contains const string and messages. +// +// created 01-01-2015 + +package gojsonschema + +type ( + // locale is an interface for definining custom error strings + locale interface { + Required() string + InvalidType() string + NumberAnyOf() string + NumberOneOf() string + NumberAllOf() string + NumberNot() string + MissingDependency() string + Internal() string + Enum() string + ArrayNoAdditionalItems() string + ArrayMinItems() string + ArrayMaxItems() string + Unique() string + ArrayMinProperties() string + ArrayMaxProperties() string + AdditionalPropertyNotAllowed() string + InvalidPropertyPattern() string + StringGTE() string + StringLTE() string + DoesNotMatchPattern() string + DoesNotMatchFormat() string + MultipleOf() string + NumberGTE() string + NumberGT() string + NumberLTE() string + NumberLT() string + + // Schema validations + RegexPattern() string + GreaterThanZero() string + MustBeOfA() string + MustBeOfAn() string + CannotBeUsedWithout() string + CannotBeGT() string + MustBeOfType() string + MustBeValidRegex() string + MustBeValidFormat() string + MustBeGTEZero() string + KeyCannotBeGreaterThan() string + KeyItemsMustBeOfType() string + KeyItemsMustBeUnique() string + ReferenceMustBeCanonical() string + NotAValidType() string + Duplicated() string + httpBadStatus() string + + // ErrorFormat + ErrorFormat() string + } + + // DefaultLocale is the default locale for this package + DefaultLocale struct{} +) + +func (l DefaultLocale) Required() string { + return `%property% is required` +} + +func (l DefaultLocale) InvalidType() string { + return `Invalid type. Expected: %expected%, given: %given%` +} + +func (l DefaultLocale) NumberAnyOf() string { + return `Must validate at least one schema (anyOf)` +} + +func (l DefaultLocale) NumberOneOf() string { + return `Must validate one and only one schema (oneOf)` +} + +func (l DefaultLocale) NumberAllOf() string { + return `Must validate all the schemas (allOf)` +} + +func (l DefaultLocale) NumberNot() string { + return `Must not validate the schema (not)` +} + +func (l DefaultLocale) MissingDependency() string { + return `Has a dependency on %dependency%` +} + +func (l DefaultLocale) Internal() string { + return `Internal Error %error%` +} + +func (l DefaultLocale) Enum() string { + return `%field% must be one of the following: %allowed%` +} + +func (l DefaultLocale) ArrayNoAdditionalItems() string { + return `No additional items allowed on array` +} + +func (l DefaultLocale) ArrayMinItems() string { + return `Array must have at least %min% items` +} + +func (l DefaultLocale) ArrayMaxItems() string { + return `Array must have at most %max% items` +} + +func (l DefaultLocale) Unique() string { + return `%type% items must be unique` +} + +func (l DefaultLocale) ArrayMinProperties() string { + return `Must have at least %min% properties` +} + +func (l DefaultLocale) ArrayMaxProperties() string { + return `Must have at most %max% properties` +} + +func (l DefaultLocale) AdditionalPropertyNotAllowed() string { + return `Additional property %property% is not allowed` +} + +func (l DefaultLocale) InvalidPropertyPattern() string { + return `Property "%property%" does not match pattern %pattern%` +} + +func (l DefaultLocale) StringGTE() string { + return `String length must be greater than or equal to %min%` +} + +func (l DefaultLocale) StringLTE() string { + return `String length must be less than or equal to %max%` +} + +func (l DefaultLocale) DoesNotMatchPattern() string { + return `Does not match pattern '%pattern%'` +} + +func (l DefaultLocale) DoesNotMatchFormat() string { + return `Does not match format '%format%'` +} + +func (l DefaultLocale) MultipleOf() string { + return `Must be a multiple of %multiple%` +} + +func (l DefaultLocale) NumberGTE() string { + return `Must be greater than or equal to %min%` +} + +func (l DefaultLocale) NumberGT() string { + return `Must be greater than %min%` +} + +func (l DefaultLocale) NumberLTE() string { + return `Must be less than or equal to %max%` +} + +func (l DefaultLocale) NumberLT() string { + return `Must be less than %max%` +} + +// Schema validators +func (l DefaultLocale) RegexPattern() string { + return `Invalid regex pattern '%pattern%'` +} + +func (l DefaultLocale) GreaterThanZero() string { + return `%number% must be strictly greater than 0` +} + +func (l DefaultLocale) MustBeOfA() string { + return `%x% must be of a %y%` +} + +func (l DefaultLocale) MustBeOfAn() string { + return `%x% must be of an %y%` +} + +func (l DefaultLocale) CannotBeUsedWithout() string { + return `%x% cannot be used without %y%` +} + +func (l DefaultLocale) CannotBeGT() string { + return `%x% cannot be greater than %y%` +} + +func (l DefaultLocale) MustBeOfType() string { + return `%key% must be of type %type%` +} + +func (l DefaultLocale) MustBeValidRegex() string { + return `%key% must be a valid regex` +} + +func (l DefaultLocale) MustBeValidFormat() string { + return `%key% must be a valid format %given%` +} + +func (l DefaultLocale) MustBeGTEZero() string { + return `%key% must be greater than or equal to 0` +} + +func (l DefaultLocale) KeyCannotBeGreaterThan() string { + return `%key% cannot be greater than %y%` +} + +func (l DefaultLocale) KeyItemsMustBeOfType() string { + return `%key% items must be %type%` +} + +func (l DefaultLocale) KeyItemsMustBeUnique() string { + return `%key% items must be unique` +} + +func (l DefaultLocale) ReferenceMustBeCanonical() string { + return `Reference %reference% must be canonical` +} + +func (l DefaultLocale) NotAValidType() string { + return `%type% is not a valid type -- ` +} + +func (l DefaultLocale) Duplicated() string { + return `%type% type is duplicated` +} + +func (l DefaultLocale) httpBadStatus() string { + return `Could not read schema from HTTP, response status is %status%` +} + +// Replacement options: field, description, context, value +func (l DefaultLocale) ErrorFormat() string { + return `%field%: %description%` +} + +const ( + STRING_NUMBER = "number" + STRING_ARRAY_OF_STRINGS = "array of strings" + STRING_ARRAY_OF_SCHEMAS = "array of schemas" + STRING_SCHEMA = "schema" + STRING_SCHEMA_OR_ARRAY_OF_STRINGS = "schema or array of strings" + STRING_PROPERTIES = "properties" + STRING_DEPENDENCY = "dependency" + STRING_PROPERTY = "property" + STRING_UNDEFINED = "undefined" + STRING_CONTEXT_ROOT = "(root)" + STRING_ROOT_SCHEMA_PROPERTY = "(root)" +) diff --git a/vendor/github.com/xeipuuv/gojsonschema/result.go b/vendor/github.com/xeipuuv/gojsonschema/result.go new file mode 100644 index 0000000..c6d3375 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/result.go @@ -0,0 +1,171 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// 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. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Result and ResultError implementations. +// +// created 01-01-2015 + +package gojsonschema + +import ( + "fmt" + "strings" +) + +type ( + // ErrorDetails is a map of details specific to each error. + // While the values will vary, every error will contain a "field" value + ErrorDetails map[string]interface{} + + // ResultError is the interface that library errors must implement + ResultError interface { + Field() string + SetType(string) + Type() string + SetContext(*jsonContext) + Context() *jsonContext + SetDescription(string) + Description() string + SetValue(interface{}) + Value() interface{} + SetDetails(ErrorDetails) + Details() ErrorDetails + } + + // ResultErrorFields holds the fields for each ResultError implementation. + // ResultErrorFields implements the ResultError interface, so custom errors + // can be defined by just embedding this type + ResultErrorFields struct { + errorType string // A string with the type of error (i.e. invalid_type) + context *jsonContext // Tree like notation of the part that failed the validation. ex (root).a.b ... + description string // A human readable error message + value interface{} // Value given by the JSON file that is the source of the error + details ErrorDetails + } + + Result struct { + errors []ResultError + // Scores how well the validation matched. Useful in generating + // better error messages for anyOf and oneOf. + score int + } +) + +// Field outputs the field name without the root context +// i.e. firstName or person.firstName instead of (root).firstName or (root).person.firstName +func (v *ResultErrorFields) Field() string { + if p, ok := v.Details()["property"]; ok { + if str, isString := p.(string); isString { + return str + } + } + + return strings.TrimPrefix(v.context.String(), STRING_ROOT_SCHEMA_PROPERTY+".") +} + +func (v *ResultErrorFields) SetType(errorType string) { + v.errorType = errorType +} + +func (v *ResultErrorFields) Type() string { + return v.errorType +} + +func (v *ResultErrorFields) SetContext(context *jsonContext) { + v.context = context +} + +func (v *ResultErrorFields) Context() *jsonContext { + return v.context +} + +func (v *ResultErrorFields) SetDescription(description string) { + v.description = description +} + +func (v *ResultErrorFields) Description() string { + return v.description +} + +func (v *ResultErrorFields) SetValue(value interface{}) { + v.value = value +} + +func (v *ResultErrorFields) Value() interface{} { + return v.value +} + +func (v *ResultErrorFields) SetDetails(details ErrorDetails) { + v.details = details +} + +func (v *ResultErrorFields) Details() ErrorDetails { + return v.details +} + +func (v ResultErrorFields) String() string { + // as a fallback, the value is displayed go style + valueString := fmt.Sprintf("%v", v.value) + + // marshal the go value value to json + if v.value == nil { + valueString = TYPE_NULL + } else { + if vs, err := marshalToJsonString(v.value); err == nil { + if vs == nil { + valueString = TYPE_NULL + } else { + valueString = *vs + } + } + } + + return formatErrorDescription(Locale.ErrorFormat(), ErrorDetails{ + "context": v.context.String(), + "description": v.description, + "value": valueString, + "field": v.Field(), + }) +} + +func (v *Result) Valid() bool { + return len(v.errors) == 0 +} + +func (v *Result) Errors() []ResultError { + return v.errors +} + +func (v *Result) addError(err ResultError, context *jsonContext, value interface{}, details ErrorDetails) { + newError(err, context, value, Locale, details) + v.errors = append(v.errors, err) + v.score -= 2 // results in a net -1 when added to the +1 we get at the end of the validation function +} + +// Used to copy errors from a sub-schema to the main one +func (v *Result) mergeErrors(otherResult *Result) { + v.errors = append(v.errors, otherResult.Errors()...) + v.score += otherResult.score +} + +func (v *Result) incrementScore() { + v.score++ +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/schema.go b/vendor/github.com/xeipuuv/gojsonschema/schema.go new file mode 100644 index 0000000..577c786 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/schema.go @@ -0,0 +1,908 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// 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. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Defines Schema, the main entry to every subSchema. +// Contains the parsing logic and error checking. +// +// created 26-02-2013 + +package gojsonschema + +import ( + // "encoding/json" + "errors" + "reflect" + "regexp" + + "github.com/xeipuuv/gojsonreference" +) + +var ( + // Locale is the default locale to use + // Library users can overwrite with their own implementation + Locale locale = DefaultLocale{} +) + +func NewSchema(l JSONLoader) (*Schema, error) { + return l.loadSchema() +} + +type Schema struct { + documentReference gojsonreference.JsonReference + rootSchema *subSchema + pool *schemaPool + referencePool *schemaReferencePool +} + +func (d *Schema) parse(document interface{}) error { + d.rootSchema = &subSchema{property: STRING_ROOT_SCHEMA_PROPERTY} + return d.parseSchema(document, d.rootSchema) +} + +func (d *Schema) SetRootSchemaName(name string) { + d.rootSchema.property = name +} + +// Parses a subSchema +// +// Pretty long function ( sorry :) )... but pretty straight forward, repetitive and boring +// Not much magic involved here, most of the job is to validate the key names and their values, +// then the values are copied into subSchema struct +// +func (d *Schema) parseSchema(documentNode interface{}, currentSchema *subSchema) error { + + if !isKind(documentNode, reflect.Map) { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_OBJECT, + "given": STRING_SCHEMA, + }, + )) + } + + m := documentNode.(map[string]interface{}) + + if currentSchema == d.rootSchema { + currentSchema.ref = &d.documentReference + } + + // $subSchema + if existsMapKey(m, KEY_SCHEMA) { + if !isKind(m[KEY_SCHEMA], reflect.String) { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_STRING, + "given": KEY_SCHEMA, + }, + )) + } + schemaRef := m[KEY_SCHEMA].(string) + schemaReference, err := gojsonreference.NewJsonReference(schemaRef) + currentSchema.subSchema = &schemaReference + if err != nil { + return err + } + } + + // $ref + if existsMapKey(m, KEY_REF) && !isKind(m[KEY_REF], reflect.String) { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_STRING, + "given": KEY_REF, + }, + )) + } + if k, ok := m[KEY_REF].(string); ok { + + if sch, ok := d.referencePool.Get(currentSchema.ref.String() + k); ok { + + currentSchema.refSchema = sch + + } else { + + var err error + err = d.parseReference(documentNode, currentSchema, k) + if err != nil { + return err + } + + return nil + } + } + + // definitions + if existsMapKey(m, KEY_DEFINITIONS) { + if isKind(m[KEY_DEFINITIONS], reflect.Map) { + currentSchema.definitions = make(map[string]*subSchema) + for dk, dv := range m[KEY_DEFINITIONS].(map[string]interface{}) { + if isKind(dv, reflect.Map) { + newSchema := &subSchema{property: KEY_DEFINITIONS, parent: currentSchema, ref: currentSchema.ref} + currentSchema.definitions[dk] = newSchema + err := d.parseSchema(dv, newSchema) + if err != nil { + return errors.New(err.Error()) + } + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": STRING_ARRAY_OF_SCHEMAS, + "given": KEY_DEFINITIONS, + }, + )) + } + } + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": STRING_ARRAY_OF_SCHEMAS, + "given": KEY_DEFINITIONS, + }, + )) + } + + } + + // id + if existsMapKey(m, KEY_ID) && !isKind(m[KEY_ID], reflect.String) { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_STRING, + "given": KEY_ID, + }, + )) + } + if k, ok := m[KEY_ID].(string); ok { + currentSchema.id = &k + } + + // title + if existsMapKey(m, KEY_TITLE) && !isKind(m[KEY_TITLE], reflect.String) { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_STRING, + "given": KEY_TITLE, + }, + )) + } + if k, ok := m[KEY_TITLE].(string); ok { + currentSchema.title = &k + } + + // description + if existsMapKey(m, KEY_DESCRIPTION) && !isKind(m[KEY_DESCRIPTION], reflect.String) { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_STRING, + "given": KEY_DESCRIPTION, + }, + )) + } + if k, ok := m[KEY_DESCRIPTION].(string); ok { + currentSchema.description = &k + } + + // type + if existsMapKey(m, KEY_TYPE) { + if isKind(m[KEY_TYPE], reflect.String) { + if k, ok := m[KEY_TYPE].(string); ok { + err := currentSchema.types.Add(k) + if err != nil { + return err + } + } + } else { + if isKind(m[KEY_TYPE], reflect.Slice) { + arrayOfTypes := m[KEY_TYPE].([]interface{}) + for _, typeInArray := range arrayOfTypes { + if reflect.ValueOf(typeInArray).Kind() != reflect.String { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_STRING + "/" + STRING_ARRAY_OF_STRINGS, + "given": KEY_TYPE, + }, + )) + } else { + currentSchema.types.Add(typeInArray.(string)) + } + } + + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_STRING + "/" + STRING_ARRAY_OF_STRINGS, + "given": KEY_TYPE, + }, + )) + } + } + } + + // properties + if existsMapKey(m, KEY_PROPERTIES) { + err := d.parseProperties(m[KEY_PROPERTIES], currentSchema) + if err != nil { + return err + } + } + + // additionalProperties + if existsMapKey(m, KEY_ADDITIONAL_PROPERTIES) { + if isKind(m[KEY_ADDITIONAL_PROPERTIES], reflect.Bool) { + currentSchema.additionalProperties = m[KEY_ADDITIONAL_PROPERTIES].(bool) + } else if isKind(m[KEY_ADDITIONAL_PROPERTIES], reflect.Map) { + newSchema := &subSchema{property: KEY_ADDITIONAL_PROPERTIES, parent: currentSchema, ref: currentSchema.ref} + currentSchema.additionalProperties = newSchema + err := d.parseSchema(m[KEY_ADDITIONAL_PROPERTIES], newSchema) + if err != nil { + return errors.New(err.Error()) + } + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_BOOLEAN + "/" + STRING_SCHEMA, + "given": KEY_ADDITIONAL_PROPERTIES, + }, + )) + } + } + + // patternProperties + if existsMapKey(m, KEY_PATTERN_PROPERTIES) { + if isKind(m[KEY_PATTERN_PROPERTIES], reflect.Map) { + patternPropertiesMap := m[KEY_PATTERN_PROPERTIES].(map[string]interface{}) + if len(patternPropertiesMap) > 0 { + currentSchema.patternProperties = make(map[string]*subSchema) + for k, v := range patternPropertiesMap { + _, err := regexp.MatchString(k, "") + if err != nil { + return errors.New(formatErrorDescription( + Locale.RegexPattern(), + ErrorDetails{"pattern": k}, + )) + } + newSchema := &subSchema{property: k, parent: currentSchema, ref: currentSchema.ref} + err = d.parseSchema(v, newSchema) + if err != nil { + return errors.New(err.Error()) + } + currentSchema.patternProperties[k] = newSchema + } + } + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": STRING_SCHEMA, + "given": KEY_PATTERN_PROPERTIES, + }, + )) + } + } + + // dependencies + if existsMapKey(m, KEY_DEPENDENCIES) { + err := d.parseDependencies(m[KEY_DEPENDENCIES], currentSchema) + if err != nil { + return err + } + } + + // items + if existsMapKey(m, KEY_ITEMS) { + if isKind(m[KEY_ITEMS], reflect.Slice) { + for _, itemElement := range m[KEY_ITEMS].([]interface{}) { + if isKind(itemElement, reflect.Map) { + newSchema := &subSchema{parent: currentSchema, property: KEY_ITEMS} + newSchema.ref = currentSchema.ref + currentSchema.AddItemsChild(newSchema) + err := d.parseSchema(itemElement, newSchema) + if err != nil { + return err + } + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": STRING_SCHEMA + "/" + STRING_ARRAY_OF_SCHEMAS, + "given": KEY_ITEMS, + }, + )) + } + currentSchema.itemsChildrenIsSingleSchema = false + } + } else if isKind(m[KEY_ITEMS], reflect.Map) { + newSchema := &subSchema{parent: currentSchema, property: KEY_ITEMS} + newSchema.ref = currentSchema.ref + currentSchema.AddItemsChild(newSchema) + err := d.parseSchema(m[KEY_ITEMS], newSchema) + if err != nil { + return err + } + currentSchema.itemsChildrenIsSingleSchema = true + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": STRING_SCHEMA + "/" + STRING_ARRAY_OF_SCHEMAS, + "given": KEY_ITEMS, + }, + )) + } + } + + // additionalItems + if existsMapKey(m, KEY_ADDITIONAL_ITEMS) { + if isKind(m[KEY_ADDITIONAL_ITEMS], reflect.Bool) { + currentSchema.additionalItems = m[KEY_ADDITIONAL_ITEMS].(bool) + } else if isKind(m[KEY_ADDITIONAL_ITEMS], reflect.Map) { + newSchema := &subSchema{property: KEY_ADDITIONAL_ITEMS, parent: currentSchema, ref: currentSchema.ref} + currentSchema.additionalItems = newSchema + err := d.parseSchema(m[KEY_ADDITIONAL_ITEMS], newSchema) + if err != nil { + return errors.New(err.Error()) + } + } else { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": TYPE_BOOLEAN + "/" + STRING_SCHEMA, + "given": KEY_ADDITIONAL_ITEMS, + }, + )) + } + } + + // validation : number / integer + + if existsMapKey(m, KEY_MULTIPLE_OF) { + multipleOfValue := mustBeNumber(m[KEY_MULTIPLE_OF]) + if multipleOfValue == nil { + return errors.New(formatErrorDescription( + Locale.InvalidType(), + ErrorDetails{ + "expected": STRING_NUMBER, + "given": KEY_MULTIPLE_OF, + }, + )) + } + if *multipleOfValue <= 0 { + return errors.New(formatErrorDescription( + Locale.GreaterThanZero(), + ErrorDetails{"number": KEY_MULTIPLE_OF}, + )) + } + currentSchema.multipleOf = multipleOfValue + } + + if existsMapKey(m, KEY_MINIMUM) { + minimumValue := mustBeNumber(m[KEY_MINIMUM]) + if minimumValue == nil { + return errors.New(formatErrorDescription( + Locale.MustBeOfA(), + ErrorDetails{"x": KEY_MINIMUM, "y": STRING_NUMBER}, + )) + } + currentSchema.minimum = minimumValue + } + + if existsMapKey(m, KEY_EXCLUSIVE_MINIMUM) { + if isKind(m[KEY_EXCLUSIVE_MINIMUM], reflect.Bool) { + if currentSchema.minimum == nil { + return errors.New(formatErrorDescription( + Locale.CannotBeUsedWithout(), + ErrorDetails{"x": KEY_EXCLUSIVE_MINIMUM, "y": KEY_MINIMUM}, + )) + } + exclusiveMinimumValue := m[KEY_EXCLUSIVE_MINIMUM].(bool) + currentSchema.exclusiveMinimum = exclusiveMinimumValue + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfA(), + ErrorDetails{"x": KEY_EXCLUSIVE_MINIMUM, "y": TYPE_BOOLEAN}, + )) + } + } + + if existsMapKey(m, KEY_MAXIMUM) { + maximumValue := mustBeNumber(m[KEY_MAXIMUM]) + if maximumValue == nil { + return errors.New(formatErrorDescription( + Locale.MustBeOfA(), + ErrorDetails{"x": KEY_MAXIMUM, "y": STRING_NUMBER}, + )) + } + currentSchema.maximum = maximumValue + } + + if existsMapKey(m, KEY_EXCLUSIVE_MAXIMUM) { + if isKind(m[KEY_EXCLUSIVE_MAXIMUM], reflect.Bool) { + if currentSchema.maximum == nil { + return errors.New(formatErrorDescription( + Locale.CannotBeUsedWithout(), + ErrorDetails{"x": KEY_EXCLUSIVE_MAXIMUM, "y": KEY_MAXIMUM}, + )) + } + exclusiveMaximumValue := m[KEY_EXCLUSIVE_MAXIMUM].(bool) + currentSchema.exclusiveMaximum = exclusiveMaximumValue + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfA(), + ErrorDetails{"x": KEY_EXCLUSIVE_MAXIMUM, "y": STRING_NUMBER}, + )) + } + } + + if currentSchema.minimum != nil && currentSchema.maximum != nil { + if *currentSchema.minimum > *currentSchema.maximum { + return errors.New(formatErrorDescription( + Locale.CannotBeGT(), + ErrorDetails{"x": KEY_MINIMUM, "y": KEY_MAXIMUM}, + )) + } + } + + // validation : string + + if existsMapKey(m, KEY_MIN_LENGTH) { + minLengthIntegerValue := mustBeInteger(m[KEY_MIN_LENGTH]) + if minLengthIntegerValue == nil { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_MIN_LENGTH, "y": TYPE_INTEGER}, + )) + } + if *minLengthIntegerValue < 0 { + return errors.New(formatErrorDescription( + Locale.MustBeGTEZero(), + ErrorDetails{"key": KEY_MIN_LENGTH}, + )) + } + currentSchema.minLength = minLengthIntegerValue + } + + if existsMapKey(m, KEY_MAX_LENGTH) { + maxLengthIntegerValue := mustBeInteger(m[KEY_MAX_LENGTH]) + if maxLengthIntegerValue == nil { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_MAX_LENGTH, "y": TYPE_INTEGER}, + )) + } + if *maxLengthIntegerValue < 0 { + return errors.New(formatErrorDescription( + Locale.MustBeGTEZero(), + ErrorDetails{"key": KEY_MAX_LENGTH}, + )) + } + currentSchema.maxLength = maxLengthIntegerValue + } + + if currentSchema.minLength != nil && currentSchema.maxLength != nil { + if *currentSchema.minLength > *currentSchema.maxLength { + return errors.New(formatErrorDescription( + Locale.CannotBeGT(), + ErrorDetails{"x": KEY_MIN_LENGTH, "y": KEY_MAX_LENGTH}, + )) + } + } + + if existsMapKey(m, KEY_PATTERN) { + if isKind(m[KEY_PATTERN], reflect.String) { + regexpObject, err := regexp.Compile(m[KEY_PATTERN].(string)) + if err != nil { + return errors.New(formatErrorDescription( + Locale.MustBeValidRegex(), + ErrorDetails{"key": KEY_PATTERN}, + )) + } + currentSchema.pattern = regexpObject + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfA(), + ErrorDetails{"x": KEY_PATTERN, "y": TYPE_STRING}, + )) + } + } + + if existsMapKey(m, KEY_FORMAT) { + formatString, ok := m[KEY_FORMAT].(string) + if ok && FormatCheckers.Has(formatString) { + currentSchema.format = formatString + } else { + return errors.New(formatErrorDescription( + Locale.MustBeValidFormat(), + ErrorDetails{"key": KEY_FORMAT, "given": m[KEY_FORMAT]}, + )) + } + } + + // validation : object + + if existsMapKey(m, KEY_MIN_PROPERTIES) { + minPropertiesIntegerValue := mustBeInteger(m[KEY_MIN_PROPERTIES]) + if minPropertiesIntegerValue == nil { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_MIN_PROPERTIES, "y": TYPE_INTEGER}, + )) + } + if *minPropertiesIntegerValue < 0 { + return errors.New(formatErrorDescription( + Locale.MustBeGTEZero(), + ErrorDetails{"key": KEY_MIN_PROPERTIES}, + )) + } + currentSchema.minProperties = minPropertiesIntegerValue + } + + if existsMapKey(m, KEY_MAX_PROPERTIES) { + maxPropertiesIntegerValue := mustBeInteger(m[KEY_MAX_PROPERTIES]) + if maxPropertiesIntegerValue == nil { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_MAX_PROPERTIES, "y": TYPE_INTEGER}, + )) + } + if *maxPropertiesIntegerValue < 0 { + return errors.New(formatErrorDescription( + Locale.MustBeGTEZero(), + ErrorDetails{"key": KEY_MAX_PROPERTIES}, + )) + } + currentSchema.maxProperties = maxPropertiesIntegerValue + } + + if currentSchema.minProperties != nil && currentSchema.maxProperties != nil { + if *currentSchema.minProperties > *currentSchema.maxProperties { + return errors.New(formatErrorDescription( + Locale.KeyCannotBeGreaterThan(), + ErrorDetails{"key": KEY_MIN_PROPERTIES, "y": KEY_MAX_PROPERTIES}, + )) + } + } + + if existsMapKey(m, KEY_REQUIRED) { + if isKind(m[KEY_REQUIRED], reflect.Slice) { + requiredValues := m[KEY_REQUIRED].([]interface{}) + for _, requiredValue := range requiredValues { + if isKind(requiredValue, reflect.String) { + err := currentSchema.AddRequired(requiredValue.(string)) + if err != nil { + return err + } + } else { + return errors.New(formatErrorDescription( + Locale.KeyItemsMustBeOfType(), + ErrorDetails{"key": KEY_REQUIRED, "type": TYPE_STRING}, + )) + } + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_REQUIRED, "y": TYPE_ARRAY}, + )) + } + } + + // validation : array + + if existsMapKey(m, KEY_MIN_ITEMS) { + minItemsIntegerValue := mustBeInteger(m[KEY_MIN_ITEMS]) + if minItemsIntegerValue == nil { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_MIN_ITEMS, "y": TYPE_INTEGER}, + )) + } + if *minItemsIntegerValue < 0 { + return errors.New(formatErrorDescription( + Locale.MustBeGTEZero(), + ErrorDetails{"key": KEY_MIN_ITEMS}, + )) + } + currentSchema.minItems = minItemsIntegerValue + } + + if existsMapKey(m, KEY_MAX_ITEMS) { + maxItemsIntegerValue := mustBeInteger(m[KEY_MAX_ITEMS]) + if maxItemsIntegerValue == nil { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_MAX_ITEMS, "y": TYPE_INTEGER}, + )) + } + if *maxItemsIntegerValue < 0 { + return errors.New(formatErrorDescription( + Locale.MustBeGTEZero(), + ErrorDetails{"key": KEY_MAX_ITEMS}, + )) + } + currentSchema.maxItems = maxItemsIntegerValue + } + + if existsMapKey(m, KEY_UNIQUE_ITEMS) { + if isKind(m[KEY_UNIQUE_ITEMS], reflect.Bool) { + currentSchema.uniqueItems = m[KEY_UNIQUE_ITEMS].(bool) + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfA(), + ErrorDetails{"x": KEY_UNIQUE_ITEMS, "y": TYPE_BOOLEAN}, + )) + } + } + + // validation : all + + if existsMapKey(m, KEY_ENUM) { + if isKind(m[KEY_ENUM], reflect.Slice) { + for _, v := range m[KEY_ENUM].([]interface{}) { + err := currentSchema.AddEnum(v) + if err != nil { + return err + } + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_ENUM, "y": TYPE_ARRAY}, + )) + } + } + + // validation : subSchema + + if existsMapKey(m, KEY_ONE_OF) { + if isKind(m[KEY_ONE_OF], reflect.Slice) { + for _, v := range m[KEY_ONE_OF].([]interface{}) { + newSchema := &subSchema{property: KEY_ONE_OF, parent: currentSchema, ref: currentSchema.ref} + currentSchema.AddOneOf(newSchema) + err := d.parseSchema(v, newSchema) + if err != nil { + return err + } + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_ONE_OF, "y": TYPE_ARRAY}, + )) + } + } + + if existsMapKey(m, KEY_ANY_OF) { + if isKind(m[KEY_ANY_OF], reflect.Slice) { + for _, v := range m[KEY_ANY_OF].([]interface{}) { + newSchema := &subSchema{property: KEY_ANY_OF, parent: currentSchema, ref: currentSchema.ref} + currentSchema.AddAnyOf(newSchema) + err := d.parseSchema(v, newSchema) + if err != nil { + return err + } + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_ANY_OF, "y": TYPE_ARRAY}, + )) + } + } + + if existsMapKey(m, KEY_ALL_OF) { + if isKind(m[KEY_ALL_OF], reflect.Slice) { + for _, v := range m[KEY_ALL_OF].([]interface{}) { + newSchema := &subSchema{property: KEY_ALL_OF, parent: currentSchema, ref: currentSchema.ref} + currentSchema.AddAllOf(newSchema) + err := d.parseSchema(v, newSchema) + if err != nil { + return err + } + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_ANY_OF, "y": TYPE_ARRAY}, + )) + } + } + + if existsMapKey(m, KEY_NOT) { + if isKind(m[KEY_NOT], reflect.Map) { + newSchema := &subSchema{property: KEY_NOT, parent: currentSchema, ref: currentSchema.ref} + currentSchema.SetNot(newSchema) + err := d.parseSchema(m[KEY_NOT], newSchema) + if err != nil { + return err + } + } else { + return errors.New(formatErrorDescription( + Locale.MustBeOfAn(), + ErrorDetails{"x": KEY_NOT, "y": TYPE_OBJECT}, + )) + } + } + + return nil +} + +func (d *Schema) parseReference(documentNode interface{}, currentSchema *subSchema, reference string) (e error) { + + var err error + + jsonReference, err := gojsonreference.NewJsonReference(reference) + if err != nil { + return err + } + + standaloneDocument := d.pool.GetStandaloneDocument() + + if jsonReference.HasFullUrl { + currentSchema.ref = &jsonReference + } else { + inheritedReference, err := currentSchema.ref.Inherits(jsonReference) + if err != nil { + return err + } + currentSchema.ref = inheritedReference + } + + jsonPointer := currentSchema.ref.GetPointer() + + var refdDocumentNode interface{} + + if standaloneDocument != nil { + + var err error + refdDocumentNode, _, err = jsonPointer.Get(standaloneDocument) + if err != nil { + return err + } + + } else { + + var err error + dsp, err := d.pool.GetDocument(*currentSchema.ref) + if err != nil { + return err + } + + refdDocumentNode, _, err = jsonPointer.Get(dsp.Document) + if err != nil { + return err + } + + } + + if !isKind(refdDocumentNode, reflect.Map) { + return errors.New(formatErrorDescription( + Locale.MustBeOfType(), + ErrorDetails{"key": STRING_SCHEMA, "type": TYPE_OBJECT}, + )) + } + + // returns the loaded referenced subSchema for the caller to update its current subSchema + newSchemaDocument := refdDocumentNode.(map[string]interface{}) + + newSchema := &subSchema{property: KEY_REF, parent: currentSchema, ref: currentSchema.ref} + d.referencePool.Add(currentSchema.ref.String()+reference, newSchema) + + err = d.parseSchema(newSchemaDocument, newSchema) + if err != nil { + return err + } + + currentSchema.refSchema = newSchema + + return nil + +} + +func (d *Schema) parseProperties(documentNode interface{}, currentSchema *subSchema) error { + + if !isKind(documentNode, reflect.Map) { + return errors.New(formatErrorDescription( + Locale.MustBeOfType(), + ErrorDetails{"key": STRING_PROPERTIES, "type": TYPE_OBJECT}, + )) + } + + m := documentNode.(map[string]interface{}) + for k := range m { + schemaProperty := k + newSchema := &subSchema{property: schemaProperty, parent: currentSchema, ref: currentSchema.ref} + currentSchema.AddPropertiesChild(newSchema) + err := d.parseSchema(m[k], newSchema) + if err != nil { + return err + } + } + + return nil +} + +func (d *Schema) parseDependencies(documentNode interface{}, currentSchema *subSchema) error { + + if !isKind(documentNode, reflect.Map) { + return errors.New(formatErrorDescription( + Locale.MustBeOfType(), + ErrorDetails{"key": KEY_DEPENDENCIES, "type": TYPE_OBJECT}, + )) + } + + m := documentNode.(map[string]interface{}) + currentSchema.dependencies = make(map[string]interface{}) + + for k := range m { + switch reflect.ValueOf(m[k]).Kind() { + + case reflect.Slice: + values := m[k].([]interface{}) + var valuesToRegister []string + + for _, value := range values { + if !isKind(value, reflect.String) { + return errors.New(formatErrorDescription( + Locale.MustBeOfType(), + ErrorDetails{ + "key": STRING_DEPENDENCY, + "type": STRING_SCHEMA_OR_ARRAY_OF_STRINGS, + }, + )) + } else { + valuesToRegister = append(valuesToRegister, value.(string)) + } + currentSchema.dependencies[k] = valuesToRegister + } + + case reflect.Map: + depSchema := &subSchema{property: k, parent: currentSchema, ref: currentSchema.ref} + err := d.parseSchema(m[k], depSchema) + if err != nil { + return err + } + currentSchema.dependencies[k] = depSchema + + default: + return errors.New(formatErrorDescription( + Locale.MustBeOfType(), + ErrorDetails{ + "key": STRING_DEPENDENCY, + "type": STRING_SCHEMA_OR_ARRAY_OF_STRINGS, + }, + )) + } + + } + + return nil +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/schemaPool.go b/vendor/github.com/xeipuuv/gojsonschema/schemaPool.go new file mode 100644 index 0000000..70ae731 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/schemaPool.go @@ -0,0 +1,110 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// 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. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Defines resources pooling. +// Eases referencing and avoids downloading the same resource twice. +// +// created 26-02-2013 + +package gojsonschema + +import ( + "errors" + "net/http" + + "github.com/xeipuuv/gojsonreference" +) + +type schemaPoolDocument struct { + Document interface{} +} + +type schemaPool struct { + schemaPoolDocuments map[string]*schemaPoolDocument + standaloneDocument interface{} + fs http.FileSystem +} + +func newSchemaPool(fs http.FileSystem) *schemaPool { + + p := &schemaPool{} + p.schemaPoolDocuments = make(map[string]*schemaPoolDocument) + p.standaloneDocument = nil + p.fs = fs + + return p +} + +func (p *schemaPool) SetStandaloneDocument(document interface{}) { + p.standaloneDocument = document +} + +func (p *schemaPool) GetStandaloneDocument() (document interface{}) { + return p.standaloneDocument +} + +func (p *schemaPool) GetDocument(reference gojsonreference.JsonReference) (*schemaPoolDocument, error) { + + if internalLogEnabled { + internalLog("Get Document ( %s )", reference.String()) + } + + var err error + + // It is not possible to load anything that is not canonical... + if !reference.IsCanonical() { + return nil, errors.New(formatErrorDescription( + Locale.ReferenceMustBeCanonical(), + ErrorDetails{"reference": reference}, + )) + } + + refToUrl := reference + refToUrl.GetUrl().Fragment = "" + + var spd *schemaPoolDocument + + // Try to find the requested document in the pool + for k := range p.schemaPoolDocuments { + if k == refToUrl.String() { + spd = p.schemaPoolDocuments[k] + } + } + + if spd != nil { + if internalLogEnabled { + internalLog(" From pool") + } + return spd, nil + } + + jsonReferenceLoader := NewReferenceLoaderFileSystem(reference.String(), p.fs) + document, err := jsonReferenceLoader.loadJSON() + if err != nil { + return nil, err + } + + spd = &schemaPoolDocument{Document: document} + // add the document to the pool for potential later use + p.schemaPoolDocuments[refToUrl.String()] = spd + + return spd, nil +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/schemaReferencePool.go b/vendor/github.com/xeipuuv/gojsonschema/schemaReferencePool.go new file mode 100644 index 0000000..294e36a --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/schemaReferencePool.go @@ -0,0 +1,67 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// 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. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Pool of referenced schemas. +// +// created 25-06-2013 + +package gojsonschema + +import ( + "fmt" +) + +type schemaReferencePool struct { + documents map[string]*subSchema +} + +func newSchemaReferencePool() *schemaReferencePool { + + p := &schemaReferencePool{} + p.documents = make(map[string]*subSchema) + + return p +} + +func (p *schemaReferencePool) Get(ref string) (r *subSchema, o bool) { + + if internalLogEnabled { + internalLog(fmt.Sprintf("Schema Reference ( %s )", ref)) + } + + if sch, ok := p.documents[ref]; ok { + if internalLogEnabled { + internalLog(fmt.Sprintf(" From pool")) + } + return sch, true + } + + return nil, false +} + +func (p *schemaReferencePool) Add(ref string, sch *subSchema) { + + if internalLogEnabled { + internalLog(fmt.Sprintf("Add Schema Reference %s to pool", ref)) + } + + p.documents[ref] = sch +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/schemaType.go b/vendor/github.com/xeipuuv/gojsonschema/schemaType.go new file mode 100644 index 0000000..e13a0fb --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/schemaType.go @@ -0,0 +1,83 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// 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. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Helper structure to handle schema types, and the combination of them. +// +// created 28-02-2013 + +package gojsonschema + +import ( + "errors" + "fmt" + "strings" +) + +type jsonSchemaType struct { + types []string +} + +// Is the schema typed ? that is containing at least one type +// When not typed, the schema does not need any type validation +func (t *jsonSchemaType) IsTyped() bool { + return len(t.types) > 0 +} + +func (t *jsonSchemaType) Add(etype string) error { + + if !isStringInSlice(JSON_TYPES, etype) { + return errors.New(formatErrorDescription(Locale.NotAValidType(), ErrorDetails{"type": etype})) + } + + if t.Contains(etype) { + return errors.New(formatErrorDescription(Locale.Duplicated(), ErrorDetails{"type": etype})) + } + + t.types = append(t.types, etype) + + return nil +} + +func (t *jsonSchemaType) Contains(etype string) bool { + + for _, v := range t.types { + if v == etype { + return true + } + } + + return false +} + +func (t *jsonSchemaType) String() string { + + if len(t.types) == 0 { + return STRING_UNDEFINED // should never happen + } + + // Displayed as a list [type1,type2,...] + if len(t.types) > 1 { + return fmt.Sprintf("[%s]", strings.Join(t.types, ",")) + } + + // Only one type: name only + return t.types[0] +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/subSchema.go b/vendor/github.com/xeipuuv/gojsonschema/subSchema.go new file mode 100644 index 0000000..b249b7e --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/subSchema.go @@ -0,0 +1,227 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// 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. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Defines the structure of a sub-subSchema. +// A sub-subSchema can contain other sub-schemas. +// +// created 27-02-2013 + +package gojsonschema + +import ( + "errors" + "regexp" + "strings" + + "github.com/xeipuuv/gojsonreference" +) + +const ( + KEY_SCHEMA = "$subSchema" + KEY_ID = "$id" + KEY_REF = "$ref" + KEY_TITLE = "title" + KEY_DESCRIPTION = "description" + KEY_TYPE = "type" + KEY_ITEMS = "items" + KEY_ADDITIONAL_ITEMS = "additionalItems" + KEY_PROPERTIES = "properties" + KEY_PATTERN_PROPERTIES = "patternProperties" + KEY_ADDITIONAL_PROPERTIES = "additionalProperties" + KEY_DEFINITIONS = "definitions" + KEY_MULTIPLE_OF = "multipleOf" + KEY_MINIMUM = "minimum" + KEY_MAXIMUM = "maximum" + KEY_EXCLUSIVE_MINIMUM = "exclusiveMinimum" + KEY_EXCLUSIVE_MAXIMUM = "exclusiveMaximum" + KEY_MIN_LENGTH = "minLength" + KEY_MAX_LENGTH = "maxLength" + KEY_PATTERN = "pattern" + KEY_FORMAT = "format" + KEY_MIN_PROPERTIES = "minProperties" + KEY_MAX_PROPERTIES = "maxProperties" + KEY_DEPENDENCIES = "dependencies" + KEY_REQUIRED = "required" + KEY_MIN_ITEMS = "minItems" + KEY_MAX_ITEMS = "maxItems" + KEY_UNIQUE_ITEMS = "uniqueItems" + KEY_ENUM = "enum" + KEY_ONE_OF = "oneOf" + KEY_ANY_OF = "anyOf" + KEY_ALL_OF = "allOf" + KEY_NOT = "not" +) + +type subSchema struct { + + // basic subSchema meta properties + id *string + title *string + description *string + + property string + + // Types associated with the subSchema + types jsonSchemaType + + // Reference url + ref *gojsonreference.JsonReference + // Schema referenced + refSchema *subSchema + // Json reference + subSchema *gojsonreference.JsonReference + + // hierarchy + parent *subSchema + definitions map[string]*subSchema + definitionsChildren []*subSchema + itemsChildren []*subSchema + itemsChildrenIsSingleSchema bool + propertiesChildren []*subSchema + + // validation : number / integer + multipleOf *float64 + maximum *float64 + exclusiveMaximum bool + minimum *float64 + exclusiveMinimum bool + + // validation : string + minLength *int + maxLength *int + pattern *regexp.Regexp + format string + + // validation : object + minProperties *int + maxProperties *int + required []string + + dependencies map[string]interface{} + additionalProperties interface{} + patternProperties map[string]*subSchema + + // validation : array + minItems *int + maxItems *int + uniqueItems bool + + additionalItems interface{} + + // validation : all + enum []string + + // validation : subSchema + oneOf []*subSchema + anyOf []*subSchema + allOf []*subSchema + not *subSchema +} + +func (s *subSchema) AddEnum(i interface{}) error { + + is, err := marshalToJsonString(i) + if err != nil { + return err + } + + if isStringInSlice(s.enum, *is) { + return errors.New(formatErrorDescription( + Locale.KeyItemsMustBeUnique(), + ErrorDetails{"key": KEY_ENUM}, + )) + } + + s.enum = append(s.enum, *is) + + return nil +} + +func (s *subSchema) ContainsEnum(i interface{}) (bool, error) { + + is, err := marshalToJsonString(i) + if err != nil { + return false, err + } + + return isStringInSlice(s.enum, *is), nil +} + +func (s *subSchema) AddOneOf(subSchema *subSchema) { + s.oneOf = append(s.oneOf, subSchema) +} + +func (s *subSchema) AddAllOf(subSchema *subSchema) { + s.allOf = append(s.allOf, subSchema) +} + +func (s *subSchema) AddAnyOf(subSchema *subSchema) { + s.anyOf = append(s.anyOf, subSchema) +} + +func (s *subSchema) SetNot(subSchema *subSchema) { + s.not = subSchema +} + +func (s *subSchema) AddRequired(value string) error { + + if isStringInSlice(s.required, value) { + return errors.New(formatErrorDescription( + Locale.KeyItemsMustBeUnique(), + ErrorDetails{"key": KEY_REQUIRED}, + )) + } + + s.required = append(s.required, value) + + return nil +} + +func (s *subSchema) AddDefinitionChild(child *subSchema) { + s.definitionsChildren = append(s.definitionsChildren, child) +} + +func (s *subSchema) AddItemsChild(child *subSchema) { + s.itemsChildren = append(s.itemsChildren, child) +} + +func (s *subSchema) AddPropertiesChild(child *subSchema) { + s.propertiesChildren = append(s.propertiesChildren, child) +} + +func (s *subSchema) PatternPropertiesString() string { + + if s.patternProperties == nil || len(s.patternProperties) == 0 { + return STRING_UNDEFINED // should never happen + } + + patternPropertiesKeySlice := []string{} + for pk, _ := range s.patternProperties { + patternPropertiesKeySlice = append(patternPropertiesKeySlice, `"`+pk+`"`) + } + + if len(patternPropertiesKeySlice) == 1 { + return patternPropertiesKeySlice[0] + } + + return "[" + strings.Join(patternPropertiesKeySlice, ",") + "]" + +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/types.go b/vendor/github.com/xeipuuv/gojsonschema/types.go new file mode 100644 index 0000000..952d22e --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/types.go @@ -0,0 +1,58 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// 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. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Contains const types for schema and JSON. +// +// created 28-02-2013 + +package gojsonschema + +const ( + TYPE_ARRAY = `array` + TYPE_BOOLEAN = `boolean` + TYPE_INTEGER = `integer` + TYPE_NUMBER = `number` + TYPE_NULL = `null` + TYPE_OBJECT = `object` + TYPE_STRING = `string` +) + +var JSON_TYPES []string +var SCHEMA_TYPES []string + +func init() { + JSON_TYPES = []string{ + TYPE_ARRAY, + TYPE_BOOLEAN, + TYPE_INTEGER, + TYPE_NUMBER, + TYPE_NULL, + TYPE_OBJECT, + TYPE_STRING} + + SCHEMA_TYPES = []string{ + TYPE_ARRAY, + TYPE_BOOLEAN, + TYPE_INTEGER, + TYPE_NUMBER, + TYPE_OBJECT, + TYPE_STRING} +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/utils.go b/vendor/github.com/xeipuuv/gojsonschema/utils.go new file mode 100644 index 0000000..4cbe0dc --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/utils.go @@ -0,0 +1,203 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// 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. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Various utility functions. +// +// created 26-02-2013 + +package gojsonschema + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "strconv" +) + +func isKind(what interface{}, kind reflect.Kind) bool { + return reflect.ValueOf(what).Kind() == kind +} + +func existsMapKey(m map[string]interface{}, k string) bool { + _, ok := m[k] + return ok +} + +func isStringInSlice(s []string, what string) bool { + for i := range s { + if s[i] == what { + return true + } + } + return false +} + +func marshalToJsonString(value interface{}) (*string, error) { + + mBytes, err := json.Marshal(value) + if err != nil { + return nil, err + } + + sBytes := string(mBytes) + return &sBytes, nil +} + +func isJsonNumber(what interface{}) bool { + + switch what.(type) { + + case json.Number: + return true + } + + return false +} + +func checkJsonNumber(what interface{}) (isValidFloat64 bool, isValidInt64 bool, isValidInt32 bool) { + + jsonNumber := what.(json.Number) + + f64, errFloat64 := jsonNumber.Float64() + s64 := strconv.FormatFloat(f64, 'f', -1, 64) + _, errInt64 := strconv.ParseInt(s64, 10, 64) + + isValidFloat64 = errFloat64 == nil + isValidInt64 = errInt64 == nil + + _, errInt32 := strconv.ParseInt(s64, 10, 32) + isValidInt32 = isValidInt64 && errInt32 == nil + + return + +} + +// same as ECMA Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER +const ( + max_json_float = float64(1<<53 - 1) // 9007199254740991.0 2^53 - 1 + min_json_float = -float64(1<<53 - 1) //-9007199254740991.0 -2^53 - 1 +) + +func isFloat64AnInteger(f float64) bool { + + if math.IsNaN(f) || math.IsInf(f, 0) || f < min_json_float || f > max_json_float { + return false + } + + return f == float64(int64(f)) || f == float64(uint64(f)) +} + +func mustBeInteger(what interface{}) *int { + + if isJsonNumber(what) { + + number := what.(json.Number) + + _, _, isValidInt32 := checkJsonNumber(number) + + if isValidInt32 { + + int64Value, err := number.Int64() + if err != nil { + return nil + } + + int32Value := int(int64Value) + return &int32Value + + } else { + return nil + } + + } + + return nil +} + +func mustBeNumber(what interface{}) *float64 { + + if isJsonNumber(what) { + + number := what.(json.Number) + float64Value, err := number.Float64() + + if err == nil { + return &float64Value + } else { + return nil + } + + } + + return nil + +} + +// formats a number so that it is displayed as the smallest string possible +func resultErrorFormatJsonNumber(n json.Number) string { + + if int64Value, err := n.Int64(); err == nil { + return fmt.Sprintf("%d", int64Value) + } + + float64Value, _ := n.Float64() + + return fmt.Sprintf("%g", float64Value) +} + +// formats a number so that it is displayed as the smallest string possible +func resultErrorFormatNumber(n float64) string { + + if isFloat64AnInteger(n) { + return fmt.Sprintf("%d", int64(n)) + } + + return fmt.Sprintf("%g", n) +} + +func convertDocumentNode(val interface{}) interface{} { + + if lval, ok := val.([]interface{}); ok { + + res := []interface{}{} + for _, v := range lval { + res = append(res, convertDocumentNode(v)) + } + + return res + + } + + if mval, ok := val.(map[interface{}]interface{}); ok { + + res := map[string]interface{}{} + + for k, v := range mval { + res[k.(string)] = convertDocumentNode(v) + } + + return res + + } + + return val +} diff --git a/vendor/github.com/xeipuuv/gojsonschema/validation.go b/vendor/github.com/xeipuuv/gojsonschema/validation.go new file mode 100644 index 0000000..2dc0df2 --- /dev/null +++ b/vendor/github.com/xeipuuv/gojsonschema/validation.go @@ -0,0 +1,829 @@ +// Copyright 2015 xeipuuv ( https://github.com/xeipuuv ) +// +// 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. + +// author xeipuuv +// author-github https://github.com/xeipuuv +// author-mail xeipuuv@gmail.com +// +// repository-name gojsonschema +// repository-desc An implementation of JSON Schema, based on IETF's draft v4 - Go language. +// +// description Extends Schema and subSchema, implements the validation phase. +// +// created 28-02-2013 + +package gojsonschema + +import ( + "encoding/json" + "reflect" + "regexp" + "strconv" + "strings" + "unicode/utf8" +) + +func Validate(ls JSONLoader, ld JSONLoader) (*Result, error) { + + var err error + + // load schema + + schema, err := NewSchema(ls) + if err != nil { + return nil, err + } + + // begine validation + + return schema.Validate(ld) + +} + +func (v *Schema) Validate(l JSONLoader) (*Result, error) { + + // load document + + root, err := l.loadJSON() + if err != nil { + return nil, err + } + + // begin validation + + result := &Result{} + context := newJsonContext(STRING_CONTEXT_ROOT, nil) + v.rootSchema.validateRecursive(v.rootSchema, root, result, context) + + return result, nil + +} + +func (v *subSchema) subValidateWithContext(document interface{}, context *jsonContext) *Result { + result := &Result{} + v.validateRecursive(v, document, result, context) + return result +} + +// Walker function to validate the json recursively against the subSchema +func (v *subSchema) validateRecursive(currentSubSchema *subSchema, currentNode interface{}, result *Result, context *jsonContext) { + + if internalLogEnabled { + internalLog("validateRecursive %s", context.String()) + internalLog(" %v", currentNode) + } + + // Handle referenced schemas, returns directly when a $ref is found + if currentSubSchema.refSchema != nil { + v.validateRecursive(currentSubSchema.refSchema, currentNode, result, context) + return + } + + // Check for null value + if currentNode == nil { + if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_NULL) { + result.addError( + new(InvalidTypeError), + context, + currentNode, + ErrorDetails{ + "expected": currentSubSchema.types.String(), + "given": TYPE_NULL, + }, + ) + return + } + + currentSubSchema.validateSchema(currentSubSchema, currentNode, result, context) + v.validateCommon(currentSubSchema, currentNode, result, context) + + } else { // Not a null value + + if isJsonNumber(currentNode) { + + value := currentNode.(json.Number) + + _, isValidInt64, _ := checkJsonNumber(value) + + validType := currentSubSchema.types.Contains(TYPE_NUMBER) || (isValidInt64 && currentSubSchema.types.Contains(TYPE_INTEGER)) + + if currentSubSchema.types.IsTyped() && !validType { + + givenType := TYPE_INTEGER + if !isValidInt64 { + givenType = TYPE_NUMBER + } + + result.addError( + new(InvalidTypeError), + context, + currentNode, + ErrorDetails{ + "expected": currentSubSchema.types.String(), + "given": givenType, + }, + ) + return + } + + currentSubSchema.validateSchema(currentSubSchema, value, result, context) + v.validateNumber(currentSubSchema, value, result, context) + v.validateCommon(currentSubSchema, value, result, context) + v.validateString(currentSubSchema, value, result, context) + + } else { + + rValue := reflect.ValueOf(currentNode) + rKind := rValue.Kind() + + switch rKind { + + // Slice => JSON array + + case reflect.Slice: + + if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_ARRAY) { + result.addError( + new(InvalidTypeError), + context, + currentNode, + ErrorDetails{ + "expected": currentSubSchema.types.String(), + "given": TYPE_ARRAY, + }, + ) + return + } + + castCurrentNode := currentNode.([]interface{}) + + currentSubSchema.validateSchema(currentSubSchema, castCurrentNode, result, context) + + v.validateArray(currentSubSchema, castCurrentNode, result, context) + v.validateCommon(currentSubSchema, castCurrentNode, result, context) + + // Map => JSON object + + case reflect.Map: + if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_OBJECT) { + result.addError( + new(InvalidTypeError), + context, + currentNode, + ErrorDetails{ + "expected": currentSubSchema.types.String(), + "given": TYPE_OBJECT, + }, + ) + return + } + + castCurrentNode, ok := currentNode.(map[string]interface{}) + if !ok { + castCurrentNode = convertDocumentNode(currentNode).(map[string]interface{}) + } + + currentSubSchema.validateSchema(currentSubSchema, castCurrentNode, result, context) + + v.validateObject(currentSubSchema, castCurrentNode, result, context) + v.validateCommon(currentSubSchema, castCurrentNode, result, context) + + for _, pSchema := range currentSubSchema.propertiesChildren { + nextNode, ok := castCurrentNode[pSchema.property] + if ok { + subContext := newJsonContext(pSchema.property, context) + v.validateRecursive(pSchema, nextNode, result, subContext) + } + } + + // Simple JSON values : string, number, boolean + + case reflect.Bool: + + if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_BOOLEAN) { + result.addError( + new(InvalidTypeError), + context, + currentNode, + ErrorDetails{ + "expected": currentSubSchema.types.String(), + "given": TYPE_BOOLEAN, + }, + ) + return + } + + value := currentNode.(bool) + + currentSubSchema.validateSchema(currentSubSchema, value, result, context) + v.validateNumber(currentSubSchema, value, result, context) + v.validateCommon(currentSubSchema, value, result, context) + v.validateString(currentSubSchema, value, result, context) + + case reflect.String: + + if currentSubSchema.types.IsTyped() && !currentSubSchema.types.Contains(TYPE_STRING) { + result.addError( + new(InvalidTypeError), + context, + currentNode, + ErrorDetails{ + "expected": currentSubSchema.types.String(), + "given": TYPE_STRING, + }, + ) + return + } + + value := currentNode.(string) + + currentSubSchema.validateSchema(currentSubSchema, value, result, context) + v.validateNumber(currentSubSchema, value, result, context) + v.validateCommon(currentSubSchema, value, result, context) + v.validateString(currentSubSchema, value, result, context) + + } + + } + + } + + result.incrementScore() +} + +// Different kinds of validation there, subSchema / common / array / object / string... +func (v *subSchema) validateSchema(currentSubSchema *subSchema, currentNode interface{}, result *Result, context *jsonContext) { + + if internalLogEnabled { + internalLog("validateSchema %s", context.String()) + internalLog(" %v", currentNode) + } + + if len(currentSubSchema.anyOf) > 0 { + + validatedAnyOf := false + var bestValidationResult *Result + + for _, anyOfSchema := range currentSubSchema.anyOf { + if !validatedAnyOf { + validationResult := anyOfSchema.subValidateWithContext(currentNode, context) + validatedAnyOf = validationResult.Valid() + + if !validatedAnyOf && (bestValidationResult == nil || validationResult.score > bestValidationResult.score) { + bestValidationResult = validationResult + } + } + } + if !validatedAnyOf { + + result.addError(new(NumberAnyOfError), context, currentNode, ErrorDetails{}) + + if bestValidationResult != nil { + // add error messages of closest matching subSchema as + // that's probably the one the user was trying to match + result.mergeErrors(bestValidationResult) + } + } + } + + if len(currentSubSchema.oneOf) > 0 { + + nbValidated := 0 + var bestValidationResult *Result + + for _, oneOfSchema := range currentSubSchema.oneOf { + validationResult := oneOfSchema.subValidateWithContext(currentNode, context) + if validationResult.Valid() { + nbValidated++ + } else if nbValidated == 0 && (bestValidationResult == nil || validationResult.score > bestValidationResult.score) { + bestValidationResult = validationResult + } + } + + if nbValidated != 1 { + + result.addError(new(NumberOneOfError), context, currentNode, ErrorDetails{}) + + if nbValidated == 0 { + // add error messages of closest matching subSchema as + // that's probably the one the user was trying to match + result.mergeErrors(bestValidationResult) + } + } + + } + + if len(currentSubSchema.allOf) > 0 { + nbValidated := 0 + + for _, allOfSchema := range currentSubSchema.allOf { + validationResult := allOfSchema.subValidateWithContext(currentNode, context) + if validationResult.Valid() { + nbValidated++ + } + result.mergeErrors(validationResult) + } + + if nbValidated != len(currentSubSchema.allOf) { + result.addError(new(NumberAllOfError), context, currentNode, ErrorDetails{}) + } + } + + if currentSubSchema.not != nil { + validationResult := currentSubSchema.not.subValidateWithContext(currentNode, context) + if validationResult.Valid() { + result.addError(new(NumberNotError), context, currentNode, ErrorDetails{}) + } + } + + if currentSubSchema.dependencies != nil && len(currentSubSchema.dependencies) > 0 { + if isKind(currentNode, reflect.Map) { + for elementKey := range currentNode.(map[string]interface{}) { + if dependency, ok := currentSubSchema.dependencies[elementKey]; ok { + switch dependency := dependency.(type) { + + case []string: + for _, dependOnKey := range dependency { + if _, dependencyResolved := currentNode.(map[string]interface{})[dependOnKey]; !dependencyResolved { + result.addError( + new(MissingDependencyError), + context, + currentNode, + ErrorDetails{"dependency": dependOnKey}, + ) + } + } + + case *subSchema: + dependency.validateRecursive(dependency, currentNode, result, context) + + } + } + } + } + } + + result.incrementScore() +} + +func (v *subSchema) validateCommon(currentSubSchema *subSchema, value interface{}, result *Result, context *jsonContext) { + + if internalLogEnabled { + internalLog("validateCommon %s", context.String()) + internalLog(" %v", value) + } + + // enum: + if len(currentSubSchema.enum) > 0 { + has, err := currentSubSchema.ContainsEnum(value) + if err != nil { + result.addError(new(InternalError), context, value, ErrorDetails{"error": err}) + } + if !has { + result.addError( + new(EnumError), + context, + value, + ErrorDetails{ + "allowed": strings.Join(currentSubSchema.enum, ", "), + }, + ) + } + } + + result.incrementScore() +} + +func (v *subSchema) validateArray(currentSubSchema *subSchema, value []interface{}, result *Result, context *jsonContext) { + + if internalLogEnabled { + internalLog("validateArray %s", context.String()) + internalLog(" %v", value) + } + + nbItems := len(value) + + // TODO explain + if currentSubSchema.itemsChildrenIsSingleSchema { + for i := range value { + subContext := newJsonContext(strconv.Itoa(i), context) + validationResult := currentSubSchema.itemsChildren[0].subValidateWithContext(value[i], subContext) + result.mergeErrors(validationResult) + } + } else { + if currentSubSchema.itemsChildren != nil && len(currentSubSchema.itemsChildren) > 0 { + + nbItems := len(currentSubSchema.itemsChildren) + nbValues := len(value) + + if nbItems == nbValues { + for i := 0; i != nbItems; i++ { + subContext := newJsonContext(strconv.Itoa(i), context) + validationResult := currentSubSchema.itemsChildren[i].subValidateWithContext(value[i], subContext) + result.mergeErrors(validationResult) + } + } else if nbItems < nbValues { + switch currentSubSchema.additionalItems.(type) { + case bool: + if !currentSubSchema.additionalItems.(bool) { + result.addError(new(ArrayNoAdditionalItemsError), context, value, ErrorDetails{}) + } + case *subSchema: + additionalItemSchema := currentSubSchema.additionalItems.(*subSchema) + for i := nbItems; i != nbValues; i++ { + subContext := newJsonContext(strconv.Itoa(i), context) + validationResult := additionalItemSchema.subValidateWithContext(value[i], subContext) + result.mergeErrors(validationResult) + } + } + } + } + } + + // minItems & maxItems + if currentSubSchema.minItems != nil { + if nbItems < int(*currentSubSchema.minItems) { + result.addError( + new(ArrayMinItemsError), + context, + value, + ErrorDetails{"min": *currentSubSchema.minItems}, + ) + } + } + if currentSubSchema.maxItems != nil { + if nbItems > int(*currentSubSchema.maxItems) { + result.addError( + new(ArrayMaxItemsError), + context, + value, + ErrorDetails{"max": *currentSubSchema.maxItems}, + ) + } + } + + // uniqueItems: + if currentSubSchema.uniqueItems { + var stringifiedItems []string + for _, v := range value { + vString, err := marshalToJsonString(v) + if err != nil { + result.addError(new(InternalError), context, value, ErrorDetails{"err": err}) + } + if isStringInSlice(stringifiedItems, *vString) { + result.addError( + new(ItemsMustBeUniqueError), + context, + value, + ErrorDetails{"type": TYPE_ARRAY}, + ) + } + stringifiedItems = append(stringifiedItems, *vString) + } + } + + result.incrementScore() +} + +func (v *subSchema) validateObject(currentSubSchema *subSchema, value map[string]interface{}, result *Result, context *jsonContext) { + + if internalLogEnabled { + internalLog("validateObject %s", context.String()) + internalLog(" %v", value) + } + + // minProperties & maxProperties: + if currentSubSchema.minProperties != nil { + if len(value) < int(*currentSubSchema.minProperties) { + result.addError( + new(ArrayMinPropertiesError), + context, + value, + ErrorDetails{"min": *currentSubSchema.minProperties}, + ) + } + } + if currentSubSchema.maxProperties != nil { + if len(value) > int(*currentSubSchema.maxProperties) { + result.addError( + new(ArrayMaxPropertiesError), + context, + value, + ErrorDetails{"max": *currentSubSchema.maxProperties}, + ) + } + } + + // required: + for _, requiredProperty := range currentSubSchema.required { + _, ok := value[requiredProperty] + if ok { + result.incrementScore() + } else { + result.addError( + new(RequiredError), + context, + value, + ErrorDetails{"property": requiredProperty}, + ) + } + } + + // additionalProperty & patternProperty: + if currentSubSchema.additionalProperties != nil { + + switch currentSubSchema.additionalProperties.(type) { + case bool: + + if !currentSubSchema.additionalProperties.(bool) { + + for pk := range value { + + found := false + for _, spValue := range currentSubSchema.propertiesChildren { + if pk == spValue.property { + found = true + } + } + + pp_has, pp_match := v.validatePatternProperty(currentSubSchema, pk, value[pk], result, context) + + if found { + + if pp_has && !pp_match { + result.addError( + new(AdditionalPropertyNotAllowedError), + context, + value, + ErrorDetails{"property": pk}, + ) + } + + } else { + + if !pp_has || !pp_match { + result.addError( + new(AdditionalPropertyNotAllowedError), + context, + value, + ErrorDetails{"property": pk}, + ) + } + + } + } + } + + case *subSchema: + + additionalPropertiesSchema := currentSubSchema.additionalProperties.(*subSchema) + for pk := range value { + + found := false + for _, spValue := range currentSubSchema.propertiesChildren { + if pk == spValue.property { + found = true + } + } + + pp_has, pp_match := v.validatePatternProperty(currentSubSchema, pk, value[pk], result, context) + + if found { + + if pp_has && !pp_match { + validationResult := additionalPropertiesSchema.subValidateWithContext(value[pk], context) + result.mergeErrors(validationResult) + } + + } else { + + if !pp_has || !pp_match { + validationResult := additionalPropertiesSchema.subValidateWithContext(value[pk], context) + result.mergeErrors(validationResult) + } + + } + + } + } + } else { + + for pk := range value { + + pp_has, pp_match := v.validatePatternProperty(currentSubSchema, pk, value[pk], result, context) + + if pp_has && !pp_match { + + result.addError( + new(InvalidPropertyPatternError), + context, + value, + ErrorDetails{ + "property": pk, + "pattern": currentSubSchema.PatternPropertiesString(), + }, + ) + } + + } + } + + result.incrementScore() +} + +func (v *subSchema) validatePatternProperty(currentSubSchema *subSchema, key string, value interface{}, result *Result, context *jsonContext) (has bool, matched bool) { + + if internalLogEnabled { + internalLog("validatePatternProperty %s", context.String()) + internalLog(" %s %v", key, value) + } + + has = false + + validatedkey := false + + for pk, pv := range currentSubSchema.patternProperties { + if matches, _ := regexp.MatchString(pk, key); matches { + has = true + subContext := newJsonContext(key, context) + validationResult := pv.subValidateWithContext(value, subContext) + result.mergeErrors(validationResult) + if validationResult.Valid() { + validatedkey = true + } + } + } + + if !validatedkey { + return has, false + } + + result.incrementScore() + + return has, true +} + +func (v *subSchema) validateString(currentSubSchema *subSchema, value interface{}, result *Result, context *jsonContext) { + + // Ignore JSON numbers + if isJsonNumber(value) { + return + } + + // Ignore non strings + if !isKind(value, reflect.String) { + return + } + + if internalLogEnabled { + internalLog("validateString %s", context.String()) + internalLog(" %v", value) + } + + stringValue := value.(string) + + // minLength & maxLength: + if currentSubSchema.minLength != nil { + if utf8.RuneCount([]byte(stringValue)) < int(*currentSubSchema.minLength) { + result.addError( + new(StringLengthGTEError), + context, + value, + ErrorDetails{"min": *currentSubSchema.minLength}, + ) + } + } + if currentSubSchema.maxLength != nil { + if utf8.RuneCount([]byte(stringValue)) > int(*currentSubSchema.maxLength) { + result.addError( + new(StringLengthLTEError), + context, + value, + ErrorDetails{"max": *currentSubSchema.maxLength}, + ) + } + } + + // pattern: + if currentSubSchema.pattern != nil { + if !currentSubSchema.pattern.MatchString(stringValue) { + result.addError( + new(DoesNotMatchPatternError), + context, + value, + ErrorDetails{"pattern": currentSubSchema.pattern}, + ) + + } + } + + // format + if currentSubSchema.format != "" { + if !FormatCheckers.IsFormat(currentSubSchema.format, stringValue) { + result.addError( + new(DoesNotMatchFormatError), + context, + value, + ErrorDetails{"format": currentSubSchema.format}, + ) + } + } + + result.incrementScore() +} + +func (v *subSchema) validateNumber(currentSubSchema *subSchema, value interface{}, result *Result, context *jsonContext) { + + // Ignore non numbers + if !isJsonNumber(value) { + return + } + + if internalLogEnabled { + internalLog("validateNumber %s", context.String()) + internalLog(" %v", value) + } + + number := value.(json.Number) + float64Value, _ := number.Float64() + + // multipleOf: + if currentSubSchema.multipleOf != nil { + + if !isFloat64AnInteger(float64Value / *currentSubSchema.multipleOf) { + result.addError( + new(MultipleOfError), + context, + resultErrorFormatJsonNumber(number), + ErrorDetails{"multiple": *currentSubSchema.multipleOf}, + ) + } + } + + //maximum & exclusiveMaximum: + if currentSubSchema.maximum != nil { + if currentSubSchema.exclusiveMaximum { + if float64Value >= *currentSubSchema.maximum { + result.addError( + new(NumberLTError), + context, + resultErrorFormatJsonNumber(number), + ErrorDetails{ + "max": resultErrorFormatNumber(*currentSubSchema.maximum), + }, + ) + } + } else { + if float64Value > *currentSubSchema.maximum { + result.addError( + new(NumberLTEError), + context, + resultErrorFormatJsonNumber(number), + ErrorDetails{ + "max": resultErrorFormatNumber(*currentSubSchema.maximum), + }, + ) + } + } + } + + //minimum & exclusiveMinimum: + if currentSubSchema.minimum != nil { + if currentSubSchema.exclusiveMinimum { + if float64Value <= *currentSubSchema.minimum { + result.addError( + new(NumberGTError), + context, + resultErrorFormatJsonNumber(number), + ErrorDetails{ + "min": resultErrorFormatNumber(*currentSubSchema.minimum), + }, + ) + } + } else { + if float64Value < *currentSubSchema.minimum { + result.addError( + new(NumberGTEError), + context, + resultErrorFormatJsonNumber(number), + ErrorDetails{ + "min": resultErrorFormatNumber(*currentSubSchema.minimum), + }, + ) + } + } + } + + result.incrementScore() +} diff --git a/vendor/go4.org/LICENSE b/vendor/go4.org/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/go4.org/LICENSE @@ -0,0 +1,201 @@ + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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/vendor/go4.org/errorutil/highlight.go b/vendor/go4.org/errorutil/highlight.go new file mode 100644 index 0000000..1b1efb0 --- /dev/null +++ b/vendor/go4.org/errorutil/highlight.go @@ -0,0 +1,58 @@ +/* +Copyright 2011 Google Inc. + +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 errorutil helps make better error messages. +package errorutil // import "go4.org/errorutil" + +import ( + "bufio" + "bytes" + "fmt" + "io" + "strings" +) + +// HighlightBytePosition takes a reader and the location in bytes of a parse +// error (for instance, from json.SyntaxError.Offset) and returns the line, column, +// and pretty-printed context around the error with an arrow indicating the exact +// position of the syntax error. +func HighlightBytePosition(f io.Reader, pos int64) (line, col int, highlight string) { + line = 1 + br := bufio.NewReader(f) + lastLine := "" + thisLine := new(bytes.Buffer) + for n := int64(0); n < pos; n++ { + b, err := br.ReadByte() + if err != nil { + break + } + if b == '\n' { + lastLine = thisLine.String() + thisLine.Reset() + line++ + col = 1 + } else { + col++ + thisLine.WriteByte(b) + } + } + if line > 1 { + highlight += fmt.Sprintf("%5d: %s\n", line-1, lastLine) + } + highlight += fmt.Sprintf("%5d: %s\n", line, thisLine.String()) + highlight += fmt.Sprintf("%s^\n", strings.Repeat(" ", col+5)) + return +} From 593b8f03402f0875bcc604cd7d57f88d0ced4519 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Thu, 9 Jun 2016 13:31:21 -0700 Subject: [PATCH 120/245] proposals: add release-approval-process This is a proposed process for approval of new releases of specifications and projects from the OCI. The creation of this process is designed to clarify how a release gets created and who needs to sign off. --- proposals/release-approval-process.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 proposals/release-approval-process.md diff --git a/proposals/release-approval-process.md b/proposals/release-approval-process.md new file mode 100644 index 0000000..ff07077 --- /dev/null +++ b/proposals/release-approval-process.md @@ -0,0 +1,26 @@ +# OCI Project Release Approval Process v1.0 + +OCI projects need a standard process for making releases so the community of maintainers can consistently know when something can be tagged and released. As the OCI maintains three categories of projects: specifications, applications, and conformance/testing tools we will set different rules for each. + +## Specifications + +**Planning a release:** Every OCI specification project SHOULD hold a weekly meeting that involves maintainers reviewing pull requests, debating outstanding issues, and planning releases. This meeting MUST be advertised on the project README and MAY happen on a phone call, video conference, or on IRC. Maintainers MUST send updates to the dev@opencontainers.org with results of these meetings. Maintainers MAY change the meeting cadence once a specification has reached v1.0.0. The meeting cadence MUST NOT be greater than once every four weeks The release plans, corresponding milestones and estimated due dates MUST be published on GitHub (e.g. https://github.com/opencontainers/runtime-spec/milestones). + +**Making a release:** OCI specification projects MUST announce intentions to release with two project maintainer sponsors (listed in the repo MAINTAINERS file) on the dev@opencontainers.org mailing list. After the announcement a two-thirds super majority of project maintainers MUST reply to the list with an LGTM within one week for the release to be approved. The maintainers MUST wait a full week for maintainers to reply but if all maintainers reply with an LGTM then the release MAY release earlier except in the case of a major releases. + +**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with a single REJECT as long two-thirds of the project maintainers approved the release. + +**Timelines:** Specifications have a variety of different timelines in their lifecycle. In early stages the spec SHOULD release often to garner feedback. In later stages there will be bug fix releases, security fix releases, and major breaking change releases. Each of these should have a different level of notification. + +- Pre-v1.0.0 specifications SHOULD release on a regular cadence and MUST follow the normal release process. In practice this should be a release every week or two. +- Major specification releases that introduce new base or optional layers, break backwards compatibility, or add non-optional features MUST release at least two release candidates spaced a minimum of one week apart. In practice this means a major release like a v1.0.0 or v2.0.0 release will take 3 weeks at minimum: one week for v1.0.0-rc1, one week for v1.0.0-rc2, and one week for v1.0.0. Maintainers SHOULD strive to make zero breaking changes during this cycle of release candidates and SHOULD add an additional release candidate when a breaking change is introduced. For example if a breaking change is introduced in -rc2 then a -rc3 SHOULD be made following the normal release process. +- Minor releases that fix bugs, grammar, introduce optional features, tests, or tooling SHOULD be made on an as-needed basis and MUST follow the normal release process. +- Security fix releases MUST follow a special release process that substitutes the dev@opencontainers.org email for security@opencontainers.org. + +## Conformance/Testing and Applications Releases + +**Making a release:** OCI application projects MUST announce intentions to release with two project maintainer sponsors on the dev@opencontainers.org mailing list. After the announcement at least one more project maintainer MUST reply to the list with an LGTM within two business days for the release to be approved. The maintainers SHOULD wait two business days for maintainers to reply and review. If all maintainers reply with an LGTM then the release MAY release earlier. + +**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with a single REJECT. + +Security fix releases MUST follow a special release process that substitutes the dev@opencontainers.org email for security@opencontainers.org. From 889639a07d5954ef867c81db26976e581ab02712 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Fri, 10 Jun 2016 16:47:38 -0700 Subject: [PATCH 121/245] proposal: release-approval-process add some motivation I got some feedback from folks that some motivation early in the document might be helpful for why the process encourages regular communication. --- proposals/release-approval-process.md => GOVERNANCE.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) rename proposals/release-approval-process.md => GOVERNANCE.md (88%) diff --git a/proposals/release-approval-process.md b/GOVERNANCE.md similarity index 88% rename from proposals/release-approval-process.md rename to GOVERNANCE.md index ff07077..5e65608 100644 --- a/proposals/release-approval-process.md +++ b/GOVERNANCE.md @@ -2,9 +2,11 @@ OCI projects need a standard process for making releases so the community of maintainers can consistently know when something can be tagged and released. As the OCI maintains three categories of projects: specifications, applications, and conformance/testing tools we will set different rules for each. +This approval process hopes to encourage early consistent consensus building during project and specification development. The mechanisms used are regular community communication on the mailing list about progress, scheduled meetings for issue resolution and release triage, and regularly paced and communicated releases. An anti-pattern that we want to avoid is heavy development or discussions "late cycle" around major releases. We want do build a community that is involved and communicates consistently through all releases instead of relying on "silent periods" as a judge of stability. + ## Specifications -**Planning a release:** Every OCI specification project SHOULD hold a weekly meeting that involves maintainers reviewing pull requests, debating outstanding issues, and planning releases. This meeting MUST be advertised on the project README and MAY happen on a phone call, video conference, or on IRC. Maintainers MUST send updates to the dev@opencontainers.org with results of these meetings. Maintainers MAY change the meeting cadence once a specification has reached v1.0.0. The meeting cadence MUST NOT be greater than once every four weeks The release plans, corresponding milestones and estimated due dates MUST be published on GitHub (e.g. https://github.com/opencontainers/runtime-spec/milestones). +**Planning a release:** Every OCI specification project SHOULD hold a weekly meeting that involves maintainers reviewing pull requests, debating outstanding issues, and planning releases. This meeting MUST be advertised on the project README and MAY happen on a phone call, video conference, or on IRC. Maintainers MUST send updates to the dev@opencontainers.org with results of these meetings. Maintainers MAY change the meeting cadence once a specification has reached v1.0.0. The meeting cadence MUST NOT be greater than once every four weeks The release plans, corresponding milestones and estimated due dates MUST be published on GitHub (e.g. https://github.com/opencontainers/runtime-spec/milestones). **Making a release:** OCI specification projects MUST announce intentions to release with two project maintainer sponsors (listed in the repo MAINTAINERS file) on the dev@opencontainers.org mailing list. After the announcement a two-thirds super majority of project maintainers MUST reply to the list with an LGTM within one week for the release to be approved. The maintainers MUST wait a full week for maintainers to reply but if all maintainers reply with an LGTM then the release MAY release earlier except in the case of a major releases. From 7e11601c61472ec0012df4bf6d9baccca93c1673 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 14 Jun 2016 11:21:00 -0700 Subject: [PATCH 122/245] proposals: release approval process to one week for apps Requiring applications wait 1 week to get feedback before making a release, removing "business day" wording @cyphar, @stevvooe, and @wking were in the discussion.[1] [1] https://github.com/opencontainers/tob/pull/15#discussion_r66543927 --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 5e65608..60d2b31 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -21,7 +21,7 @@ This approval process hopes to encourage early consistent consensus building dur ## Conformance/Testing and Applications Releases -**Making a release:** OCI application projects MUST announce intentions to release with two project maintainer sponsors on the dev@opencontainers.org mailing list. After the announcement at least one more project maintainer MUST reply to the list with an LGTM within two business days for the release to be approved. The maintainers SHOULD wait two business days for maintainers to reply and review. If all maintainers reply with an LGTM then the release MAY release earlier. +**Making a release:** OCI application projects MUST announce intentions to release with two project maintainer sponsors on the dev@opencontainers.org mailing list. After the announcement at least one more project maintainer MUST reply to the list with an LGTM within one week for the release to be approved. The maintainers SHOULD wait one week for maintainers to reply and review. If all maintainers reply with an LGTM then the release MAY release earlier. **Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with a single REJECT. From e48c6c7b63e6e2160e958b3444e5d03153ec852e Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 14 Jun 2016 11:57:58 -0700 Subject: [PATCH 123/245] proposals: release approval process 3 rcs required Requiring the _minimum_ process for a major release to be 3 rcs and a final release. This will establish a _minimum_ timeline of 1 month to get to a release assuming zero required changes. @stevvooe, @wking were in this discussion. https://github.com/opencontainers/tob/pull/15#discussion_r66543579 --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 60d2b31..5053355 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -15,7 +15,7 @@ This approval process hopes to encourage early consistent consensus building dur **Timelines:** Specifications have a variety of different timelines in their lifecycle. In early stages the spec SHOULD release often to garner feedback. In later stages there will be bug fix releases, security fix releases, and major breaking change releases. Each of these should have a different level of notification. - Pre-v1.0.0 specifications SHOULD release on a regular cadence and MUST follow the normal release process. In practice this should be a release every week or two. -- Major specification releases that introduce new base or optional layers, break backwards compatibility, or add non-optional features MUST release at least two release candidates spaced a minimum of one week apart. In practice this means a major release like a v1.0.0 or v2.0.0 release will take 3 weeks at minimum: one week for v1.0.0-rc1, one week for v1.0.0-rc2, and one week for v1.0.0. Maintainers SHOULD strive to make zero breaking changes during this cycle of release candidates and SHOULD add an additional release candidate when a breaking change is introduced. For example if a breaking change is introduced in -rc2 then a -rc3 SHOULD be made following the normal release process. +- Major specification releases that introduce new base or optional layers, break backwards compatibility, or add non-optional features MUST release at least three release candidates spaced a minimum of one week apart. In practice this means a major release like a v1.0.0 or v2.0.0 release will take 1 month at minimum: one week for v1.0.0-rc1, one week for v1.0.0-rc2, one week for v1.0.0-rc3, and one week for v1.0.0. Maintainers SHOULD strive to make zero breaking changes during this cycle of release candidates and SHOULD add an additional release candidate when a breaking change is introduced. For example if a breaking change is introduced in -rc2 then a -rc3 SHOULD be made following the normal release process. - Minor releases that fix bugs, grammar, introduce optional features, tests, or tooling SHOULD be made on an as-needed basis and MUST follow the normal release process. - Security fix releases MUST follow a special release process that substitutes the dev@opencontainers.org email for security@opencontainers.org. From 445ee2d3534ed721dcd9ccbfd9a2af27cf2a49cc Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 14 Jun 2016 12:00:45 -0700 Subject: [PATCH 124/245] proposals: release approval process: one month pre-releases Changing the release goal for projects to a "SHOULD monthly release" from the original bi-weekly. @diogomonica, @stevvooe, @mrunalp, @RobDolinMS were in that discussion https://github.com/opencontainers/tob/pull/15#discussion_r66520187 --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 5053355..4259b57 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -14,7 +14,7 @@ This approval process hopes to encourage early consistent consensus building dur **Timelines:** Specifications have a variety of different timelines in their lifecycle. In early stages the spec SHOULD release often to garner feedback. In later stages there will be bug fix releases, security fix releases, and major breaking change releases. Each of these should have a different level of notification. -- Pre-v1.0.0 specifications SHOULD release on a regular cadence and MUST follow the normal release process. In practice this should be a release every week or two. +- Pre-v1.0.0 specifications SHOULD release on a regular cadence and MUST follow the normal release process. In practice this should be a release every month. - Major specification releases that introduce new base or optional layers, break backwards compatibility, or add non-optional features MUST release at least three release candidates spaced a minimum of one week apart. In practice this means a major release like a v1.0.0 or v2.0.0 release will take 1 month at minimum: one week for v1.0.0-rc1, one week for v1.0.0-rc2, one week for v1.0.0-rc3, and one week for v1.0.0. Maintainers SHOULD strive to make zero breaking changes during this cycle of release candidates and SHOULD add an additional release candidate when a breaking change is introduced. For example if a breaking change is introduced in -rc2 then a -rc3 SHOULD be made following the normal release process. - Minor releases that fix bugs, grammar, introduce optional features, tests, or tooling SHOULD be made on an as-needed basis and MUST follow the normal release process. - Security fix releases MUST follow a special release process that substitutes the dev@opencontainers.org email for security@opencontainers.org. From f62909474c40402a930e7af56fd47dd1970be0be Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 14 Jun 2016 12:02:44 -0700 Subject: [PATCH 125/245] proposals: release approval process: use consistent language for rejects Fix up the language around REJECTs so it is easier to understand. The basic premise is that a release may continue with REJECTs if 2/3 of the maintainers vote to make the release. But, the maintainers SHOULD discuss and allow time for any REJECTs to become LGTMs. Spread over two discussions: [1](https://github.com/opencontainers/tob/pull/15/files/bdfa70d70f093146bc730be2576586ec8ed57cca#r66519789) and [2](https://github.com/opencontainers/tob/pull/15/files/bdfa70d70f093146bc730be2576586ec8ed57cca#r66668148) --- GOVERNANCE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 4259b57..567327b 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -10,7 +10,7 @@ This approval process hopes to encourage early consistent consensus building dur **Making a release:** OCI specification projects MUST announce intentions to release with two project maintainer sponsors (listed in the repo MAINTAINERS file) on the dev@opencontainers.org mailing list. After the announcement a two-thirds super majority of project maintainers MUST reply to the list with an LGTM within one week for the release to be approved. The maintainers MUST wait a full week for maintainers to reply but if all maintainers reply with an LGTM then the release MAY release earlier except in the case of a major releases. -**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with a single REJECT as long two-thirds of the project maintainers approved the release. +**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long two-thirds of the project maintainers approved the release. **Timelines:** Specifications have a variety of different timelines in their lifecycle. In early stages the spec SHOULD release often to garner feedback. In later stages there will be bug fix releases, security fix releases, and major breaking change releases. Each of these should have a different level of notification. @@ -23,6 +23,6 @@ This approval process hopes to encourage early consistent consensus building dur **Making a release:** OCI application projects MUST announce intentions to release with two project maintainer sponsors on the dev@opencontainers.org mailing list. After the announcement at least one more project maintainer MUST reply to the list with an LGTM within one week for the release to be approved. The maintainers SHOULD wait one week for maintainers to reply and review. If all maintainers reply with an LGTM then the release MAY release earlier. -**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with a single REJECT. +**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long two-thirds of the project maintainers approved the release. Security fix releases MUST follow a special release process that substitutes the dev@opencontainers.org email for security@opencontainers.org. From c15c0e2009e93e5b6fc78c1efea24ac21ad5a758 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 14 Jun 2016 12:11:46 -0700 Subject: [PATCH 126/245] proposals: release approval process: clarify utility of GitHub Based on discussion with @wking and @stevvooe https://github.com/opencontainers/tob/pull/15/files/bdfa70d70f093146bc730be2576586ec8ed57cca#r66543381 --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 567327b..50df019 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -6,7 +6,7 @@ This approval process hopes to encourage early consistent consensus building dur ## Specifications -**Planning a release:** Every OCI specification project SHOULD hold a weekly meeting that involves maintainers reviewing pull requests, debating outstanding issues, and planning releases. This meeting MUST be advertised on the project README and MAY happen on a phone call, video conference, or on IRC. Maintainers MUST send updates to the dev@opencontainers.org with results of these meetings. Maintainers MAY change the meeting cadence once a specification has reached v1.0.0. The meeting cadence MUST NOT be greater than once every four weeks The release plans, corresponding milestones and estimated due dates MUST be published on GitHub (e.g. https://github.com/opencontainers/runtime-spec/milestones). +**Planning a release:** Every OCI specification project SHOULD hold a weekly meeting that involves maintainers reviewing pull requests, debating outstanding issues, and planning releases. This meeting MUST be advertised on the project README and MAY happen on a phone call, video conference, or on IRC. Maintainers MUST send updates to the dev@opencontainers.org with results of these meetings. Maintainers MAY change the meeting cadence once a specification has reached v1.0.0. The meeting cadence MUST NOT be greater than once every four weeks The release plans, corresponding milestones and estimated due dates MUST be published on GitHub (e.g. https://github.com/opencontainers/runtime-spec/milestones). GitHub milestones and issues are only used for community organization and all releases MUST follow this process. **Making a release:** OCI specification projects MUST announce intentions to release with two project maintainer sponsors (listed in the repo MAINTAINERS file) on the dev@opencontainers.org mailing list. After the announcement a two-thirds super majority of project maintainers MUST reply to the list with an LGTM within one week for the release to be approved. The maintainers MUST wait a full week for maintainers to reply but if all maintainers reply with an LGTM then the release MAY release earlier except in the case of a major releases. From 3fd90e89cf1c5e7c369d5771df2fbd9116963384 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 14 Jun 2016 12:20:48 -0700 Subject: [PATCH 127/245] proposals: release-approval-process: add voting members language The intention of the voting members language is to ensure that releases can proceed even if people are unresponsive, on vacation, etc without ambiguity. This is similar to how the TOB operates. Identified by @wking here: https://github.com/opencontainers/tob/pull/15#discussion_r67036851 --- GOVERNANCE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 50df019..bc02337 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -8,9 +8,9 @@ This approval process hopes to encourage early consistent consensus building dur **Planning a release:** Every OCI specification project SHOULD hold a weekly meeting that involves maintainers reviewing pull requests, debating outstanding issues, and planning releases. This meeting MUST be advertised on the project README and MAY happen on a phone call, video conference, or on IRC. Maintainers MUST send updates to the dev@opencontainers.org with results of these meetings. Maintainers MAY change the meeting cadence once a specification has reached v1.0.0. The meeting cadence MUST NOT be greater than once every four weeks The release plans, corresponding milestones and estimated due dates MUST be published on GitHub (e.g. https://github.com/opencontainers/runtime-spec/milestones). GitHub milestones and issues are only used for community organization and all releases MUST follow this process. -**Making a release:** OCI specification projects MUST announce intentions to release with two project maintainer sponsors (listed in the repo MAINTAINERS file) on the dev@opencontainers.org mailing list. After the announcement a two-thirds super majority of project maintainers MUST reply to the list with an LGTM within one week for the release to be approved. The maintainers MUST wait a full week for maintainers to reply but if all maintainers reply with an LGTM then the release MAY release earlier except in the case of a major releases. +**Making a release:** OCI specification projects MUST announce intentions to release with two project maintainer sponsors (listed in the repo MAINTAINERS file) on the dev@opencontainers.org mailing list. After the announcement two-thirds of the voting project maintainers MUST reply to the list with an LGTM within one week for the release to be approved. The maintainers MUST wait a full week for maintainers to reply but if all maintainers reply with an LGTM then the release MAY release earlier except in the case of a major releases. -**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long two-thirds of the project maintainers approved the release. +**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long two-thirds of the voting project maintainers approved the release. **Timelines:** Specifications have a variety of different timelines in their lifecycle. In early stages the spec SHOULD release often to garner feedback. In later stages there will be bug fix releases, security fix releases, and major breaking change releases. Each of these should have a different level of notification. @@ -23,6 +23,6 @@ This approval process hopes to encourage early consistent consensus building dur **Making a release:** OCI application projects MUST announce intentions to release with two project maintainer sponsors on the dev@opencontainers.org mailing list. After the announcement at least one more project maintainer MUST reply to the list with an LGTM within one week for the release to be approved. The maintainers SHOULD wait one week for maintainers to reply and review. If all maintainers reply with an LGTM then the release MAY release earlier. -**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long two-thirds of the project maintainers approved the release. +**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long two-thirds of the voting project maintainers approved the release. Security fix releases MUST follow a special release process that substitutes the dev@opencontainers.org email for security@opencontainers.org. From 267f916549933cca37d869ed146f824fefe5ad1d Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Wed, 15 Jun 2016 13:18:53 -0700 Subject: [PATCH 128/245] proposals: release approval process: add quorum language Based on discussion with wking and mrunalp participating and Stephen Day acking in IRC: https://github.com/opencontainers/tob/pull/15#discussion_r67041206 --- GOVERNANCE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index bc02337..29557dd 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -8,9 +8,9 @@ This approval process hopes to encourage early consistent consensus building dur **Planning a release:** Every OCI specification project SHOULD hold a weekly meeting that involves maintainers reviewing pull requests, debating outstanding issues, and planning releases. This meeting MUST be advertised on the project README and MAY happen on a phone call, video conference, or on IRC. Maintainers MUST send updates to the dev@opencontainers.org with results of these meetings. Maintainers MAY change the meeting cadence once a specification has reached v1.0.0. The meeting cadence MUST NOT be greater than once every four weeks The release plans, corresponding milestones and estimated due dates MUST be published on GitHub (e.g. https://github.com/opencontainers/runtime-spec/milestones). GitHub milestones and issues are only used for community organization and all releases MUST follow this process. -**Making a release:** OCI specification projects MUST announce intentions to release with two project maintainer sponsors (listed in the repo MAINTAINERS file) on the dev@opencontainers.org mailing list. After the announcement two-thirds of the voting project maintainers MUST reply to the list with an LGTM within one week for the release to be approved. The maintainers MUST wait a full week for maintainers to reply but if all maintainers reply with an LGTM then the release MAY release earlier except in the case of a major releases. +**Making a release:** OCI specification projects MUST announce intentions to release with two project maintainer sponsors (listed in the repo MAINTAINERS file) on the dev@opencontainers.org mailing list. Voting on proposed releases should happen on the dev@opencontainers.org mailing list with maintainers posting LGTM or REJECT. Maintainers may also explicitly not vote by posting ABSTAIN (which is useful to revert a previous vote). Maintainers may post multiple times (e.g. as they revise their position based on feeback), but only their final post counts as their vote. A proposed release passes if two-thirds of votes cast, a quorum having voted, are in favor of the release. A quorum is established when at two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. -**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long two-thirds of the voting project maintainers approved the release. +**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long as a quorom has been established and two-thirds of the voting project maintainers approved the release. A quorum is established when at two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. **Timelines:** Specifications have a variety of different timelines in their lifecycle. In early stages the spec SHOULD release often to garner feedback. In later stages there will be bug fix releases, security fix releases, and major breaking change releases. Each of these should have a different level of notification. From eecc4fec01b4dc57645610861ee6a677262d6f41 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Wed, 15 Jun 2016 13:21:39 -0700 Subject: [PATCH 129/245] proposals: release approval process: add language about mailing list This addresses @stevvooe's concern about GitHub issues being the only medium for discussion of a reject. @wking and @philips were involved in this discussion: https://github.com/opencontainers/tob/pull/15#discussion_r67046737 --- GOVERNANCE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 29557dd..aeffd65 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -10,7 +10,7 @@ This approval process hopes to encourage early consistent consensus building dur **Making a release:** OCI specification projects MUST announce intentions to release with two project maintainer sponsors (listed in the repo MAINTAINERS file) on the dev@opencontainers.org mailing list. Voting on proposed releases should happen on the dev@opencontainers.org mailing list with maintainers posting LGTM or REJECT. Maintainers may also explicitly not vote by posting ABSTAIN (which is useful to revert a previous vote). Maintainers may post multiple times (e.g. as they revise their position based on feeback), but only their final post counts as their vote. A proposed release passes if two-thirds of votes cast, a quorum having voted, are in favor of the release. A quorum is established when at two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. -**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long as a quorom has been established and two-thirds of the voting project maintainers approved the release. A quorum is established when at two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. +**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues or inline in the mailing list reply. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long as a quorom has been established and two-thirds of the voting project maintainers approved the release. A quorum is established when at two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. **Timelines:** Specifications have a variety of different timelines in their lifecycle. In early stages the spec SHOULD release often to garner feedback. In later stages there will be bug fix releases, security fix releases, and major breaking change releases. Each of these should have a different level of notification. @@ -23,6 +23,6 @@ This approval process hopes to encourage early consistent consensus building dur **Making a release:** OCI application projects MUST announce intentions to release with two project maintainer sponsors on the dev@opencontainers.org mailing list. After the announcement at least one more project maintainer MUST reply to the list with an LGTM within one week for the release to be approved. The maintainers SHOULD wait one week for maintainers to reply and review. If all maintainers reply with an LGTM then the release MAY release earlier. -**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long two-thirds of the voting project maintainers approved the release. +**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues or inline in the mailing list reply. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long two-thirds of the voting project maintainers approved the release. Security fix releases MUST follow a special release process that substitutes the dev@opencontainers.org email for security@opencontainers.org. From f2148b694be8df6c5602eddb1cd7fbd518026a5d Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Wed, 15 Jun 2016 13:42:19 -0700 Subject: [PATCH 130/245] proposals: release approval process: add information to projects Projects have a happy path and a slow path. The happy path is a release with maintainers agreeing and a timeout. The slow path has rejects and quorums. Based on discussion with @wking https://github.com/opencontainers/tob/pull/15#discussion_r67241092 --- GOVERNANCE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index aeffd65..9bfd65f 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -10,7 +10,7 @@ This approval process hopes to encourage early consistent consensus building dur **Making a release:** OCI specification projects MUST announce intentions to release with two project maintainer sponsors (listed in the repo MAINTAINERS file) on the dev@opencontainers.org mailing list. Voting on proposed releases should happen on the dev@opencontainers.org mailing list with maintainers posting LGTM or REJECT. Maintainers may also explicitly not vote by posting ABSTAIN (which is useful to revert a previous vote). Maintainers may post multiple times (e.g. as they revise their position based on feeback), but only their final post counts as their vote. A proposed release passes if two-thirds of votes cast, a quorum having voted, are in favor of the release. A quorum is established when at two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. -**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues or inline in the mailing list reply. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long as a quorom has been established and two-thirds of the voting project maintainers approved the release. A quorum is established when at two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. +**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues or inline in the mailing list reply. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long as a quorom has been established and two-thirds of the voting project maintainers approved the release. A quorum is established when at two-thirds of maintainers have voted. **Timelines:** Specifications have a variety of different timelines in their lifecycle. In early stages the spec SHOULD release often to garner feedback. In later stages there will be bug fix releases, security fix releases, and major breaking change releases. Each of these should have a different level of notification. @@ -23,6 +23,6 @@ This approval process hopes to encourage early consistent consensus building dur **Making a release:** OCI application projects MUST announce intentions to release with two project maintainer sponsors on the dev@opencontainers.org mailing list. After the announcement at least one more project maintainer MUST reply to the list with an LGTM within one week for the release to be approved. The maintainers SHOULD wait one week for maintainers to reply and review. If all maintainers reply with an LGTM then the release MAY release earlier. -**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues or inline in the mailing list reply. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long two-thirds of the voting project maintainers approved the release. +**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues or inline in the mailing list reply. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long as a quorom has been established and two-thirds of the voting project maintainers approved the release. A quorum is established when at two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. Security fix releases MUST follow a special release process that substitutes the dev@opencontainers.org email for security@opencontainers.org. From 89afeeb59ae55fd3656b11fd6f71b7b86ea282a2 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Wed, 15 Jun 2016 15:38:00 -0700 Subject: [PATCH 131/245] proposals: release approval process: improve REJECT feedback Instead of being prescriptive provide suggestions instead for how to provide release REJECTS feedback. Based on feedback from Stephen Day and @wking. --- GOVERNANCE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 9bfd65f..2d61f35 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -10,7 +10,7 @@ This approval process hopes to encourage early consistent consensus building dur **Making a release:** OCI specification projects MUST announce intentions to release with two project maintainer sponsors (listed in the repo MAINTAINERS file) on the dev@opencontainers.org mailing list. Voting on proposed releases should happen on the dev@opencontainers.org mailing list with maintainers posting LGTM or REJECT. Maintainers may also explicitly not vote by posting ABSTAIN (which is useful to revert a previous vote). Maintainers may post multiple times (e.g. as they revise their position based on feeback), but only their final post counts as their vote. A proposed release passes if two-thirds of votes cast, a quorum having voted, are in favor of the release. A quorum is established when at two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. -**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues or inline in the mailing list reply. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long as a quorom has been established and two-thirds of the voting project maintainers approved the release. A quorum is established when at two-thirds of maintainers have voted. +**Rejecting a release:** A project maintainer MAY choose to reply with REJECT. A project maintainer posting a REJECT MUST include a list of concerns or links to written documentation for those concerns (e.g. GitHub issues or mailing-list threads). The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long as a quorom has been established and two-thirds of the voting project maintainers approved the release. A quorum is established when at two-thirds of maintainers have voted. **Timelines:** Specifications have a variety of different timelines in their lifecycle. In early stages the spec SHOULD release often to garner feedback. In later stages there will be bug fix releases, security fix releases, and major breaking change releases. Each of these should have a different level of notification. @@ -23,6 +23,6 @@ This approval process hopes to encourage early consistent consensus building dur **Making a release:** OCI application projects MUST announce intentions to release with two project maintainer sponsors on the dev@opencontainers.org mailing list. After the announcement at least one more project maintainer MUST reply to the list with an LGTM within one week for the release to be approved. The maintainers SHOULD wait one week for maintainers to reply and review. If all maintainers reply with an LGTM then the release MAY release earlier. -**Rejecting a release:** A project maintainer MAY choose to reply with REJECT and MUST include a list of concerns filed as GitHub issues or inline in the mailing list reply. The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long as a quorom has been established and two-thirds of the voting project maintainers approved the release. A quorum is established when at two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. +**Rejecting a release:** A project maintainer MAY choose to reply with REJECT. A project maintainer posting a REJECT MUST include a list of concerns or links to written documentation for those concerns (e.g. GitHub issues or mailing-list threads). The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long as a quorom has been established and two-thirds of the voting project maintainers approved the release. A quorum is established when at two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. Security fix releases MUST follow a special release process that substitutes the dev@opencontainers.org email for security@opencontainers.org. From 775db84941618b3818bb0cc414db9ec79de60b87 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Wed, 15 Jun 2016 18:10:15 -0700 Subject: [PATCH 132/245] proposals: release-approval-process: fixup additional typos Fixup qourum typos based on feedback from @wking. --- GOVERNANCE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 2d61f35..d43ed44 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -8,9 +8,9 @@ This approval process hopes to encourage early consistent consensus building dur **Planning a release:** Every OCI specification project SHOULD hold a weekly meeting that involves maintainers reviewing pull requests, debating outstanding issues, and planning releases. This meeting MUST be advertised on the project README and MAY happen on a phone call, video conference, or on IRC. Maintainers MUST send updates to the dev@opencontainers.org with results of these meetings. Maintainers MAY change the meeting cadence once a specification has reached v1.0.0. The meeting cadence MUST NOT be greater than once every four weeks The release plans, corresponding milestones and estimated due dates MUST be published on GitHub (e.g. https://github.com/opencontainers/runtime-spec/milestones). GitHub milestones and issues are only used for community organization and all releases MUST follow this process. -**Making a release:** OCI specification projects MUST announce intentions to release with two project maintainer sponsors (listed in the repo MAINTAINERS file) on the dev@opencontainers.org mailing list. Voting on proposed releases should happen on the dev@opencontainers.org mailing list with maintainers posting LGTM or REJECT. Maintainers may also explicitly not vote by posting ABSTAIN (which is useful to revert a previous vote). Maintainers may post multiple times (e.g. as they revise their position based on feeback), but only their final post counts as their vote. A proposed release passes if two-thirds of votes cast, a quorum having voted, are in favor of the release. A quorum is established when at two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. +**Making a release:** OCI specification projects MUST announce intentions to release with two project maintainer sponsors (listed in the repo MAINTAINERS file) on the dev@opencontainers.org mailing list. Voting on proposed releases should happen on the dev@opencontainers.org mailing list with maintainers posting LGTM or REJECT. Maintainers may also explicitly not vote by posting ABSTAIN (which is useful to revert a previous vote). Maintainers may post multiple times (e.g. as they revise their position based on feeback), but only their final post counts as their vote. A proposed release passes if two-thirds of votes cast, a quorum having voted, are in favor of the release. A quorum is established when at least two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. -**Rejecting a release:** A project maintainer MAY choose to reply with REJECT. A project maintainer posting a REJECT MUST include a list of concerns or links to written documentation for those concerns (e.g. GitHub issues or mailing-list threads). The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long as a quorom has been established and two-thirds of the voting project maintainers approved the release. A quorum is established when at two-thirds of maintainers have voted. +**Rejecting a release:** A project maintainer MAY choose to reply with REJECT. A project maintainer posting a REJECT MUST include a list of concerns or links to written documentation for those concerns (e.g. GitHub issues or mailing-list threads). The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long as a quorom has been established and two-thirds of the voting project maintainers approved the release. **Timelines:** Specifications have a variety of different timelines in their lifecycle. In early stages the spec SHOULD release often to garner feedback. In later stages there will be bug fix releases, security fix releases, and major breaking change releases. Each of these should have a different level of notification. @@ -23,6 +23,6 @@ This approval process hopes to encourage early consistent consensus building dur **Making a release:** OCI application projects MUST announce intentions to release with two project maintainer sponsors on the dev@opencontainers.org mailing list. After the announcement at least one more project maintainer MUST reply to the list with an LGTM within one week for the release to be approved. The maintainers SHOULD wait one week for maintainers to reply and review. If all maintainers reply with an LGTM then the release MAY release earlier. -**Rejecting a release:** A project maintainer MAY choose to reply with REJECT. A project maintainer posting a REJECT MUST include a list of concerns or links to written documentation for those concerns (e.g. GitHub issues or mailing-list threads). The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long as a quorom has been established and two-thirds of the voting project maintainers approved the release. A quorum is established when at two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. +**Rejecting a release:** A project maintainer MAY choose to reply with REJECT. A project maintainer posting a REJECT MUST include a list of concerns or links to written documentation for those concerns (e.g. GitHub issues or mailing-list threads). The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long as a quorom has been established and two-thirds of the voting project maintainers approved the release. A quorum is established when at least two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. Security fix releases MUST follow a special release process that substitutes the dev@opencontainers.org email for security@opencontainers.org. From 40966cf4c25cf61a956342d212167ceb2de9b293 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 15 Jun 2016 21:39:42 -0700 Subject: [PATCH 133/245] release-approval: Shuffle to make more DRY Avoid duplication by collecting common ideas (e.g. list-based voting) in their own sections. After this reshuffling, it became apparent that there were no special application restrictions, so I added additional language to motivate the specification-specific additions. Signed-off-by: W. Trevor King --- GOVERNANCE.md | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index d43ed44..f8cd33e 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -1,28 +1,38 @@ # OCI Project Release Approval Process v1.0 -OCI projects need a standard process for making releases so the community of maintainers can consistently know when something can be tagged and released. As the OCI maintains three categories of projects: specifications, applications, and conformance/testing tools we will set different rules for each. +OCI projects need a standard process for making releases so the community of maintainers can consistently know when something can be tagged and released. This approval process hopes to encourage early consistent consensus building during project and specification development. The mechanisms used are regular community communication on the mailing list about progress, scheduled meetings for issue resolution and release triage, and regularly paced and communicated releases. An anti-pattern that we want to avoid is heavy development or discussions "late cycle" around major releases. We want do build a community that is involved and communicates consistently through all releases instead of relying on "silent periods" as a judge of stability. -This approval process hopes to encourage early consistent consensus building during project and specification development. The mechanisms used are regular community communication on the mailing list about progress, scheduled meetings for issue resolution and release triage, and regularly paced and communicated releases. An anti-pattern that we want to avoid is heavy development or discussions "late cycle" around major releases. We want do build a community that is involved and communicates consistently through all releases instead of relying on "silent periods" as a judge of stability. +## List-based voting -## Specifications +**Making a release:** Maintainers (listed in the repository's MAINTAINERS file) MUST announce intentions to release on the dev@opencontainers.org mailing list with another maintainer as a co-sponsor. Voting on proposed releases SHOULD happen on the dev@opencontainers.org mailing list (except [security fixes](#security-fixes)) with maintainers posting LGTM or REJECT. Maintainers may also explicitly not vote by posting ABSTAIN (which is useful to revert a previous vote). Maintainers may post multiple times (e.g. as they revise their position based on feeback), but only their final post counts in the final tally. A proposed release passes if two-thirds of votes cast, a quorum having voted, are in favor of the release. A quorum is established when at least two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. + +**Rejecting a release:** A project maintainer MAY choose to reply with REJECT. A project maintainer posting a REJECT MUST include a list of concerns or links to written documentation for those concerns (e.g. GitHub issues or mailing-list threads). The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY pass with REJECTs, as outlined in the previous paragraph. + +## Security fixes + +Security fix releases MUST use security@opencontainers.org instead of dev@opencontainers.org, but should otherwise follow the standard [list-based voting process](#list-based-voting). -**Planning a release:** Every OCI specification project SHOULD hold a weekly meeting that involves maintainers reviewing pull requests, debating outstanding issues, and planning releases. This meeting MUST be advertised on the project README and MAY happen on a phone call, video conference, or on IRC. Maintainers MUST send updates to the dev@opencontainers.org with results of these meetings. Maintainers MAY change the meeting cadence once a specification has reached v1.0.0. The meeting cadence MUST NOT be greater than once every four weeks The release plans, corresponding milestones and estimated due dates MUST be published on GitHub (e.g. https://github.com/opencontainers/runtime-spec/milestones). GitHub milestones and issues are only used for community organization and all releases MUST follow this process. +## Parallel proposals -**Making a release:** OCI specification projects MUST announce intentions to release with two project maintainer sponsors (listed in the repo MAINTAINERS file) on the dev@opencontainers.org mailing list. Voting on proposed releases should happen on the dev@opencontainers.org mailing list with maintainers posting LGTM or REJECT. Maintainers may also explicitly not vote by posting ABSTAIN (which is useful to revert a previous vote). Maintainers may post multiple times (e.g. as they revise their position based on feeback), but only their final post counts as their vote. A proposed release passes if two-thirds of votes cast, a quorum having voted, are in favor of the release. A quorum is established when at least two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. +A single repository MAY have several release proposals in parallel. However each proposed release after the first MUST be based on a previous release that has already landed. + +For example, runtime-spec maintainers may propose a v1.0.0-rc2 on the 1st of the month and a v0.9.1 bugfix on the 2nd of the month. They may not propose a v1.0.0-rc3 until the v1.0.0-rc2 is accepted (on the 7th if the vote initiated on the 1st passes). + +## Specifications -**Rejecting a release:** A project maintainer MAY choose to reply with REJECT. A project maintainer posting a REJECT MUST include a list of concerns or links to written documentation for those concerns (e.g. GitHub issues or mailing-list threads). The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long as a quorom has been established and two-thirds of the voting project maintainers approved the release. +The OCI maintains three categories of projects: specifications, applications, and conformance-testing tools. However, specification releases have special restrictions in the [OCI charter][charter]: -**Timelines:** Specifications have a variety of different timelines in their lifecycle. In early stages the spec SHOULD release often to garner feedback. In later stages there will be bug fix releases, security fix releases, and major breaking change releases. Each of these should have a different level of notification. +* They are the target of backwards compatibility (§7.g), and +* They are subject to the OFWa patent grant (§8.d and e). -- Pre-v1.0.0 specifications SHOULD release on a regular cadence and MUST follow the normal release process. In practice this should be a release every month. -- Major specification releases that introduce new base or optional layers, break backwards compatibility, or add non-optional features MUST release at least three release candidates spaced a minimum of one week apart. In practice this means a major release like a v1.0.0 or v2.0.0 release will take 1 month at minimum: one week for v1.0.0-rc1, one week for v1.0.0-rc2, one week for v1.0.0-rc3, and one week for v1.0.0. Maintainers SHOULD strive to make zero breaking changes during this cycle of release candidates and SHOULD add an additional release candidate when a breaking change is introduced. For example if a breaking change is introduced in -rc2 then a -rc3 SHOULD be made following the normal release process. -- Minor releases that fix bugs, grammar, introduce optional features, tests, or tooling SHOULD be made on an as-needed basis and MUST follow the normal release process. -- Security fix releases MUST follow a special release process that substitutes the dev@opencontainers.org email for security@opencontainers.org. +To avoid unfortunate side effects (onerous backwards compatibity requirements or Member resignations), the following additional procedures apply to specification releases: -## Conformance/Testing and Applications Releases +**Planning a release:** Every OCI specification project SHOULD hold meetings that involves maintainers reviewing pull requests, debating outstanding issues, and planning releases. This meeting MUST be advertised on the project README and MAY happen on a phone call, video conference, or on IRC. Maintainers MUST send updates to the dev@opencontainers.org with results of these meetings. Before the specification reaches v1.0.0, the meetings SHOULD be weekly. Once a specification has reached v1.0.0, the maintainers may alter the cadence, but the meeting cadence MUST NOT be greater than once every four weeks. The release plans, corresponding milestones and estimated due dates MUST be published on GitHub (e.g. https://github.com/opencontainers/runtime-spec/milestones). GitHub milestones and issues are only used for community organization and all releases MUST follow the [list-based voting process](#list-based-voting). -**Making a release:** OCI application projects MUST announce intentions to release with two project maintainer sponsors on the dev@opencontainers.org mailing list. After the announcement at least one more project maintainer MUST reply to the list with an LGTM within one week for the release to be approved. The maintainers SHOULD wait one week for maintainers to reply and review. If all maintainers reply with an LGTM then the release MAY release earlier. +**Timelines:** Specifications have a variety of different timelines in their lifecycle. -**Rejecting a release:** A project maintainer MAY choose to reply with REJECT. A project maintainer posting a REJECT MUST include a list of concerns or links to written documentation for those concerns (e.g. GitHub issues or mailing-list threads). The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY continue with REJECTs as long as a quorom has been established and two-thirds of the voting project maintainers approved the release. A quorum is established when at least two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. +- Pre-v1.0.0 specifications SHOULD release on a monthly cadence to garner feedback. +- Major specification releases MUST release at least three release candidates spaced a minimum of one week apart. This means a major release like a v1.0.0 or v2.0.0 release will take 1 month at minimum: one week for rc1, one week for rc2, one week for rc3, and one week for the major release itself. Maintainers SHOULD strive to make zero breaking changes during this cycle of release candidates and SHOULD add restart the three-candidate count when a breaking change is introduced. For example if a breaking change is introduced in v1.0.0-rc2 then the series would end with v1.0.0-rc4 and v1.0.0. +- Minor and patch releases SHOULD be made on an as-needed basis. -Security fix releases MUST follow a special release process that substitutes the dev@opencontainers.org email for security@opencontainers.org. +[charter]: https://www.opencontainers.org/about/governance From c340e73f4a9c58324e28e06dff2d88e0bab6e082 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 16 Jun 2016 23:14:08 -0700 Subject: [PATCH 134/245] release-approval: Add non-spec unanimous quorum reduction https://github.com/philips/tob/pull/1#issuecomment-226686812 Signed-off-by: W. Trevor King --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index f8cd33e..f746bdd 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -4,7 +4,7 @@ OCI projects need a standard process for making releases so the community of mai ## List-based voting -**Making a release:** Maintainers (listed in the repository's MAINTAINERS file) MUST announce intentions to release on the dev@opencontainers.org mailing list with another maintainer as a co-sponsor. Voting on proposed releases SHOULD happen on the dev@opencontainers.org mailing list (except [security fixes](#security-fixes)) with maintainers posting LGTM or REJECT. Maintainers may also explicitly not vote by posting ABSTAIN (which is useful to revert a previous vote). Maintainers may post multiple times (e.g. as they revise their position based on feeback), but only their final post counts in the final tally. A proposed release passes if two-thirds of votes cast, a quorum having voted, are in favor of the release. A quorum is established when at least two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. +**Making a release:** Maintainers (listed in the repository's MAINTAINERS file) MUST announce intentions to release on the dev@opencontainers.org mailing list with another maintainer as a co-sponsor. Voting on proposed releases SHOULD happen on the dev@opencontainers.org mailing list (except [security fixes](#security-fixes)) with maintainers posting LGTM or REJECT. Maintainers may also explicitly not vote by posting ABSTAIN (which is useful to revert a previous vote). Maintainers may post multiple times (e.g. as they revise their position based on feeback), but only their final post counts in the final tally. A proposed release passes if two-thirds of votes cast, a quorum having voted, are in favor of the release. A quorum is established when at least two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. For projects that are not specifications, a proposed release also passes if the final tally is at least three LGTMs and no REJECTs, even if three votes does not meet the usual two-thirds quorum. **Rejecting a release:** A project maintainer MAY choose to reply with REJECT. A project maintainer posting a REJECT MUST include a list of concerns or links to written documentation for those concerns (e.g. GitHub issues or mailing-list threads). The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY pass with REJECTs, as outlined in the previous paragraph. From af1013d882b942417d2df96832c5f9391b74350e Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Wed, 22 Jun 2016 09:38:42 -0700 Subject: [PATCH 135/245] proposals: release-approval-process fix a grammar thing --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index f746bdd..ff811a8 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -1,6 +1,6 @@ # OCI Project Release Approval Process v1.0 -OCI projects need a standard process for making releases so the community of maintainers can consistently know when something can be tagged and released. This approval process hopes to encourage early consistent consensus building during project and specification development. The mechanisms used are regular community communication on the mailing list about progress, scheduled meetings for issue resolution and release triage, and regularly paced and communicated releases. An anti-pattern that we want to avoid is heavy development or discussions "late cycle" around major releases. We want do build a community that is involved and communicates consistently through all releases instead of relying on "silent periods" as a judge of stability. +OCI projects need a standard process for making releases so the community of maintainers can consistently know when something can be tagged and released. This approval process hopes to encourage early consistent consensus building during project and specification development. The mechanisms used are regular community communication on the mailing list about progress, scheduled meetings for issue resolution and release triage, and regularly paced and communicated releases. An anti-pattern that we want to avoid is heavy development or discussions "late cycle" around major releases. We want to build a community that is involved and communicates consistently through all releases instead of relying on "silent periods" as a judge of stability. ## List-based voting From be104567deeb208b3e652886415a8723a19e4ff3 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Wed, 22 Jun 2016 10:16:28 -0700 Subject: [PATCH 136/245] proposal: fix a typo Reported by Tianon https://github.com/opencontainers/tob/pull/15#discussion_r68092550 --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index ff811a8..a8fb86f 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -32,7 +32,7 @@ To avoid unfortunate side effects (onerous backwards compatibity requirements or **Timelines:** Specifications have a variety of different timelines in their lifecycle. - Pre-v1.0.0 specifications SHOULD release on a monthly cadence to garner feedback. -- Major specification releases MUST release at least three release candidates spaced a minimum of one week apart. This means a major release like a v1.0.0 or v2.0.0 release will take 1 month at minimum: one week for rc1, one week for rc2, one week for rc3, and one week for the major release itself. Maintainers SHOULD strive to make zero breaking changes during this cycle of release candidates and SHOULD add restart the three-candidate count when a breaking change is introduced. For example if a breaking change is introduced in v1.0.0-rc2 then the series would end with v1.0.0-rc4 and v1.0.0. +- Major specification releases MUST release at least three release candidates spaced a minimum of one week apart. This means a major release like a v1.0.0 or v2.0.0 release will take 1 month at minimum: one week for rc1, one week for rc2, one week for rc3, and one week for the major release itself. Maintainers SHOULD strive to make zero breaking changes during this cycle of release candidates and SHOULD restart the three-candidate count when a breaking change is introduced. For example if a breaking change is introduced in v1.0.0-rc2 then the series would end with v1.0.0-rc4 and v1.0.0. - Minor and patch releases SHOULD be made on an as-needed basis. [charter]: https://www.opencontainers.org/about/governance From 86b3087364cbcefba470d7d5b2802ae022906a14 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Fri, 24 Jun 2016 17:46:46 -0700 Subject: [PATCH 137/245] proposals: release approval process explain security@ email Expand a bit more information about the security@ alias and who is involved in a security sensitive release. --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index a8fb86f..f87a400 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -10,7 +10,7 @@ OCI projects need a standard process for making releases so the community of mai ## Security fixes -Security fix releases MUST use security@opencontainers.org instead of dev@opencontainers.org, but should otherwise follow the standard [list-based voting process](#list-based-voting). +Security fix releases MUST use security@opencontainers.org instead of dev@opencontainers.org, but should otherwise follow the standard [list-based voting process](#list-based-voting). The security@opencontainers.org email includes all members of the TOB; the TOB will guide the security sensitive release with project maintainers. ## Parallel proposals From bcffc06b24dc6cdc6dd29948969798ae6c0b1e74 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 1 Jul 2016 22:16:04 -0700 Subject: [PATCH 138/245] manifest: Punt mediaType, size, and digest to descriptor docs 1149faca (manifest: use descriptor doc as canonical source, 2016-06-15, #143) was a step in this direction. But we can DRY things up a bit more by punting those three properties entirely and just saying "it's a descriptor", and then listing extensions, if any. Signed-off-by: W. Trevor King --- manifest.md | 56 ++++------------------------------------------------- 1 file changed, 4 insertions(+), 52 deletions(-) diff --git a/manifest.md b/manifest.md index c9428fe..ea1a61f 100644 --- a/manifest.md +++ b/manifest.md @@ -35,22 +35,7 @@ A client will distinguish a manifest list from an image manifest based on the Co 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. - Properties of each object in the manifests list are: - - - **`mediaType`** *string* - - This REQUIRED property contains the MIME type of the referenced object. - (i.e. `application/vnd.oci.image.manifest.v1+json`) - - - **`size`** *int* - - This REQUIRED property specifies the size in bytes of the object. - This property exists so that a client will have an expected size for the content before validating. - If the length of the retrieved content does not match the specified length, the content should not be trusted. - - - **`digest`** *string* - - This REQUIRED property is the digest of the content, as defined by the [Descriptor](descriptor.md) digest format. + Each object in the manifest is a [descriptor](descriptor.md) with the following additional properties: - **`platform`** *object* @@ -146,51 +131,18 @@ Unlike the [Manifest List](#manifest-list), which contains information about a s This REQUIRED property contains the MIME type of the image manifest. For this version of the specification, this MUST be set to `application/vnd.oci.image.manifest.v1+json`. -- **`config`** *object* +- **`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](serialization.md#image-json-description). - Properties of `config` are: - - - **`mediaType`** *string* - - This REQUIRED property contains the MIME type of the referenced object. - (i.e. `application/vnd.oci.image.serialization.config.v1+json`) - - - **`size`** *int* - - This REQUIRED property specifies the size in bytes of the object. - This property exists so that a client will have an expected size for the content before validating. - If the length of the retrieved content does not match the specified length, the content should not be trusted. - - - **`digest`** *string* - - This REQUIRED property is the digest of the content, as defined by the [Descriptor](descriptor.md) digest format. - - **`layers`** *array* - The layer list MUST have the base image at index 0. + 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. - Properties of an item in the layers list are: - - - **`mediaType`** *string* - - This REQUIRED property contains the MIME type of the referenced object. - (i.e. `application/vnd.oci.image.serialization.rootfs.tar.gzip`) - - - **`size`** *int* - - This REQUIRED property specifies the size in bytes of the object. - This property exists so that a client will have an expected size for the content before validating. - If the length of the retrieved content does not match the specified length, the content should not be trusted. - - - **`digest`** *string* - - This REQUIRED property is the digest of the content, as defined by the [Descriptor](descriptor.md) digest format. - - **`annotations`** *hashmap* This OPTIONAL property contains arbitrary metadata for the manifest list. From 56629bb8651a2864c0ec890e4df9b33f306f455b Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 1 Jul 2016 22:22:57 -0700 Subject: [PATCH 139/245] manifest: Remove a tab-indent for "Subsequent layers..." The tab came in with the line in fa4c6e7c (Minor tweaks to manifest for clarity, 2016-05-06, #68), but the rest of this file indents with spaces. Signed-off-by: W. Trevor King --- manifest.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.md b/manifest.md index ea1a61f..d7331ab 100644 --- a/manifest.md +++ b/manifest.md @@ -140,7 +140,7 @@ Unlike the [Manifest List](#manifest-list), which contains information about a s 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. + 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`** *hashmap* From 6c04600738de12f8cc7230c9a9991c941777c63c Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Wed, 6 Jul 2016 19:44:48 -0700 Subject: [PATCH 140/245] Add a working call-in number Signed-off-by: Doug Davis --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b225c43..a10fe9b 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ When in doubt, start on the [mailing-list](#mailing-list). ## Weekly Call The contributors and maintainers of all OCI projects have a weekly meeting Wednesdays at 10:00 AM (USA Pacific.) -Everyone is welcome to participate via [UberConference web][UberConference] or audio-only: 646-494-8704 (no PIN needed.) +Everyone is welcome to participate via [UberConference web][UberConference] or audio-only: 888-587-9088 or 860-706-8529 (no PIN needed.) An initial agenda will be posted to the [mailing list](#mailing-list) earlier in the week, and everyone is welcome to propose additional topics or suggest other agenda alterations there. Minutes are posted to the [mailing list](#mailing-list) and minutes from past calls are archived to the [wiki](https://github.com/opencontainers/runtime-spec/wiki) for those who are unable to join the call. @@ -169,5 +169,5 @@ Read more on [How to Write a Git Commit Message](http://chris.beams.io/posts/git 8. When possible, one keyword to scope the change in the subject (i.e. "README: ...", "runtime: ...") -[UberConference]: https://www.uberconference.com/ssaul +[UberConference]: https://www.uberconference.com/opencontainers [irc-logs]: http://ircbot.wl.linuxfoundation.org/eavesdrop/%23opencontainers/ From ce633c117da634abe5f5637c889efe68a9f9ea1e Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 27 Jun 2016 09:24:04 -0400 Subject: [PATCH 141/245] Makefile: comments and simplify After mulling on https://github.com/opencontainers/image-spec/pull/148 perhaps removing the for loop is a bit more simple and safe. Signed-off-by: Vincent Batts --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8d983c0..e27e150 100644 --- a/Makefile +++ b/Makefile @@ -90,11 +90,12 @@ lint: test: go test -race ./... +## this uses https://github.com/Masterminds/glide and https://github.com/sgotti/glide-vc update-deps: glide update --strip-vcs --strip-vendor --update-vendored --delete glide-vc --only-code --no-tests # see http://sed.sourceforge.net/sed1line.txt - for f in $$(find vendor -type f); do sed -i -e :a -e '/^\n*$$/{$$d;N;ba' -e '}' $$f; done + find vendor -type f -exec sed -i -e :a -e '/^\n*$$/{$$d;N;ba' -e '}' "{}" \; img/%.png: img/%.dot dot -Tpng $^ > $@ From c732cc2e2ec654f5f28e937de5c55d02d229e84e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 24 Jun 2016 21:16:33 -0700 Subject: [PATCH 142/245] project-governance: Make voting more generic This is useful for more than release approval. For example, it's useful for updating the project governance document itself [1]. I've also tried to address Jason's other points, except for defining a "breaking change" (since that is tied up in [2]). New wording about motions and whatnot is pulled from Roberts' [3], see proposing a motion (RRoO I.4, p33) and seconding a motion (RRoO I.5, p36). The subject templates I just made up on my own after thinking over the initial proposal emails (e.g. [4]). I also pulled in the one-sentence pattern [5] since I was touching so much. [1]: https://groups.google.com/a/opencontainers.org/d/msg/dev/ik3MIDWq4Us/Zx1JUStXBAAJ Subject: Re: Vote Required: OCI Image Spec Release Process Date: Fri, 24 Jun 2016 16:58:58 -0700 Message-ID: [2]: https://github.com/opencontainers/tob/issues/16 [3]: http://archive.org/details/Robertsrulesofor00robe_201303 [4]: https://groups.google.com/a/opencontainers.org/forum/#!topic/dev/ik3MIDWq4Us Subject: Vote Required: OCI Image Spec Release Process Date: Thu, 23 Jun 2016 15:56:40 +0000 Message-ID: [5]: https://github.com/opencontainers/tob/pull/15#issuecomment-226247299 Signed-off-by: W. Trevor King --- GOVERNANCE.md | 112 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 97 insertions(+), 15 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index f87a400..2aeb3d3 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -1,38 +1,120 @@ -# OCI Project Release Approval Process v1.0 +# Project governance -OCI projects need a standard process for making releases so the community of maintainers can consistently know when something can be tagged and released. This approval process hopes to encourage early consistent consensus building during project and specification development. The mechanisms used are regular community communication on the mailing list about progress, scheduled meetings for issue resolution and release triage, and regularly paced and communicated releases. An anti-pattern that we want to avoid is heavy development or discussions "late cycle" around major releases. We want to build a community that is involved and communicates consistently through all releases instead of relying on "silent periods" as a judge of stability. +The [OCI charter][charter] §5.b.viii tasks an OCI Project's maintainers (listed in the repository's MAINTAINERS file and sometimes referred to as "the TDC", [§5.e][charter]) with: -## List-based voting +> Creating, maintaining and enforcing governance guidelines for the TDC, approved by the maintainers, and which shall be posted visibly for the TDC. -**Making a release:** Maintainers (listed in the repository's MAINTAINERS file) MUST announce intentions to release on the dev@opencontainers.org mailing list with another maintainer as a co-sponsor. Voting on proposed releases SHOULD happen on the dev@opencontainers.org mailing list (except [security fixes](#security-fixes)) with maintainers posting LGTM or REJECT. Maintainers may also explicitly not vote by posting ABSTAIN (which is useful to revert a previous vote). Maintainers may post multiple times (e.g. as they revise their position based on feeback), but only their final post counts in the final tally. A proposed release passes if two-thirds of votes cast, a quorum having voted, are in favor of the release. A quorum is established when at least two-thirds of maintainers have voted. Voting SHOULD remain open for a week, although under exceptional conditions (e.g. security fixes) non-major releases which reach quorum with unanimous support MAY be released earlier. For projects that are not specifications, a proposed release also passes if the final tally is at least three LGTMs and no REJECTs, even if three votes does not meet the usual two-thirds quorum. +This section describes generic rules and procedures for fulfilling that mandate. -**Rejecting a release:** A project maintainer MAY choose to reply with REJECT. A project maintainer posting a REJECT MUST include a list of concerns or links to written documentation for those concerns (e.g. GitHub issues or mailing-list threads). The project maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. However, a release MAY pass with REJECTs, as outlined in the previous paragraph. +## Proposing a motion -## Security fixes +A maintainer SHOULD propose a motion on the dev@opencontainers.org mailing list (except [security issues](#security-issues)) with another maintainer as a co-sponsor. -Security fix releases MUST use security@opencontainers.org instead of dev@opencontainers.org, but should otherwise follow the standard [list-based voting process](#list-based-voting). The security@opencontainers.org email includes all members of the TOB; the TOB will guide the security sensitive release with project maintainers. +## Voting -## Parallel proposals +Voting on a proposed motion SHOULD happen on the dev@opencontainers.org mailing list (except [security issues](#security-issues)) with maintainers posting LGTM or REJECT. +Maintainers MAY also explicitly not vote by posting ABSTAIN (which is useful to revert a previous vote). +Maintainers MAY post multiple times (e.g. as they revise their position based on feeback), but only their final post counts in the tally. +A proposed motion is adopted if two-thirds of votes cast, a quorum having voted, are in favor of the release. -A single repository MAY have several release proposals in parallel. However each proposed release after the first MUST be based on a previous release that has already landed. +Voting SHOULD remain open for a week to collect feedback from the wider community and allow the maintainers to digest the proposed motion. +Under exceptional conditions (e.g. non-major security fix releases) proposals which reach quorum with unanimous support MAY be adopted earlier. -For example, runtime-spec maintainers may propose a v1.0.0-rc2 on the 1st of the month and a v0.9.1 bugfix on the 2nd of the month. They may not propose a v1.0.0-rc3 until the v1.0.0-rc2 is accepted (on the 7th if the vote initiated on the 1st passes). +A maintainer MAY choose to reply with REJECT. +A maintainer posting a REJECT MUST include a list of concerns or links to written documentation for those concerns (e.g. GitHub issues or mailing-list threads). +The maintainers SHOULD try to resolve the concerns and wait for the rejecting maintainer to change their opinion to LGTM. +However, a motion MAY be adopted with REJECTs, as outlined in the previous paragraphs. + +## Quorum + +A quorum is established when at least two-thirds of maintainers have voted. + +For projects that are not specifications, a [motion to release](#release-approval) MAY be adopted if the tally is at least three LGTMs and no REJECTs, even if three votes does not meet the usual two-thirds quorum. + +## Security issues + +Motions with sensitive security implications MUST be proposed on the security@opencontainers.org mailing list instead of dev@opencontainers.org, but should otherwise follow the standard [proposal](#proposing-a-motion) process. +The security@opencontainers.org mailing list includes all members of the TOB. +The TOB will contact the project maintainers and provide a channel for discussing and voting on the motion, but voting will otherwise follow the standard [voting](#voting) and [quorum](#quorum) rules. +The TOB and project maintainers will work together to notify affected parties before making an adopted motion public. + +## Amendments + +The [project governance](#project-governance) rules and procedures MAY be ammended or replaced using the procedures themselves. +No additional quorum or voting restrictions apply to such motions. + +## Subject templates + +Maintainers are busy and get lots of email. +To make project proposals recognizable, proposed motions SHOULD use the following subject templates. + +### Proposing a motion + +> [{project} VOTE]: {motion description} (closes {end of voting window}) + +For example: + +> [runtime-spec VOTE]: Tag 0647920 as 1.0.0-rc (closes 2016-06-03 20:00 UTC) + +### Tallying results + +After voting closes, a maintainer SHOULD post a tally to the motion thread with a subject template like: + +> [{project} {status}]: {motion description} (+{LGTMs} -{REJECTs} #{ABSTAINs}) + +Where `{status}` is either `adopted` or `rejected`. +For example: + +> [runtime-spec adopted]: Tag 0647920 as 1.0.0-rc (+6 -0 #3) + +# Releases + +The release process hopes to encourage early, consistent consensus-building during project development. +The mechanisms used are regular community communication on the mailing list about progress, scheduled meetings for issue resolution and release triage, and regularly paced and communicated releases. +Releases are proposed and adopted or rejected using the usual [project governance](#project-governance) rules and procedures. + +An anti-pattern that we want to avoid is heavy development or discussions "late cycle" around major releases. +We want to build a community that is involved and communicates consistently through all releases instead of relying on "silent periods" as a judge of stability. + +## Parallel releases + +A single project MAY consider several motions to release in parallel. +However each motion to release after the initial 0.1.0 MUST be based on a previous release that has already landed. + +For example, runtime-spec maintainers may propose a v1.0.0-rc2 on the 1st of the month and a v0.9.1 bugfix on the 2nd of the month. +They may not propose a v1.0.0-rc3 until the v1.0.0-rc2 is accepted (on the 7th if the vote initiated on the 1st passes). ## Specifications -The OCI maintains three categories of projects: specifications, applications, and conformance-testing tools. However, specification releases have special restrictions in the [OCI charter][charter]: +The OCI maintains three categories of projects: specifications, applications, and conformance-testing tools. +However, specification releases have special restrictions in the [OCI charter][charter]: * They are the target of backwards compatibility (§7.g), and * They are subject to the OFWa patent grant (§8.d and e). To avoid unfortunate side effects (onerous backwards compatibity requirements or Member resignations), the following additional procedures apply to specification releases: -**Planning a release:** Every OCI specification project SHOULD hold meetings that involves maintainers reviewing pull requests, debating outstanding issues, and planning releases. This meeting MUST be advertised on the project README and MAY happen on a phone call, video conference, or on IRC. Maintainers MUST send updates to the dev@opencontainers.org with results of these meetings. Before the specification reaches v1.0.0, the meetings SHOULD be weekly. Once a specification has reached v1.0.0, the maintainers may alter the cadence, but the meeting cadence MUST NOT be greater than once every four weeks. The release plans, corresponding milestones and estimated due dates MUST be published on GitHub (e.g. https://github.com/opencontainers/runtime-spec/milestones). GitHub milestones and issues are only used for community organization and all releases MUST follow the [list-based voting process](#list-based-voting). +### Planning a release + +Every OCI specification project SHOULD hold meetings that involve maintainers reviewing pull requests, debating outstanding issues, and planning releases. +This meeting MUST be advertised on the project README and MAY happen on a phone call, video conference, or on IRC. +Maintainers MUST send updates to the dev@opencontainers.org with results of these meetings. + +Before the specification reaches v1.0.0, the meetings SHOULD be weekly. +Once a specification has reached v1.0.0, the maintainers may alter the cadence, but a meeting MUST be held within four weeks of the previous meeting. + +The release plans, corresponding milestones and estimated due dates MUST be published on GitHub (e.g. https://github.com/opencontainers/runtime-spec/milestones). +GitHub milestones and issues are only used for community organization and all releases MUST follow the [project governance](#project-governance) rules and procedures. + +### Timelines -**Timelines:** Specifications have a variety of different timelines in their lifecycle. +Specifications have a variety of different timelines in their lifecycle. -- Pre-v1.0.0 specifications SHOULD release on a monthly cadence to garner feedback. -- Major specification releases MUST release at least three release candidates spaced a minimum of one week apart. This means a major release like a v1.0.0 or v2.0.0 release will take 1 month at minimum: one week for rc1, one week for rc2, one week for rc3, and one week for the major release itself. Maintainers SHOULD strive to make zero breaking changes during this cycle of release candidates and SHOULD restart the three-candidate count when a breaking change is introduced. For example if a breaking change is introduced in v1.0.0-rc2 then the series would end with v1.0.0-rc4 and v1.0.0. +* Pre-v1.0.0 specifications SHOULD release on a monthly cadence to garner feedback. +* Major specification releases MUST release at least three release candidates spaced a minimum of one week apart. + This means a major release like a v1.0.0 or v2.0.0 release will take 1 month at minimum: one week for rc1, one week for rc2, one week for rc3, and one week for the major release itself. + Maintainers SHOULD strive to make zero breaking changes during this cycle of release candidates and SHOULD restart the three-candidate count when a breaking change is introduced. + For example if a breaking change is introduced in v1.0.0-rc2 then the series would end with v1.0.0-rc4 and v1.0.0. - Minor and patch releases SHOULD be made on an as-needed basis. [charter]: https://www.opencontainers.org/about/governance From 56abe1227eaf11066fa0005d955b376cbd4883a5 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 28 Jun 2016 18:33:06 -0700 Subject: [PATCH 143/245] GOVERNANCE and RELEASES: split the files Split files into governance and releases and outline the maintainers of the GOVERNANCE doc itself. Signed-off-by: Brandon Philips --- GOVERNANCE.md | 52 +-------------------------------------------------- RELEASES.md | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 51 deletions(-) create mode 100644 RELEASES.md diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 2aeb3d3..59c00bb 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -41,7 +41,7 @@ The TOB and project maintainers will work together to notify affected parties be ## Amendments The [project governance](#project-governance) rules and procedures MAY be ammended or replaced using the procedures themselves. -No additional quorum or voting restrictions apply to such motions. +The MAINTAINERS of this project governance document is the total set of MAINTAINERS from all Open Containers projects (runC, runtime-spec, and image-spec). ## Subject templates @@ -67,54 +67,4 @@ For example: > [runtime-spec adopted]: Tag 0647920 as 1.0.0-rc (+6 -0 #3) -# Releases - -The release process hopes to encourage early, consistent consensus-building during project development. -The mechanisms used are regular community communication on the mailing list about progress, scheduled meetings for issue resolution and release triage, and regularly paced and communicated releases. -Releases are proposed and adopted or rejected using the usual [project governance](#project-governance) rules and procedures. - -An anti-pattern that we want to avoid is heavy development or discussions "late cycle" around major releases. -We want to build a community that is involved and communicates consistently through all releases instead of relying on "silent periods" as a judge of stability. - -## Parallel releases - -A single project MAY consider several motions to release in parallel. -However each motion to release after the initial 0.1.0 MUST be based on a previous release that has already landed. - -For example, runtime-spec maintainers may propose a v1.0.0-rc2 on the 1st of the month and a v0.9.1 bugfix on the 2nd of the month. -They may not propose a v1.0.0-rc3 until the v1.0.0-rc2 is accepted (on the 7th if the vote initiated on the 1st passes). - -## Specifications - -The OCI maintains three categories of projects: specifications, applications, and conformance-testing tools. -However, specification releases have special restrictions in the [OCI charter][charter]: - -* They are the target of backwards compatibility (§7.g), and -* They are subject to the OFWa patent grant (§8.d and e). - -To avoid unfortunate side effects (onerous backwards compatibity requirements or Member resignations), the following additional procedures apply to specification releases: - -### Planning a release - -Every OCI specification project SHOULD hold meetings that involve maintainers reviewing pull requests, debating outstanding issues, and planning releases. -This meeting MUST be advertised on the project README and MAY happen on a phone call, video conference, or on IRC. -Maintainers MUST send updates to the dev@opencontainers.org with results of these meetings. - -Before the specification reaches v1.0.0, the meetings SHOULD be weekly. -Once a specification has reached v1.0.0, the maintainers may alter the cadence, but a meeting MUST be held within four weeks of the previous meeting. - -The release plans, corresponding milestones and estimated due dates MUST be published on GitHub (e.g. https://github.com/opencontainers/runtime-spec/milestones). -GitHub milestones and issues are only used for community organization and all releases MUST follow the [project governance](#project-governance) rules and procedures. - -### Timelines - -Specifications have a variety of different timelines in their lifecycle. - -* Pre-v1.0.0 specifications SHOULD release on a monthly cadence to garner feedback. -* Major specification releases MUST release at least three release candidates spaced a minimum of one week apart. - This means a major release like a v1.0.0 or v2.0.0 release will take 1 month at minimum: one week for rc1, one week for rc2, one week for rc3, and one week for the major release itself. - Maintainers SHOULD strive to make zero breaking changes during this cycle of release candidates and SHOULD restart the three-candidate count when a breaking change is introduced. - For example if a breaking change is introduced in v1.0.0-rc2 then the series would end with v1.0.0-rc4 and v1.0.0. -- Minor and patch releases SHOULD be made on an as-needed basis. - [charter]: https://www.opencontainers.org/about/governance diff --git a/RELEASES.md b/RELEASES.md new file mode 100644 index 0000000..e220042 --- /dev/null +++ b/RELEASES.md @@ -0,0 +1,51 @@ +# Releases + +The release process hopes to encourage early, consistent consensus-building during project development. +The mechanisms used are regular community communication on the mailing list about progress, scheduled meetings for issue resolution and release triage, and regularly paced and communicated releases. +Releases are proposed and adopted or rejected using the usual [project governance](GOVERNANCE.md) rules and procedures. + +An anti-pattern that we want to avoid is heavy development or discussions "late cycle" around major releases. +We want to build a community that is involved and communicates consistently through all releases instead of relying on "silent periods" as a judge of stability. + +## Parallel releases + +A single project MAY consider several motions to release in parallel. +However each motion to release after the initial 0.1.0 MUST be based on a previous release that has already landed. + +For example, runtime-spec maintainers may propose a v1.0.0-rc2 on the 1st of the month and a v0.9.1 bugfix on the 2nd of the month. +They may not propose a v1.0.0-rc3 until the v1.0.0-rc2 is accepted (on the 7th if the vote initiated on the 1st passes). + +## Specifications + +The OCI maintains three categories of projects: specifications, applications, and conformance-testing tools. +However, specification releases have special restrictions in the [OCI charter][charter]: + +* They are the target of backwards compatibility (§7.g), and +* They are subject to the OFWa patent grant (§8.d and e). + +To avoid unfortunate side effects (onerous backwards compatibity requirements or Member resignations), the following additional procedures apply to specification releases: + +### Planning a release + +Every OCI specification project SHOULD hold meetings that involve maintainers reviewing pull requests, debating outstanding issues, and planning releases. +This meeting MUST be advertised on the project README and MAY happen on a phone call, video conference, or on IRC. +Maintainers MUST send updates to the dev@opencontainers.org with results of these meetings. + +Before the specification reaches v1.0.0, the meetings SHOULD be weekly. +Once a specification has reached v1.0.0, the maintainers may alter the cadence, but a meeting MUST be held within four weeks of the previous meeting. + +The release plans, corresponding milestones and estimated due dates MUST be published on GitHub (e.g. https://github.com/opencontainers/runtime-spec/milestones). +GitHub milestones and issues are only used for community organization and all releases MUST follow the [project governance](GOVERNANCE.md) rules and procedures. + +### Timelines + +Specifications have a variety of different timelines in their lifecycle. + +* Pre-v1.0.0 specifications SHOULD release on a monthly cadence to garner feedback. +* Major specification releases MUST release at least three release candidates spaced a minimum of one week apart. + This means a major release like a v1.0.0 or v2.0.0 release will take 1 month at minimum: one week for rc1, one week for rc2, one week for rc3, and one week for the major release itself. + Maintainers SHOULD strive to make zero breaking changes during this cycle of release candidates and SHOULD restart the three-candidate count when a breaking change is introduced. + For example if a breaking change is introduced in v1.0.0-rc2 then the series would end with v1.0.0-rc4 and v1.0.0. +- Minor and patch releases SHOULD be made on an as-needed basis. + +[charter]: https://www.opencontainers.org/about/governance From 16f0fee92fe3c4c976d0c41176205152d3bb43af Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 16 Jun 2016 10:09:45 +0200 Subject: [PATCH 144/245] specs-go: add version types Signed-off-by: Antonio Murdaca --- specs-go/version.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 specs-go/version.go diff --git a/specs-go/version.go b/specs-go/version.go new file mode 100644 index 0000000..9827861 --- /dev/null +++ b/specs-go/version.go @@ -0,0 +1,18 @@ +package specs + +import "fmt" + +const ( + // VersionMajor is for an API incompatible changes + VersionMajor = 0 + // VersionMinor is for functionality in a backwards-compatible manner + VersionMinor = 3 + // VersionPatch is for backwards-compatible bug fixes + VersionPatch = 0 + + // VersionDev indicates development branch. Releases will be empty string. + VersionDev = "-dev" +) + +// Version is the specification version that the package types support. +var Version = fmt.Sprintf("%d.%d.%d%s", VersionMajor, VersionMinor, VersionPatch, VersionDev) From 4267a18d918c4d87bb7423b9f1d6ae72b9ea9e76 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 16 Jun 2016 11:04:44 +0200 Subject: [PATCH 145/245] specs-go: add v1 types Signed-off-by: Antonio Murdaca --- specs-go/descriptor.go | 27 ++++++++++ specs-go/v1/config.go | 99 ++++++++++++++++++++++++++++++++++++ specs-go/v1/manifest.go | 32 ++++++++++++ specs-go/v1/manifest_list.go | 62 ++++++++++++++++++++++ specs-go/v1/mediatype.go | 32 ++++++++++++ specs-go/version.go | 14 +++++ specs-go/versioned.go | 26 ++++++++++ 7 files changed, 292 insertions(+) create mode 100644 specs-go/descriptor.go create mode 100644 specs-go/v1/config.go create mode 100644 specs-go/v1/manifest.go create mode 100644 specs-go/v1/manifest_list.go create mode 100644 specs-go/v1/mediatype.go create mode 100644 specs-go/versioned.go diff --git a/specs-go/descriptor.go b/specs-go/descriptor.go new file mode 100644 index 0000000..34c4037 --- /dev/null +++ b/specs-go/descriptor.go @@ -0,0 +1,27 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package specs + +// Descriptor describes the disposition of targeted content. +type Descriptor struct { + // MediaType contains the MIME type of the referenced object. + MediaType string `json:"mediaType"` + + // Digests is the digest of the targeted content. + Digest string `json:"digest"` + + // Size specifies the size in bytes of the blob + Size int64 `json:"size"` +} diff --git a/specs-go/v1/config.go b/specs-go/v1/config.go new file mode 100644 index 0000000..552404d --- /dev/null +++ b/specs-go/v1/config.go @@ -0,0 +1,99 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1 + +// ImageConfig defines the execution parameters which should be used as a base when running a container using an image. +type ImageConfig struct { + // User defines the username or UID which the process in the container should run as. + User string `json:"User"` + + // Memory defines the memory limit. + Memory int64 `json:"Memory"` + + // MemorySwap defines the total memory usage limit (memory + swap). + MemorySwap int64 `json:"MemorySwap"` + + // CPUShares is the CPU shares (relative weight vs. other containers). + CPUShares int64 `json:"CpuShares"` + + // ExposedPorts a set of ports to expose from a container running this image. + ExposedPorts map[string]struct{} `json:"ExposedPorts"` + + // Env is a list of environment variables to be used in a container. + Env []string `json:"Env"` + + // Entrypoint defines a list of arguments to use as the command to execute when the container starts. + EntryPoint []string `json:"EntryPoint"` + + // Cmd defines the default arguments to the entry point of the container. + Cmd []string `json:"Cmd"` + + // Volumes is a set of directories which should be created as data volumes in a container running this image. + Volumes map[string]struct{} `json:"Volumes"` + + // WorkingDir sets the current working directory of the entry point process in the container. + WorkingDir string `json:"WorkingDir"` +} + +// RootFS describes a layer content addresses +type RootFS struct { + // Type is the type of the rootfs. + Type string `json:"type"` + + // DiffIDs is an array of layer content hashes (DiffIDs), in order from bottom-most to top-most. + DiffIDs []string `json:"diff_ids"` +} + +// History describes the history of a layer. +type History struct { + // Created is the creation time. + Created string `json:"created"` + + // CreatedBy is the command which created the layer. + CreatedBy string `json:"created_by"` + + // Author is the author of the build point. + Author string `json:"author"` + + // Comment is a custom message set when creating the layer. + Comment string `json:"comment"` + + // EmptyLayer is used to mark if the history item created a filesystem diff. + EmptyLayer bool `json:"empty_layer"` +} + +// Image is the JSON structure which describes some basic information about the image. +type Image struct { + // Created defines an ISO-8601 formatted combined date and time at which the image was created. + Created string `json:"created"` + + // Author defines the name and/or email address of the person or entity which created and is responsible for maintaining the image. + Author string `json:"author"` + + // Architecture is the CPU architecture which the binaries in this image are built to run on. + Architecture string `json:"architecture"` + + // OS is the name of the operating system which the image is built to run on. + OS string `json:"os"` + + // Config defines the execution parameters which should be used as a base when running a container using the image. + Config ImageConfig `json:"config"` + + // RootFS references the layer content addresses used by the image. + RootFS RootFS `json:"rootfs"` + + // History describes the history of each layer. + History []History `json:"history"` +} diff --git a/specs-go/v1/manifest.go b/specs-go/v1/manifest.go new file mode 100644 index 0000000..10bde08 --- /dev/null +++ b/specs-go/v1/manifest.go @@ -0,0 +1,32 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1 + +import "github.com/opencontainers/image-spec/specs-go" + +// Manifest defines a schema2 manifest +type Manifest struct { + specs.Versioned + + // Config references a configuration object for a container, by digest. + // The referenced configuration object is a JSON blob that the runtime uses to set up the container. + Config specs.Descriptor `json:"config"` + + // Layers is an indexed list of layers referenced by the manifest. + Layers []specs.Descriptor `json:"layers"` + + // Annotations contains arbitrary metadata for the manifest list. + Annotations map[string]string `json:"annotations"` +} diff --git a/specs-go/v1/manifest_list.go b/specs-go/v1/manifest_list.go new file mode 100644 index 0000000..8c02ee2 --- /dev/null +++ b/specs-go/v1/manifest_list.go @@ -0,0 +1,62 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1 + +import "github.com/opencontainers/image-spec/specs-go" + +// Platform describes the platform which the image in the manifest runs on. +type Platform struct { + // Architecture field specifies the CPU architecture, for example + // `amd64` or `ppc64`. + Architecture string `json:"architecture"` + + // OS specifies the operating system, for example `linux` or `windows`. + OS string `json:"os"` + + // OSVersion is an optional field specifying the operating system + // version, for example `10.0.10586`. + OSVersion string `json:"os.version,omitempty"` + + // OSFeatures is an optional field specifying an array of strings, + // each listing a required OS feature (for example on Windows `win32k`). + OSFeatures []string `json:"os.features,omitempty"` + + // Variant is an optional field specifying a variant of the CPU, for + // example `ppc64le` to specify a little-endian version of a PowerPC CPU. + Variant string `json:"variant,omitempty"` + + // Features is an optional field specifying an array of strings, each + // listing a required CPU feature (for example `sse4` or `aes`). + Features []string `json:"features,omitempty"` +} + +// ManifestDescriptor describes a platform specific manifest. +type ManifestDescriptor struct { + specs.Descriptor + + // Platform describes the platform which the image in the manifest runs on. + Platform Platform `json:"platform"` +} + +// ManifestList references manifests for various platforms. +type ManifestList struct { + specs.Versioned + + // Manifests references platform specific manifests. + Manifests []ManifestDescriptor `json:"manifests"` + + // Annotations contains arbitrary metadata for the manifest list. + Annotations map[string]string `json:"annotations"` +} diff --git a/specs-go/v1/mediatype.go b/specs-go/v1/mediatype.go new file mode 100644 index 0000000..d5f128f --- /dev/null +++ b/specs-go/v1/mediatype.go @@ -0,0 +1,32 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1 + +const ( + // MediaTypeDescriptor specifies the mediaType for a content descriptor. + MediaTypeDescriptor = "application/vnd.oci.descriptor.v1+json" + + // MediaTypeImageManifest specifies the mediaType for an image manifest. + MediaTypeImageManifest = "application/vnd.oci.image.manifest.v1+json" + + // MediaTypeImageManifestList specifies the mediaType for an image manifest list. + MediaTypeImageManifestList = "application/vnd.oci.image.manifest.list.v1+json" + + // MediaTypeImageSerialization is the mediaType used for layers referenced by the manifest. + MediaTypeImageSerialization = "application/vnd.oci.image.serialization.rootfs.tar.gzip" + + // MediaTypeImageSerializationConfig specifies the mediaType for the image configuration. + MediaTypeImageSerializationConfig = "application/vnd.oci.image.serialization.config.v1+json" +) diff --git a/specs-go/version.go b/specs-go/version.go index 9827861..7770429 100644 --- a/specs-go/version.go +++ b/specs-go/version.go @@ -1,3 +1,17 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package specs import "fmt" diff --git a/specs-go/versioned.go b/specs-go/versioned.go new file mode 100644 index 0000000..ca0bd41 --- /dev/null +++ b/specs-go/versioned.go @@ -0,0 +1,26 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package specs + +// Versioned provides a struct with the manifest schemaVersion and mediaType. +// Incoming content with unknown schema version can be decoded against this +// struct to check the version. +type Versioned struct { + // SchemaVersion is the image manifest schema that this image follows + SchemaVersion int `json:"schemaVersion"` + + // MediaType is the media type of this schema. + MediaType string `json:"mediaType,omitempty"` +} From 0e8f74b634b84f56e6fcb46b68971219fd3d9e55 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Fri, 22 Jul 2016 11:14:05 +0200 Subject: [PATCH 146/245] cmd/oci-image-tool: replace colon with hyphen to get blobs by digest Signed-off-by: Antonio Murdaca --- image/config.go | 2 +- image/descriptor.go | 11 ++++++++--- image/manifest.go | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/image/config.go b/image/config.go index d486c01..a6b7ad1 100644 --- a/image/config.go +++ b/image/config.go @@ -51,7 +51,7 @@ type config struct { func findConfig(w walker, d *descriptor) (*config, error) { var c config - cpath := filepath.Join("blobs", d.Digest) + cpath := filepath.Join("blobs", d.getDigest()) f := func(path string, info os.FileInfo, r io.Reader) error { if info.IsDir() { diff --git a/image/descriptor.go b/image/descriptor.go index ca576e6..525bdc5 100644 --- a/image/descriptor.go +++ b/image/descriptor.go @@ -22,6 +22,7 @@ import ( "io" "os" "path/filepath" + "strings" "github.com/pkg/errors" ) @@ -32,6 +33,10 @@ type descriptor struct { Size int64 `json:"size"` } +func (d *descriptor) getDigest() string { + return strings.Replace(d.Digest, ":", "-", -1) +} + func findDescriptor(w walker, name string) (*descriptor, error) { var d descriptor dpath := filepath.Join("refs", name) @@ -71,7 +76,7 @@ func (d *descriptor) validate(w walker) error { } digest, err := filepath.Rel("blobs", filepath.Clean(path)) - if err != nil || d.Digest != digest { + if err != nil || d.getDigest() != digest { return nil // ignore } @@ -84,11 +89,11 @@ func (d *descriptor) validate(w walker) error { switch err := w.walk(f); err { case nil: - return fmt.Errorf("%s: not found", d.Digest) + return fmt.Errorf("%s: not found", d.getDigest()) case errEOW: // found, continue below default: - return errors.Wrapf(err, "%s: validation failed", d.Digest) + return errors.Wrapf(err, "%s: validation failed", d.getDigest()) } return nil diff --git a/image/manifest.go b/image/manifest.go index 61e7674..b41d6fe 100644 --- a/image/manifest.go +++ b/image/manifest.go @@ -38,7 +38,7 @@ type manifest struct { func findManifest(w walker, d *descriptor) (*manifest, error) { var m manifest - mpath := filepath.Join("blobs", d.Digest) + mpath := filepath.Join("blobs", d.getDigest()) f := func(path string, info os.FileInfo, r io.Reader) error { if info.IsDir() { From 116950ea16e4a0622403e9955aa2fbb0359ac3ee Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Fri, 22 Jul 2016 11:25:08 +0200 Subject: [PATCH 147/245] cmd/oci-image-tool: fix config.json generation Signed-off-by: Antonio Murdaca --- image/config.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/image/config.go b/image/config.go index a6b7ad1..0e2a268 100644 --- a/image/config.go +++ b/image/config.go @@ -98,11 +98,18 @@ func (c *config) runtimeSpec(rootfs string) (*specs.Spec, error) { var s specs.Spec s.Version = "0.5.0" s.Root.Path = rootfs - s.Process.Cwd = c.Config.WorkingDir - s.Process.Env = append([]string(nil), c.Config.Env...) - s.Process.Args = append([]string(nil), c.Config.Entrypoint...) + s.Process.Cwd = "/" + if c.Config.WorkingDir != "" { + s.Process.Cwd = c.Config.WorkingDir + } + s.Process.Env = append(s.Process.Env, c.Config.Env...) + s.Process.Args = append(s.Process.Env, c.Config.Entrypoint...) s.Process.Args = append(s.Process.Args, c.Config.Cmd...) + if len(s.Process.Args) == 0 { + s.Process.Args = append(s.Process.Args, "sh") + } + if uid, err := strconv.Atoi(c.Config.User); err == nil { s.Process.User.UID = uint32(uid) } else if ug := strings.Split(c.Config.User, ":"); len(ug) == 2 { @@ -118,7 +125,7 @@ func (c *config) runtimeSpec(rootfs string) (*specs.Spec, error) { s.Process.User.UID = uint32(uid) s.Process.User.GID = uint32(gid) - } else { + } else if c.Config.User != "" { return nil, errors.New("config.User: unsupported format") } From 65413928efe23ff1df2a034a194e423116fee217 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Fri, 22 Jul 2016 11:25:44 +0200 Subject: [PATCH 148/245] cmd/oci-image-tool: error out if path destination does not exist Signed-off-by: Antonio Murdaca --- cmd/oci-image-tool/create_runtime_bundle.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/oci-image-tool/create_runtime_bundle.go b/cmd/oci-image-tool/create_runtime_bundle.go index 1ec8ab1..57b6745 100644 --- a/cmd/oci-image-tool/create_runtime_bundle.go +++ b/cmd/oci-image-tool/create_runtime_bundle.go @@ -82,6 +82,11 @@ func (v *bundleCmd) Run(cmd *cobra.Command, args []string) { os.Exit(1) } + if _, err := os.Stat(args[1]); os.IsNotExist(err) { + v.stderr.Printf("destination path %s does not exist", args[1]) + os.Exit(1) + } + if v.typ == "" { typ, err := autodetect(args[0]) if err != nil { From 44210d05045ef0183a4cd3342e6a8d45e4dd52f1 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Fri, 22 Jul 2016 12:06:42 +0200 Subject: [PATCH 149/245] cmd/oci-image-tool: fix unpacking... Signed-off-by: Antonio Murdaca --- image/config.go | 6 ++++-- image/descriptor.go | 8 ++++---- image/manifest.go | 50 +++++++++++++++++++++++++++++++++++++-------- 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/image/config.go b/image/config.go index 0e2a268..994d2ff 100644 --- a/image/config.go +++ b/image/config.go @@ -51,7 +51,7 @@ type config struct { func findConfig(w walker, d *descriptor) (*config, error) { var c config - cpath := filepath.Join("blobs", d.getDigest()) + cpath := filepath.Join("blobs", d.normalizeDigest()) f := func(path string, info os.FileInfo, r io.Reader) error { if info.IsDir() { @@ -97,13 +97,15 @@ func (c *config) runtimeSpec(rootfs string) (*specs.Spec, error) { var s specs.Spec s.Version = "0.5.0" + // we should at least apply the default spec, otherwise this is totally useless + s.Process.Terminal = true s.Root.Path = rootfs s.Process.Cwd = "/" if c.Config.WorkingDir != "" { s.Process.Cwd = c.Config.WorkingDir } s.Process.Env = append(s.Process.Env, c.Config.Env...) - s.Process.Args = append(s.Process.Env, c.Config.Entrypoint...) + s.Process.Args = append(s.Process.Args, c.Config.Entrypoint...) s.Process.Args = append(s.Process.Args, c.Config.Cmd...) if len(s.Process.Args) == 0 { diff --git a/image/descriptor.go b/image/descriptor.go index 525bdc5..9a5d183 100644 --- a/image/descriptor.go +++ b/image/descriptor.go @@ -33,7 +33,7 @@ type descriptor struct { Size int64 `json:"size"` } -func (d *descriptor) getDigest() string { +func (d *descriptor) normalizeDigest() string { return strings.Replace(d.Digest, ":", "-", -1) } @@ -76,7 +76,7 @@ func (d *descriptor) validate(w walker) error { } digest, err := filepath.Rel("blobs", filepath.Clean(path)) - if err != nil || d.getDigest() != digest { + if err != nil || d.normalizeDigest() != digest { return nil // ignore } @@ -89,11 +89,11 @@ func (d *descriptor) validate(w walker) error { switch err := w.walk(f); err { case nil: - return fmt.Errorf("%s: not found", d.getDigest()) + return fmt.Errorf("%s: not found", d.normalizeDigest()) case errEOW: // found, continue below default: - return errors.Wrapf(err, "%s: validation failed", d.getDigest()) + return errors.Wrapf(err, "%s: validation failed", d.normalizeDigest()) } return nil diff --git a/image/manifest.go b/image/manifest.go index b41d6fe..562745d 100644 --- a/image/manifest.go +++ b/image/manifest.go @@ -38,7 +38,7 @@ type manifest struct { func findManifest(w walker, d *descriptor) (*manifest, error) { var m manifest - mpath := filepath.Join("blobs", d.getDigest()) + mpath := filepath.Join("blobs", d.normalizeDigest()) f := func(path string, info os.FileInfo, r io.Reader) error { if info.IsDir() { @@ -107,7 +107,7 @@ func (m *manifest) unpack(w walker, dest string) error { } dd, err := filepath.Rel("blobs", filepath.Clean(path)) - if err != nil || d.Digest != dd { + if err != nil || d.normalizeDigest() != dd { return nil // ignore } @@ -134,6 +134,7 @@ func unpackLayer(dest string, r io.Reader) error { } defer gz.Close() + var dirs []*tar.Header tr := tar.NewReader(gz) loop: @@ -148,8 +149,26 @@ loop: return errors.Wrapf(err, "error advancing tar stream") } - path := filepath.Join(dest, filepath.Clean(hdr.Name)) + hdr.Name = filepath.Clean(hdr.Name) + if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) { + // Not the root directory, ensure that the parent directory exists + parent := filepath.Dir(hdr.Name) + parentPath := filepath.Join(dest, parent) + if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { + if err2 := os.MkdirAll(parentPath, 0777); err2 != nil { + return err2 + } + } + } + path := filepath.Join(dest, hdr.Name) + rel, err := filepath.Rel(dest, path) + if err != nil { + return err + } info := hdr.FileInfo() + if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { + return fmt.Errorf("%q is outside of %q", hdr.Name, dest) + } if strings.HasPrefix(info.Name(), ".wh.") { path = strings.Replace(path, ".wh.", "", 1) @@ -163,12 +182,14 @@ loop: switch hdr.Typeflag { case tar.TypeDir: - if err := os.MkdirAll(path, info.Mode()); err != nil { - return errors.Wrap(err, "error creating directory") + if fi, err := os.Lstat(path); !(err == nil && fi.IsDir()) { + if err2 := os.MkdirAll(path, info.Mode()); err2 != nil { + return errors.Wrap(err2, "error creating directory") + } } case tar.TypeReg, tar.TypeRegA: - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode()) + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, info.Mode()) if err != nil { return errors.Wrap(err, "unable to open file") } @@ -200,13 +221,24 @@ loop: if err := os.Symlink(hdr.Linkname, path); err != nil { return err } - + case tar.TypeXGlobalHeader: + return nil + } + // Directory mtimes must be handled at the end to avoid further + // file creation in them to modify the directory mtime + if hdr.Typeflag == tar.TypeDir { + dirs = append(dirs, hdr) } + } + for _, hdr := range dirs { + path := filepath.Join(dest, hdr.Name) - if err := os.Chtimes(path, time.Now().UTC(), info.ModTime()); err != nil { + finfo := hdr.FileInfo() + // I believe the old version was using time.Now().UTC() to overcome an + // invalid error from chtimes.....but here we lose hdr.AccessTime like this... + if err := os.Chtimes(path, time.Now().UTC(), finfo.ModTime()); err != nil { return errors.Wrap(err, "error changing time") } } - return nil } From 08bed93730c7ef5123f8740cea68d72855dd6fcf Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Fri, 22 Jul 2016 16:23:44 +0200 Subject: [PATCH 150/245] .tool/lint: bump gocyclo req Signed-off-by: Antonio Murdaca --- .tool/lint | 2 +- image/manifest.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.tool/lint b/.tool/lint index f25a668..e7c9bab 100755 --- a/.tool/lint +++ b/.tool/lint @@ -17,7 +17,7 @@ for d in $(find . -type d -not -iwholename '*.git*' -a -not -iname '.tool' -a -n --exclude='schema/fs.go' \ --disable=aligncheck \ --disable=gotype \ - --cyclo-over=20 \ + --cyclo-over=35 \ --tests \ --deadline=10s "${d}" done diff --git a/image/manifest.go b/image/manifest.go index 562745d..74cbfeb 100644 --- a/image/manifest.go +++ b/image/manifest.go @@ -154,9 +154,9 @@ loop: // Not the root directory, ensure that the parent directory exists parent := filepath.Dir(hdr.Name) parentPath := filepath.Join(dest, parent) - if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { - if err2 := os.MkdirAll(parentPath, 0777); err2 != nil { - return err2 + if _, err2 := os.Lstat(parentPath); err2 != nil && os.IsNotExist(err2) { + if err3 := os.MkdirAll(parentPath, 0777); err3 != nil { + return err3 } } } From cf5a7fc55b51b8c933e9c45eb62c23db4523b3d7 Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Fri, 22 Jul 2016 16:39:04 +0200 Subject: [PATCH 151/245] glide: add github.com/russross/blackfriday Signed-off-by: Sergiusz Urbaniak --- glide.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/glide.yaml b/glide.yaml index 496a379..204d144 100644 --- a/glide.yaml +++ b/glide.yaml @@ -9,3 +9,7 @@ import: - 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 From 9a93f84b6a7fb6c788f3b8e3895b5dc31a2e30c8 Mon Sep 17 00:00:00 2001 From: Sergiusz Urbaniak Date: Fri, 22 Jul 2016 16:42:00 +0200 Subject: [PATCH 152/245] glide: vendor new dependencies Signed-off-by: Sergiusz Urbaniak --- glide.lock | 16 +- .../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 + vendor/github.com/spf13/cobra/command.go | 116 +- 11 files changed, 5283 insertions(+), 64 deletions(-) create mode 100644 vendor/github.com/russross/blackfriday/LICENSE.txt create mode 100644 vendor/github.com/russross/blackfriday/block.go create mode 100644 vendor/github.com/russross/blackfriday/html.go create mode 100644 vendor/github.com/russross/blackfriday/inline.go create mode 100644 vendor/github.com/russross/blackfriday/latex.go create mode 100644 vendor/github.com/russross/blackfriday/markdown.go create mode 100644 vendor/github.com/russross/blackfriday/smartypants.go create mode 100644 vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE create mode 100644 vendor/github.com/shurcooL/sanitized_anchor_name/main.go diff --git a/glide.lock b/glide.lock index 3329039..57280d2 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 9eee25c418553e1d79fe7fb956eae4b056402d2c1fe7c8c79f5849da571dd99b -updated: 2016-06-23T09:00:42.232695928+02:00 +hash: 223985f204597c6ed49657a4fc38273f683d40c39e8d48d13ed0dbf632107427 +updated: 2016-07-22T16:40:50.020731917+02:00 imports: - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 @@ -9,10 +9,14 @@ imports: - 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 - name: github.com/spf13/cobra - version: 6a8bd97bdb1fc0d08a83459940498ea49d3e8c93 + version: f62e98d28ab7ad31d707ba837a966378465c7b57 - name: github.com/spf13/pflag - version: 367864438f1b1a3c7db4da06a2f55b144e6784e0 + version: 1560c1005499d61b80f865c04d39ca7505bf7f0b - name: github.com/xeipuuv/gojsonpointer version: e0fe6f68307607d540ed8eac07a342c33fa1b54a - name: github.com/xeipuuv/gojsonreference @@ -20,7 +24,7 @@ imports: - name: github.com/xeipuuv/gojsonschema version: d5336c75940ef31c9ceeb0ae64cf92944bccb4ee - name: go4.org - version: 15c19124e43b90eba9aa27b4341e38365254a84a + version: 85455cb60c902182109ca27131042a41bc4cb85d subpackages: - errorutil -devImports: [] +testImports: [] diff --git a/vendor/github.com/russross/blackfriday/LICENSE.txt b/vendor/github.com/russross/blackfriday/LICENSE.txt new file mode 100644 index 0000000..2885af3 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/LICENSE.txt @@ -0,0 +1,29 @@ +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 new file mode 100644 index 0000000..b5b0841 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/block.go @@ -0,0 +1,1398 @@ +// +// 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 new file mode 100644 index 0000000..74e67ee --- /dev/null +++ b/vendor/github.com/russross/blackfriday/html.go @@ -0,0 +1,949 @@ +// +// 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