Implements garbage collection subcommand
- Includes a change in the command to run the registry. The registry server itself is now started up as a subcommand. - Includes changes to the high level interfaces to support enumeration of various registry objects. Signed-off-by: Andrew T Nguyen <andrew.nguyen@docker.com>master
							parent
							
								
									e430d77342
								
							
						
					
					
						commit
						feab4aafbc
					
				|  | @ -16,4 +16,4 @@ RUN make PREFIX=/go clean binaries | |||
| VOLUME ["/var/lib/registry"] | ||||
| EXPOSE 5000 | ||||
| ENTRYPOINT ["registry"] | ||||
| CMD ["/etc/docker/registry/config.yml"] | ||||
| CMD ["serve", "/etc/docker/registry/config.yml"] | ||||
|  |  | |||
|  | @ -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 [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. | ||||
|  | @ -0,0 +1,3 @@ | |||
| AWS SDK for Go | ||||
| Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.  | ||||
| Copyright 2014-2015 Stripe, Inc. | ||||
|  | @ -0,0 +1,185 @@ | |||
| This software is licensed under the LGPLv3, included below. | ||||
| 
 | ||||
| As a special exception to the GNU Lesser General Public License version 3 | ||||
| ("LGPL3"), the copyright holders of this Library give you permission to | ||||
| convey to a third party a Combined Work that links statically or dynamically | ||||
| to this Library without providing any Minimal Corresponding Source or | ||||
| Minimal Application Code as set out in 4d or providing the installation | ||||
| information set out in section 4e, provided that you comply with the other | ||||
| provisions of LGPL3 and provided that you meet, for the Application the | ||||
| terms and conditions of the license(s) which apply to the Application. | ||||
| 
 | ||||
| Except as stated in this special exception, the provisions of LGPL3 will | ||||
| continue to comply in full to this Library. If you modify this Library, you | ||||
| may apply this exception to your version of this Library, but you are not | ||||
| obliged to do so. If you do not wish to do so, delete this exception | ||||
| statement from your version. This exception does not (and cannot) modify any | ||||
| license terms which apply to the Application, with which you must still | ||||
| comply. | ||||
| 
 | ||||
| 
 | ||||
|                    GNU LESSER GENERAL PUBLIC LICENSE | ||||
|                        Version 3, 29 June 2007 | ||||
| 
 | ||||
|  Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> | ||||
|  Everyone is permitted to copy and distribute verbatim copies | ||||
|  of this license document, but changing it is not allowed. | ||||
| 
 | ||||
| 
 | ||||
|   This version of the GNU Lesser General Public License incorporates | ||||
| the terms and conditions of version 3 of the GNU General Public | ||||
| License, supplemented by the additional permissions listed below. | ||||
| 
 | ||||
|   0. Additional Definitions. | ||||
| 
 | ||||
|   As used herein, "this License" refers to version 3 of the GNU Lesser | ||||
| General Public License, and the "GNU GPL" refers to version 3 of the GNU | ||||
| General Public License. | ||||
| 
 | ||||
|   "The Library" refers to a covered work governed by this License, | ||||
| other than an Application or a Combined Work as defined below. | ||||
| 
 | ||||
|   An "Application" is any work that makes use of an interface provided | ||||
| by the Library, but which is not otherwise based on the Library. | ||||
| Defining a subclass of a class defined by the Library is deemed a mode | ||||
| of using an interface provided by the Library. | ||||
| 
 | ||||
|   A "Combined Work" is a work produced by combining or linking an | ||||
| Application with the Library.  The particular version of the Library | ||||
| with which the Combined Work was made is also called the "Linked | ||||
| Version". | ||||
| 
 | ||||
|   The "Minimal Corresponding Source" for a Combined Work means the | ||||
| Corresponding Source for the Combined Work, excluding any source code | ||||
| for portions of the Combined Work that, considered in isolation, are | ||||
| based on the Application, and not on the Linked Version. | ||||
| 
 | ||||
|   The "Corresponding Application Code" for a Combined Work means the | ||||
| object code and/or source code for the Application, including any data | ||||
| and utility programs needed for reproducing the Combined Work from the | ||||
| Application, but excluding the System Libraries of the Combined Work. | ||||
| 
 | ||||
|   1. Exception to Section 3 of the GNU GPL. | ||||
| 
 | ||||
|   You may convey a covered work under sections 3 and 4 of this License | ||||
| without being bound by section 3 of the GNU GPL. | ||||
| 
 | ||||
|   2. Conveying Modified Versions. | ||||
| 
 | ||||
|   If you modify a copy of the Library, and, in your modifications, a | ||||
| facility refers to a function or data to be supplied by an Application | ||||
| that uses the facility (other than as an argument passed when the | ||||
| facility is invoked), then you may convey a copy of the modified | ||||
| version: | ||||
| 
 | ||||
|    a) under this License, provided that you make a good faith effort to | ||||
|    ensure that, in the event an Application does not supply the | ||||
|    function or data, the facility still operates, and performs | ||||
|    whatever part of its purpose remains meaningful, or | ||||
| 
 | ||||
|    b) under the GNU GPL, with none of the additional permissions of | ||||
|    this License applicable to that copy. | ||||
| 
 | ||||
|   3. Object Code Incorporating Material from Library Header Files. | ||||
| 
 | ||||
|   The object code form of an Application may incorporate material from | ||||
| a header file that is part of the Library.  You may convey such object | ||||
| code under terms of your choice, provided that, if the incorporated | ||||
| material is not limited to numerical parameters, data structure | ||||
| layouts and accessors, or small macros, inline functions and templates | ||||
| (ten or fewer lines in length), you do both of the following: | ||||
| 
 | ||||
|    a) Give prominent notice with each copy of the object code that the | ||||
|    Library is used in it and that the Library and its use are | ||||
|    covered by this License. | ||||
| 
 | ||||
|    b) Accompany the object code with a copy of the GNU GPL and this license | ||||
|    document. | ||||
| 
 | ||||
|   4. Combined Works. | ||||
| 
 | ||||
|   You may convey a Combined Work under terms of your choice that, | ||||
| taken together, effectively do not restrict modification of the | ||||
| portions of the Library contained in the Combined Work and reverse | ||||
| engineering for debugging such modifications, if you also do each of | ||||
| the following: | ||||
| 
 | ||||
|    a) Give prominent notice with each copy of the Combined Work that | ||||
|    the Library is used in it and that the Library and its use are | ||||
|    covered by this License. | ||||
| 
 | ||||
|    b) Accompany the Combined Work with a copy of the GNU GPL and this license | ||||
|    document. | ||||
| 
 | ||||
|    c) For a Combined Work that displays copyright notices during | ||||
|    execution, include the copyright notice for the Library among | ||||
|    these notices, as well as a reference directing the user to the | ||||
|    copies of the GNU GPL and this license document. | ||||
| 
 | ||||
|    d) Do one of the following: | ||||
| 
 | ||||
|        0) Convey the Minimal Corresponding Source under the terms of this | ||||
|        License, and the Corresponding Application Code in a form | ||||
|        suitable for, and under terms that permit, the user to | ||||
|        recombine or relink the Application with a modified version of | ||||
|        the Linked Version to produce a modified Combined Work, in the | ||||
|        manner specified by section 6 of the GNU GPL for conveying | ||||
|        Corresponding Source. | ||||
| 
 | ||||
|        1) Use a suitable shared library mechanism for linking with the | ||||
|        Library.  A suitable mechanism is one that (a) uses at run time | ||||
|        a copy of the Library already present on the user's computer | ||||
|        system, and (b) will operate properly with a modified version | ||||
|        of the Library that is interface-compatible with the Linked | ||||
|        Version. | ||||
| 
 | ||||
|    e) Provide Installation Information, but only if you would otherwise | ||||
|    be required to provide such information under section 6 of the | ||||
|    GNU GPL, and only to the extent that such information is | ||||
|    necessary to install and execute a modified version of the | ||||
|    Combined Work produced by recombining or relinking the | ||||
|    Application with a modified version of the Linked Version. (If | ||||
|    you use option 4d0, the Installation Information must accompany | ||||
|    the Minimal Corresponding Source and Corresponding Application | ||||
|    Code. If you use option 4d1, you must provide the Installation | ||||
|    Information in the manner specified by section 6 of the GNU GPL | ||||
|    for conveying Corresponding Source.) | ||||
| 
 | ||||
|   5. Combined Libraries. | ||||
| 
 | ||||
|   You may place library facilities that are a work based on the | ||||
| Library side by side in a single library together with other library | ||||
| facilities that are not Applications and are not covered by this | ||||
| License, and convey such a combined library under terms of your | ||||
| choice, if you do both of the following: | ||||
| 
 | ||||
|    a) Accompany the combined library with a copy of the same work based | ||||
|    on the Library, uncombined with any other library facilities, | ||||
|    conveyed under the terms of this License. | ||||
| 
 | ||||
|    b) Give prominent notice with the combined library that part of it | ||||
|    is a work based on the Library, and explaining where to find the | ||||
|    accompanying uncombined form of the same work. | ||||
| 
 | ||||
|   6. Revised Versions of the GNU Lesser General Public License. | ||||
| 
 | ||||
|   The Free Software Foundation may publish revised and/or new versions | ||||
| of the GNU Lesser General Public License from time to time. Such new | ||||
| versions will be similar in spirit to the present version, but may | ||||
| differ in detail to address new problems or concerns. | ||||
| 
 | ||||
|   Each version is given a distinguishing version number. If the | ||||
| Library as you received it specifies that a certain numbered version | ||||
| of the GNU Lesser General Public License "or any later version" | ||||
| applies to it, you have the option of following the terms and | ||||
| conditions either of that published version or of any later version | ||||
| published by the Free Software Foundation. If the Library as you | ||||
| received it does not specify a version number of the GNU Lesser | ||||
| General Public License, you may choose any version of the GNU Lesser | ||||
| General Public License ever published by the Free Software Foundation. | ||||
| 
 | ||||
|   If the Library as you received it specifies that a proxy can decide | ||||
| whether future versions of the GNU Lesser General Public License shall | ||||
| apply, that proxy's public statement of acceptance of any version is | ||||
| permanent authorization for you to choose that version for the | ||||
| Library. | ||||
|  | @ -0,0 +1,22 @@ | |||
| Copyright (c) 2013 The Gorilla Handlers 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. | ||||
| 
 | ||||
| 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. | ||||
|  | @ -0,0 +1,71 @@ | |||
| package handlers | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| type canonical struct { | ||||
| 	h      http.Handler | ||||
| 	domain string | ||||
| 	code   int | ||||
| } | ||||
| 
 | ||||
| // CanonicalHost is HTTP middleware that re-directs requests to the canonical
 | ||||
| // domain. It accepts a domain and a status code (e.g. 301 or 302) and
 | ||||
| // re-directs clients to this domain. The existing request path is maintained.
 | ||||
| //
 | ||||
| // Note: If the provided domain is considered invalid by url.Parse or otherwise
 | ||||
| // returns an empty scheme or host, clients are not re-directed.
 | ||||
| // not re-directed.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| //
 | ||||
| //  r := mux.NewRouter()
 | ||||
| //  canonical := handlers.CanonicalHost("http://www.gorillatoolkit.org", 302)
 | ||||
| //  r.HandleFunc("/route", YourHandler)
 | ||||
| //
 | ||||
| //  log.Fatal(http.ListenAndServe(":7000", canonical(r)))
 | ||||
| //
 | ||||
| func CanonicalHost(domain string, code int) func(h http.Handler) http.Handler { | ||||
| 	fn := func(h http.Handler) http.Handler { | ||||
| 		return canonical{h, domain, code} | ||||
| 	} | ||||
| 
 | ||||
| 	return fn | ||||
| } | ||||
| 
 | ||||
| func (c canonical) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	dest, err := url.Parse(c.domain) | ||||
| 	if err != nil { | ||||
| 		// Call the next handler if the provided domain fails to parse.
 | ||||
| 		c.h.ServeHTTP(w, r) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if dest.Scheme == "" || dest.Host == "" { | ||||
| 		// Call the next handler if the scheme or host are empty.
 | ||||
| 		// Note that url.Parse won't fail on in this case.
 | ||||
| 		c.h.ServeHTTP(w, r) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if !strings.EqualFold(cleanHost(r.Host), dest.Host) { | ||||
| 		// Re-build the destination URL
 | ||||
| 		dest := dest.Scheme + "://" + dest.Host + r.URL.Path | ||||
| 		http.Redirect(w, r, dest, c.code) | ||||
| 	} | ||||
| 
 | ||||
| 	c.h.ServeHTTP(w, r) | ||||
| } | ||||
| 
 | ||||
| // cleanHost cleans invalid Host headers by stripping anything after '/' or ' '.
 | ||||
| // This is backported from Go 1.5 (in response to issue #11206) and attempts to
 | ||||
| // mitigate malformed Host headers that do not match the format in RFC7230.
 | ||||
| func cleanHost(in string) string { | ||||
| 	if i := strings.IndexAny(in, " /"); i != -1 { | ||||
| 		return in[:i] | ||||
| 	} | ||||
| 	return in | ||||
| } | ||||
|  | @ -0,0 +1,9 @@ | |||
| /* | ||||
| Package handlers is a collection of handlers (aka "HTTP middleware") for use | ||||
| with Go's net/http package (or any framework supporting http.Handler). | ||||
| 
 | ||||
| The package includes handlers for logging in standardised formats, compressing | ||||
| HTTP responses, validating content types and other useful tools for manipulating | ||||
| requests and responses. | ||||
| */ | ||||
| package handlers | ||||
							
								
								
									
										113
									
								
								Godeps/_workspace/src/github.com/gorilla/handlers/proxy_headers.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							
							
						
						
									
										113
									
								
								Godeps/_workspace/src/github.com/gorilla/handlers/proxy_headers.go
								
								
									generated
								
								
									vendored
								
								
									Normal file
								
							|  | @ -0,0 +1,113 @@ | |||
| package handlers | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	// De-facto standard header keys.
 | ||||
| 	xForwardedFor   = http.CanonicalHeaderKey("X-Forwarded-For") | ||||
| 	xRealIP         = http.CanonicalHeaderKey("X-Real-IP") | ||||
| 	xForwardedProto = http.CanonicalHeaderKey("X-Forwarded-Scheme") | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	// RFC7239 defines a new "Forwarded: " header designed to replace the
 | ||||
| 	// existing use of X-Forwarded-* headers.
 | ||||
| 	// e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43
 | ||||
| 	forwarded = http.CanonicalHeaderKey("Forwarded") | ||||
| 	// Allows for a sub-match of the first value after 'for=' to the next
 | ||||
| 	// comma, semi-colon or space. The match is case-insensitive.
 | ||||
| 	forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|,| )]+)`) | ||||
| 	// Allows for a sub-match for the first instance of scheme (http|https)
 | ||||
| 	// prefixed by 'proto='. The match is case-insensitive.
 | ||||
| 	protoRegex = regexp.MustCompile(`(?i)(?:proto=)(https|http)`) | ||||
| ) | ||||
| 
 | ||||
| // ProxyHeaders inspects common reverse proxy headers and sets the corresponding
 | ||||
| // fields in the HTTP request struct. These are X-Forwarded-For and X-Real-IP
 | ||||
| // for the remote (client) IP address, X-Forwarded-Proto for the scheme
 | ||||
| // (http|https) and the RFC7239 Forwarded header, which may include both client
 | ||||
| // IPs and schemes.
 | ||||
| //
 | ||||
| // NOTE: This middleware should only be used when behind a reverse
 | ||||
| // proxy like nginx, HAProxy or Apache. Reverse proxies that don't (or are
 | ||||
| // configured not to) strip these headers from client requests, or where these
 | ||||
| // headers are accepted "as is" from a remote client (e.g. when Go is not behind
 | ||||
| // a proxy), can manifest as a vulnerability if your application uses these
 | ||||
| // headers for validating the 'trustworthiness' of a request.
 | ||||
| func ProxyHeaders(h http.Handler) http.Handler { | ||||
| 	fn := func(w http.ResponseWriter, r *http.Request) { | ||||
| 		// Set the remote IP with the value passed from the proxy.
 | ||||
| 		if fwd := getIP(r); fwd != "" { | ||||
| 			r.RemoteAddr = fwd | ||||
| 		} | ||||
| 
 | ||||
| 		// Set the scheme (proto) with the value passed from the proxy.
 | ||||
| 		if scheme := getScheme(r); scheme != "" { | ||||
| 			r.URL.Scheme = scheme | ||||
| 		} | ||||
| 
 | ||||
| 		// Call the next handler in the chain.
 | ||||
| 		h.ServeHTTP(w, r) | ||||
| 	} | ||||
| 
 | ||||
| 	return http.HandlerFunc(fn) | ||||
| } | ||||
| 
 | ||||
| // getIP retrieves the IP from the X-Forwarded-For, X-Real-IP and RFC7239
 | ||||
| // Forwarded headers (in that order).
 | ||||
| func getIP(r *http.Request) string { | ||||
| 	var addr string | ||||
| 
 | ||||
| 	if fwd := r.Header.Get(xForwardedFor); fwd != "" { | ||||
| 		// Only grab the first (client) address. Note that '192.168.0.1,
 | ||||
| 		// 10.1.1.1' is a valid key for X-Forwarded-For where addresses after
 | ||||
| 		// the first may represent forwarding proxies earlier in the chain.
 | ||||
| 		s := strings.Index(fwd, ", ") | ||||
| 		if s == -1 { | ||||
| 			s = len(fwd) | ||||
| 		} | ||||
| 		addr = fwd[:s] | ||||
| 	} else if fwd := r.Header.Get(xRealIP); fwd != "" { | ||||
| 		// X-Real-IP should only contain one IP address (the client making the
 | ||||
| 		// request).
 | ||||
| 		addr = fwd | ||||
| 	} else if fwd := r.Header.Get(forwarded); fwd != "" { | ||||
| 		// match should contain at least two elements if the protocol was
 | ||||
| 		// specified in the Forwarded header. The first element will always be
 | ||||
| 		// the 'for=' capture, which we ignore. In the case of multiple IP
 | ||||
| 		// addresses (for=8.8.8.8, 8.8.4.4,172.16.1.20 is valid) we only
 | ||||
| 		// extract the first, which should be the client IP.
 | ||||
| 		if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 { | ||||
| 			// IPv6 addresses in Forwarded headers are quoted-strings. We strip
 | ||||
| 			// these quotes.
 | ||||
| 			addr = strings.Trim(match[1], `"`) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return addr | ||||
| } | ||||
| 
 | ||||
| // getScheme retrieves the scheme from the X-Forwarded-Proto and RFC7239
 | ||||
| // Forwarded headers (in that order).
 | ||||
| func getScheme(r *http.Request) string { | ||||
| 	var scheme string | ||||
| 
 | ||||
| 	// Retrieve the scheme from X-Forwarded-Proto.
 | ||||
| 	if proto := r.Header.Get(xForwardedProto); proto != "" { | ||||
| 		scheme = strings.ToLower(proto) | ||||
| 	} else if proto := r.Header.Get(forwarded); proto != "" { | ||||
| 		// match should contain at least two elements if the protocol was
 | ||||
| 		// specified in the Forwarded header. The first element will always be
 | ||||
| 		// the 'proto=' capture, which we ignore. In the case of multiple proto
 | ||||
| 		// parameters (invalid) we only extract the first.
 | ||||
| 		if match := protoRegex.FindStringSubmatch(proto); len(match) > 1 { | ||||
| 			scheme = strings.ToLower(match[1]) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return scheme | ||||
| } | ||||
|  | @ -0,0 +1,21 @@ | |||
| The MIT License (MIT) | ||||
| 
 | ||||
| Copyright (c) 2014 Noah Watkins | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
|  | @ -0,0 +1,27 @@ | |||
| Copyright (c) 2009 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. | ||||
|  | @ -0,0 +1,22 @@ | |||
| Additional IP Rights Grant (Patents) | ||||
| 
 | ||||
| "This implementation" means the copyrightable works distributed by | ||||
| Google as part of the Go project. | ||||
| 
 | ||||
| Google 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, | ||||
| transfer and otherwise run, modify and propagate the contents of this | ||||
| implementation of Go, where such license applies only to those patent | ||||
| claims, both currently owned or controlled by Google and acquired in | ||||
| the future, licensable by Google that are necessarily infringed by this | ||||
| implementation of Go.  This grant does not include claims that would be | ||||
| infringed only as a consequence of further modification of this | ||||
| implementation.  If you or your agent or exclusive licensee institute or | ||||
| order or agree to the institution of patent litigation against any | ||||
| entity (including a cross-claim or counterclaim in a lawsuit) alleging | ||||
| that this implementation of Go or any code incorporated within this | ||||
| implementation of Go constitutes direct or contributory patent | ||||
| infringement, or inducement of patent infringement, then any patent | ||||
| rights granted to you under this License for this implementation of Go | ||||
| shall terminate as of the date such litigation is filed. | ||||
|  | @ -0,0 +1,27 @@ | |||
| Copyright (c) 2009 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. | ||||
|  | @ -0,0 +1,22 @@ | |||
| Additional IP Rights Grant (Patents) | ||||
| 
 | ||||
| "This implementation" means the copyrightable works distributed by | ||||
| Google as part of the Go project. | ||||
| 
 | ||||
| Google 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, | ||||
| transfer and otherwise run, modify and propagate the contents of this | ||||
| implementation of Go, where such license applies only to those patent | ||||
| claims, both currently owned or controlled by Google and acquired in | ||||
| the future, licensable by Google that are necessarily infringed by this | ||||
| implementation of Go.  This grant does not include claims that would be | ||||
| infringed only as a consequence of further modification of this | ||||
| implementation.  If you or your agent or exclusive licensee institute or | ||||
| order or agree to the institution of patent litigation against any | ||||
| entity (including a cross-claim or counterclaim in a lawsuit) alleging | ||||
| that this implementation of Go or any code incorporated within this | ||||
| implementation of Go constitutes direct or contributory patent | ||||
| infringement, or inducement of patent infringement, then any patent | ||||
| rights granted to you under this License for this implementation of Go | ||||
| shall terminate as of the date such litigation is filed. | ||||
							
								
								
									
										5
									
								
								blobs.go
								
								
								
								
							
							
						
						
									
										5
									
								
								blobs.go
								
								
								
								
							|  | @ -97,6 +97,11 @@ type BlobDeleter interface { | |||
| 	Delete(ctx context.Context, dgst digest.Digest) error | ||||
| } | ||||
| 
 | ||||
| // BlobEnumerator enables iterating over blobs from storage
 | ||||
| type BlobEnumerator interface { | ||||
| 	Enumerate(ctx context.Context, ingester func(dgst digest.Digest) error) error | ||||
| } | ||||
| 
 | ||||
| // BlobDescriptorService manages metadata about a blob by digest. Most
 | ||||
| // implementations will not expose such an interface explicitly. Such mappings
 | ||||
| // should be maintained by interacting with the BlobIngester. Hence, this is
 | ||||
|  |  | |||
|  | @ -20,5 +20,5 @@ import ( | |||
| ) | ||||
| 
 | ||||
| func main() { | ||||
| 	registry.Cmd.Execute() | ||||
| 	registry.RootCmd.Execute() | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,28 @@ | |||
| <!--[metadata]> | ||||
| +++ | ||||
| title = "Garbage Collection" | ||||
| description = "High level discussion of garabage collection" | ||||
| keywords = ["registry, garbage, images, tags, repository, distribution"] | ||||
| +++ | ||||
| <![end-metadata]--> | ||||
| 
 | ||||
| # What Garbage Collection Does | ||||
| 
 | ||||
| Garbage collection is a process that delete blobs to which no manifests refer. | ||||
| It runs in two phases. First, in the 'mark' phase, the process scans all the  | ||||
| manifests in the registry. From these manifests, it constructs a set of content  | ||||
| address digests. This set is the 'mark set' and denotes the set of blobs to *not* | ||||
| delete. Secondly, in the 'sweep' phase, the process scans all the blobs and if  | ||||
| a blob's content address digest is not in the mark set, the process will delete  | ||||
| it. | ||||
| 
 | ||||
| 
 | ||||
| # How to Run | ||||
| 
 | ||||
| You can run garbage collection by running | ||||
| 
 | ||||
| 	docker run --rm registry-image-name garbage-collect /etc/docker/registry/config.yml | ||||
| 
 | ||||
| NOTE: You should ensure that the registry itself is in read-only mode or not running at | ||||
| all. If you were to upload an image while garbage collection is running, there is the | ||||
| risk that the image's layers will be mistakenly deleted, leading to a corrupted image. | ||||
							
								
								
									
										16
									
								
								manifests.go
								
								
								
								
							
							
						
						
									
										16
									
								
								manifests.go
								
								
								
								
							|  | @ -53,12 +53,18 @@ type ManifestService interface { | |||
| 	// Delete removes the manifest specified by the given digest. Deleting
 | ||||
| 	// a manifest that doesn't exist will return ErrManifestNotFound
 | ||||
| 	Delete(ctx context.Context, dgst digest.Digest) error | ||||
| } | ||||
| 
 | ||||
| 	// Enumerate fills 'manifests' with the manifests in this service up
 | ||||
| 	// to the size of 'manifests' and returns 'n' for the number of entries
 | ||||
| 	// which were filled.  'last' contains an offset in the manifest set
 | ||||
| 	// and can be used to resume iteration.
 | ||||
| 	//Enumerate(ctx context.Context, manifests []Manifest, last Manifest) (n int, err error)
 | ||||
| // ManifestEnumerator enables iterating over manifests
 | ||||
| type ManifestEnumerator interface { | ||||
| 	// Enumerate calls ingester for each manifest.
 | ||||
| 	Enumerate(ctx context.Context, ingester func(digest.Digest) error) error | ||||
| } | ||||
| 
 | ||||
| // SignaturesGetter provides an interface for getting the signatures of a schema1 manifest. If the digest
 | ||||
| // refered to is not a schema1 manifest, an error should be returned.
 | ||||
| type SignaturesGetter interface { | ||||
| 	GetSignatures(ctx context.Context, manifestDigest digest.Digest) ([]digest.Digest, error) | ||||
| } | ||||
| 
 | ||||
| // Describable is an interface for descriptors
 | ||||
|  |  | |||
							
								
								
									
										11
									
								
								registry.go
								
								
								
								
							
							
						
						
									
										11
									
								
								registry.go
								
								
								
								
							|  | @ -40,6 +40,17 @@ type Namespace interface { | |||
| 	// which were filled.  'last' contains an offset in the catalog, and 'err' will be
 | ||||
| 	// set to io.EOF if there are no more entries to obtain.
 | ||||
| 	Repositories(ctx context.Context, repos []string, last string) (n int, err error) | ||||
| 
 | ||||
| 	// Blobs returns a blob enumerator to access all blobs
 | ||||
| 	Blobs() BlobEnumerator | ||||
| 
 | ||||
| 	// BlobStatter returns a BlobStatter to control
 | ||||
| 	BlobStatter() BlobStatter | ||||
| } | ||||
| 
 | ||||
| // RepositoryEnumerator describes an operation to enumerate repositories
 | ||||
| type RepositoryEnumerator interface { | ||||
| 	Enumerate(ctx context.Context, ingester func(string) error) error | ||||
| } | ||||
| 
 | ||||
| // ManifestServiceOption is a function argument for Manifest Service methods
 | ||||
|  |  | |||
|  | @ -0,0 +1,150 @@ | |||
| package registry | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 
 | ||||
| 	"github.com/docker/distribution" | ||||
| 	"github.com/docker/distribution/context" | ||||
| 	"github.com/docker/distribution/digest" | ||||
| 	"github.com/docker/distribution/manifest/schema1" | ||||
| 	"github.com/docker/distribution/manifest/schema2" | ||||
| 	"github.com/docker/distribution/reference" | ||||
| 	"github.com/docker/distribution/registry/storage" | ||||
| 	"github.com/docker/distribution/registry/storage/driver" | ||||
| 	"github.com/docker/distribution/registry/storage/driver/factory" | ||||
| 
 | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
| 
 | ||||
| func markAndSweep(storageDriver driver.StorageDriver) error { | ||||
| 	ctx := context.Background() | ||||
| 
 | ||||
| 	// Construct a registry
 | ||||
| 	registry, err := storage.NewRegistry(ctx, storageDriver) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to construct registry: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	repositoryEnumerator, ok := registry.(distribution.RepositoryEnumerator) | ||||
| 	if !ok { | ||||
| 		return fmt.Errorf("coercion error: unable to convert Namespace to RepositoryEnumerator") | ||||
| 	} | ||||
| 
 | ||||
| 	// mark
 | ||||
| 	markSet := make(map[digest.Digest]struct{}) | ||||
| 	err = repositoryEnumerator.Enumerate(ctx, func(repoName string) error { | ||||
| 		var err error | ||||
| 		named, err := reference.ParseNamed(repoName) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("failed to parse repo name %s: %v", repoName, err) | ||||
| 		} | ||||
| 		repository, err := registry.Repository(ctx, named) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("failed to construct repository: %v", err) | ||||
| 		} | ||||
| 
 | ||||
| 		manifestService, err := repository.Manifests(ctx) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("failed to construct manifest service: %v", err) | ||||
| 		} | ||||
| 
 | ||||
| 		manifestEnumerator, ok := manifestService.(distribution.ManifestEnumerator) | ||||
| 		if !ok { | ||||
| 			return fmt.Errorf("coercion error: unable to convert ManifestService into ManifestEnumerator") | ||||
| 		} | ||||
| 
 | ||||
| 		err = manifestEnumerator.Enumerate(ctx, func(dgst digest.Digest) error { | ||||
| 			// Mark the manifest's blob
 | ||||
| 			markSet[dgst] = struct{}{} | ||||
| 
 | ||||
| 			manifest, err := manifestService.Get(ctx, dgst) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("failed to retrieve manifest for digest %v: %v", dgst, err) | ||||
| 			} | ||||
| 
 | ||||
| 			descriptors := manifest.References() | ||||
| 			for _, descriptor := range descriptors { | ||||
| 				markSet[descriptor.Digest] = struct{}{} | ||||
| 			} | ||||
| 
 | ||||
| 			switch manifest.(type) { | ||||
| 			case *schema1.SignedManifest: | ||||
| 				signaturesGetter, ok := manifestService.(distribution.SignaturesGetter) | ||||
| 				if !ok { | ||||
| 					return fmt.Errorf("coercion error: unable to convert ManifestSErvice into SignaturesGetter") | ||||
| 				} | ||||
| 				signatures, err := signaturesGetter.GetSignatures(ctx, dgst) | ||||
| 				if err != nil { | ||||
| 					return fmt.Errorf("failed to get signatures for signed manifest: %v", err) | ||||
| 				} | ||||
| 				for _, signatureDigest := range signatures { | ||||
| 					markSet[signatureDigest] = struct{}{} | ||||
| 				} | ||||
| 				break | ||||
| 			case *schema2.DeserializedManifest: | ||||
| 				config := manifest.(*schema2.DeserializedManifest).Config | ||||
| 				markSet[config.Digest] = struct{}{} | ||||
| 				break | ||||
| 			} | ||||
| 
 | ||||
| 			return nil | ||||
| 		}) | ||||
| 
 | ||||
| 		return err | ||||
| 	}) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to mark: %v\n", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// sweep
 | ||||
| 	blobService := registry.Blobs() | ||||
| 	deleteSet := make(map[digest.Digest]struct{}) | ||||
| 	err = blobService.Enumerate(ctx, func(dgst digest.Digest) error { | ||||
| 		// check if digest is in markSet. If not, delete it!
 | ||||
| 		if _, ok := markSet[dgst]; !ok { | ||||
| 			deleteSet[dgst] = struct{}{} | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| 
 | ||||
| 	// Construct vacuum
 | ||||
| 	vacuum := storage.NewVacuum(ctx, storageDriver) | ||||
| 	for dgst := range deleteSet { | ||||
| 		err = vacuum.RemoveBlob(string(dgst)) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("failed to delete blob %s: %v\n", dgst, err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // GCCmd is the cobra command that corresponds to the garbage-collect subcommand
 | ||||
| var GCCmd = &cobra.Command{ | ||||
| 	Use:   "garbage-collect <config>", | ||||
| 	Short: "`garbage-collects` deletes layers not referenced by any manifests", | ||||
| 	Long:  "`garbage-collects` deletes layers not referenced by any manifests", | ||||
| 	Run: func(cmd *cobra.Command, args []string) { | ||||
| 
 | ||||
| 		config, err := resolveConfiguration(args) | ||||
| 		if err != nil { | ||||
| 			fmt.Fprintf(os.Stderr, "configuration error: %v\n", err) | ||||
| 			cmd.Usage() | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 
 | ||||
| 		driver, err := factory.Create(config.Storage.Type(), config.Storage.Parameters()) | ||||
| 		if err != nil { | ||||
| 			fmt.Fprintf(os.Stderr, "failed to construct %s driver: %v", config.Storage.Type(), err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 
 | ||||
| 		err = markAndSweep(driver) | ||||
| 		if err != nil { | ||||
| 			fmt.Fprintf(os.Stderr, "failed to garbage collect: %v", err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 	}, | ||||
| } | ||||
|  | @ -0,0 +1,343 @@ | |||
| package registry | ||||
| 
 | ||||
| import ( | ||||
| 	"io" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/docker/distribution" | ||||
| 	"github.com/docker/distribution/context" | ||||
| 	"github.com/docker/distribution/digest" | ||||
| 	"github.com/docker/distribution/reference" | ||||
| 	"github.com/docker/distribution/registry/storage" | ||||
| 	"github.com/docker/distribution/registry/storage/driver" | ||||
| 	"github.com/docker/distribution/registry/storage/driver/inmemory" | ||||
| 	"github.com/docker/distribution/testutil" | ||||
| ) | ||||
| 
 | ||||
| type image struct { | ||||
| 	manifest       distribution.Manifest | ||||
| 	manifestDigest digest.Digest | ||||
| 	layers         map[digest.Digest]io.ReadSeeker | ||||
| } | ||||
| 
 | ||||
| func createRegistry(t *testing.T, driver driver.StorageDriver) distribution.Namespace { | ||||
| 	ctx := context.Background() | ||||
| 	registry, err := storage.NewRegistry(ctx, driver, storage.EnableDelete) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to construct namespace") | ||||
| 	} | ||||
| 	return registry | ||||
| } | ||||
| 
 | ||||
| func makeRepository(t *testing.T, registry distribution.Namespace, name string) distribution.Repository { | ||||
| 	ctx := context.Background() | ||||
| 
 | ||||
| 	// Initialize a dummy repository
 | ||||
| 	named, err := reference.ParseNamed(name) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to parse name %s:  %v", name, err) | ||||
| 	} | ||||
| 
 | ||||
| 	repo, err := registry.Repository(ctx, named) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to construct repository: %v", err) | ||||
| 	} | ||||
| 	return repo | ||||
| } | ||||
| 
 | ||||
| func makeManifestService(t *testing.T, repository distribution.Repository) distribution.ManifestService { | ||||
| 	ctx := context.Background() | ||||
| 
 | ||||
| 	manifestService, err := repository.Manifests(ctx) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to construct manifest store: %v", err) | ||||
| 	} | ||||
| 	return manifestService | ||||
| } | ||||
| 
 | ||||
| func allBlobs(t *testing.T, registry distribution.Namespace) map[digest.Digest]struct{} { | ||||
| 	ctx := context.Background() | ||||
| 	blobService := registry.Blobs() | ||||
| 	allBlobsMap := make(map[digest.Digest]struct{}) | ||||
| 	err := blobService.Enumerate(ctx, func(dgst digest.Digest) error { | ||||
| 		allBlobsMap[dgst] = struct{}{} | ||||
| 		return nil | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Error getting all blobs: %v", err) | ||||
| 	} | ||||
| 	return allBlobsMap | ||||
| } | ||||
| 
 | ||||
| func uploadImage(t *testing.T, repository distribution.Repository, im image) digest.Digest { | ||||
| 	// upload layers
 | ||||
| 	err := testutil.UploadBlobs(repository, im.layers) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("layer upload failed: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// upload manifest
 | ||||
| 	ctx := context.Background() | ||||
| 	manifestService := makeManifestService(t, repository) | ||||
| 	manifestDigest, err := manifestService.Put(ctx, im.manifest) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("manifest upload failed: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return manifestDigest | ||||
| } | ||||
| 
 | ||||
| func uploadRandomSchema1Image(t *testing.T, repository distribution.Repository) image { | ||||
| 	randomLayers, err := testutil.CreateRandomLayers(2) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("%v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	digests := []digest.Digest{} | ||||
| 	for digest := range randomLayers { | ||||
| 		digests = append(digests, digest) | ||||
| 	} | ||||
| 
 | ||||
| 	manifest, err := testutil.MakeSchema1Manifest(digests) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("%v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	manifestDigest := uploadImage(t, repository, image{manifest: manifest, layers: randomLayers}) | ||||
| 	return image{ | ||||
| 		manifest:       manifest, | ||||
| 		manifestDigest: manifestDigest, | ||||
| 		layers:         randomLayers, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func uploadRandomSchema2Image(t *testing.T, repository distribution.Repository) image { | ||||
| 	randomLayers, err := testutil.CreateRandomLayers(2) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("%v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	digests := []digest.Digest{} | ||||
| 	for digest := range randomLayers { | ||||
| 		digests = append(digests, digest) | ||||
| 	} | ||||
| 
 | ||||
| 	manifest, err := testutil.MakeSchema2Manifest(repository, digests) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("%v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	manifestDigest := uploadImage(t, repository, image{manifest: manifest, layers: randomLayers}) | ||||
| 	return image{ | ||||
| 		manifest:       manifest, | ||||
| 		manifestDigest: manifestDigest, | ||||
| 		layers:         randomLayers, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestNoDeletionNoEffect(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
| 	inmemoryDriver := inmemory.New() | ||||
| 
 | ||||
| 	registry := createRegistry(t, inmemoryDriver) | ||||
| 	repo := makeRepository(t, registry, "palailogos") | ||||
| 	manifestService, err := repo.Manifests(ctx) | ||||
| 
 | ||||
| 	image1 := uploadRandomSchema1Image(t, repo) | ||||
| 	image2 := uploadRandomSchema1Image(t, repo) | ||||
| 	image3 := uploadRandomSchema2Image(t, repo) | ||||
| 
 | ||||
| 	// construct manifestlist for fun.
 | ||||
| 	blobstatter := registry.BlobStatter() | ||||
| 	manifestList, err := testutil.MakeManifestList(blobstatter, []digest.Digest{ | ||||
| 		image1.manifestDigest, image2.manifestDigest}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to make manifest list: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = manifestService.Put(ctx, manifestList) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to add manifest list: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Run GC
 | ||||
| 	err = markAndSweep(inmemoryDriver) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed mark and sweep: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	blobs := allBlobs(t, registry) | ||||
| 
 | ||||
| 	// the +1 at the end is for the manifestList
 | ||||
| 	// the first +3 at the end for each manifest's blob
 | ||||
| 	// the second +3 at the end for each manifest's signature/config layer
 | ||||
| 	totalBlobCount := len(image1.layers) + len(image2.layers) + len(image3.layers) + 1 + 3 + 3 | ||||
| 	if len(blobs) != totalBlobCount { | ||||
| 		t.Fatalf("Garbage collection affected storage") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestDeletionHasEffect(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
| 	inmemoryDriver := inmemory.New() | ||||
| 
 | ||||
| 	registry := createRegistry(t, inmemoryDriver) | ||||
| 	repo := makeRepository(t, registry, "komnenos") | ||||
| 	manifests, err := repo.Manifests(ctx) | ||||
| 
 | ||||
| 	image1 := uploadRandomSchema1Image(t, repo) | ||||
| 	image2 := uploadRandomSchema1Image(t, repo) | ||||
| 	image3 := uploadRandomSchema2Image(t, repo) | ||||
| 
 | ||||
| 	manifests.Delete(ctx, image2.manifestDigest) | ||||
| 	manifests.Delete(ctx, image3.manifestDigest) | ||||
| 
 | ||||
| 	// Run GC
 | ||||
| 	err = markAndSweep(inmemoryDriver) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed mark and sweep: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	blobs := allBlobs(t, registry) | ||||
| 
 | ||||
| 	// check that the image1 manifest and all the layers are still in blobs
 | ||||
| 	if _, ok := blobs[image1.manifestDigest]; !ok { | ||||
| 		t.Fatalf("First manifest is missing") | ||||
| 	} | ||||
| 
 | ||||
| 	for layer := range image1.layers { | ||||
| 		if _, ok := blobs[layer]; !ok { | ||||
| 			t.Fatalf("manifest 1 layer is missing: %v", layer) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// check that image2 and image3 layers are not still around
 | ||||
| 	for layer := range image2.layers { | ||||
| 		if _, ok := blobs[layer]; ok { | ||||
| 			t.Fatalf("manifest 2 layer is present: %v", layer) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for layer := range image3.layers { | ||||
| 		if _, ok := blobs[layer]; ok { | ||||
| 			t.Fatalf("manifest 3 layer is present: %v", layer) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func getAnyKey(digests map[digest.Digest]io.ReadSeeker) (d digest.Digest) { | ||||
| 	for d = range digests { | ||||
| 		break | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func getKeys(digests map[digest.Digest]io.ReadSeeker) (ds []digest.Digest) { | ||||
| 	for d := range digests { | ||||
| 		ds = append(ds, d) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func TestDeletionWithSharedLayer(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
| 	inmemoryDriver := inmemory.New() | ||||
| 
 | ||||
| 	registry := createRegistry(t, inmemoryDriver) | ||||
| 	repo := makeRepository(t, registry, "tzimiskes") | ||||
| 
 | ||||
| 	// Create random layers
 | ||||
| 	randomLayers1, err := testutil.CreateRandomLayers(3) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to make layers: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	randomLayers2, err := testutil.CreateRandomLayers(3) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to make layers: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Upload all layers
 | ||||
| 	err = testutil.UploadBlobs(repo, randomLayers1) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to upload layers: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = testutil.UploadBlobs(repo, randomLayers2) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to upload layers: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Construct manifests
 | ||||
| 	manifest1, err := testutil.MakeSchema1Manifest(getKeys(randomLayers1)) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to make manifest: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	sharedKey := getAnyKey(randomLayers1) | ||||
| 	manifest2, err := testutil.MakeSchema2Manifest(repo, append(getKeys(randomLayers2), sharedKey)) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to make manifest: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	manifestService := makeManifestService(t, repo) | ||||
| 
 | ||||
| 	// Upload manifests
 | ||||
| 	_, err = manifestService.Put(ctx, manifest1) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("manifest upload failed: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	manifestDigest2, err := manifestService.Put(ctx, manifest2) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("manifest upload failed: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// delete
 | ||||
| 	err = manifestService.Delete(ctx, manifestDigest2) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("manifest deletion failed: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// check that all of the layers in layer 1 are still there
 | ||||
| 	blobs := allBlobs(t, registry) | ||||
| 	for dgst := range randomLayers1 { | ||||
| 		if _, ok := blobs[dgst]; !ok { | ||||
| 			t.Fatalf("random layer 1 blob missing: %v", dgst) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestOrphanBlobDeleted(t *testing.T) { | ||||
| 	inmemoryDriver := inmemory.New() | ||||
| 
 | ||||
| 	registry := createRegistry(t, inmemoryDriver) | ||||
| 	repo := makeRepository(t, registry, "michael_z_doukas") | ||||
| 
 | ||||
| 	digests, err := testutil.CreateRandomLayers(1) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed to create random digest: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if err = testutil.UploadBlobs(repo, digests); err != nil { | ||||
| 		t.Fatalf("Failed to upload blob: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// formality to create the necessary directories
 | ||||
| 	uploadRandomSchema2Image(t, repo) | ||||
| 
 | ||||
| 	// Run GC
 | ||||
| 	err = markAndSweep(inmemoryDriver) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Failed mark and sweep: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	blobs := allBlobs(t, registry) | ||||
| 
 | ||||
| 	// check that orphan blob layers are not still around
 | ||||
| 	for dgst := range digests { | ||||
| 		if _, ok := blobs[dgst]; ok { | ||||
| 			t.Fatalf("Orphan layer is present: %v", dgst) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -93,8 +93,3 @@ func (pms proxyManifestStore) Put(ctx context.Context, manifest distribution.Man | |||
| func (pms proxyManifestStore) Delete(ctx context.Context, dgst digest.Digest) error { | ||||
| 	return distribution.ErrUnsupported | ||||
| } | ||||
| 
 | ||||
| /*func (pms proxyManifestStore) Enumerate(ctx context.Context, manifests []distribution.Manifest, last distribution.Manifest) (n int, err error) { | ||||
| 	return 0, distribution.ErrUnsupported | ||||
| } | ||||
| */ | ||||
|  |  | |||
|  | @ -166,6 +166,14 @@ func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named | |||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (pr *proxyingRegistry) Blobs() distribution.BlobEnumerator { | ||||
| 	return pr.embedded.Blobs() | ||||
| } | ||||
| 
 | ||||
| func (pr *proxyingRegistry) BlobStatter() distribution.BlobStatter { | ||||
| 	return pr.embedded.BlobStatter() | ||||
| } | ||||
| 
 | ||||
| // authChallenger encapsulates a request to the upstream to establish credential challenges
 | ||||
| type authChallenger interface { | ||||
| 	tryEstablishChallenges(context.Context) error | ||||
|  |  | |||
|  | @ -24,16 +24,12 @@ import ( | |||
| 	"github.com/yvasiyarov/gorelic" | ||||
| ) | ||||
| 
 | ||||
| // Cmd is a cobra command for running the registry.
 | ||||
| var Cmd = &cobra.Command{ | ||||
| 	Use:   "registry <config>", | ||||
| 	Short: "registry stores and distributes Docker images", | ||||
| 	Long:  "registry stores and distributes Docker images.", | ||||
| // ServeCmd is a cobra command for running the registry.
 | ||||
| var ServeCmd = &cobra.Command{ | ||||
| 	Use:   "serve <config>", | ||||
| 	Short: "`serve` stores and distributes Docker images", | ||||
| 	Long:  "`serve` stores and distributes Docker images.", | ||||
| 	Run: func(cmd *cobra.Command, args []string) { | ||||
| 		if showVersion { | ||||
| 			version.PrintVersion() | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		// setup context
 | ||||
| 		ctx := context.WithVersion(context.Background(), version.Version) | ||||
|  | @ -65,12 +61,6 @@ var Cmd = &cobra.Command{ | |||
| 	}, | ||||
| } | ||||
| 
 | ||||
| var showVersion bool | ||||
| 
 | ||||
| func init() { | ||||
| 	Cmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "show the version and exit") | ||||
| } | ||||
| 
 | ||||
| // A Registry represents a complete instance of the registry.
 | ||||
| // TODO(aaronl): It might make sense for Registry to become an interface.
 | ||||
| type Registry struct { | ||||
|  |  | |||
|  | @ -0,0 +1,28 @@ | |||
| package registry | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/docker/distribution/version" | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
| 
 | ||||
| var showVersion bool | ||||
| 
 | ||||
| func init() { | ||||
| 	RootCmd.AddCommand(ServeCmd) | ||||
| 	RootCmd.AddCommand(GCCmd) | ||||
| 	RootCmd.Flags().BoolVarP(&showVersion, "version", "v", false, "show the version and exit") | ||||
| } | ||||
| 
 | ||||
| // RootCmd is the main command for the 'registry' binary.
 | ||||
| var RootCmd = &cobra.Command{ | ||||
| 	Use:   "registry", | ||||
| 	Short: "`registry`", | ||||
| 	Long:  "`registry`", | ||||
| 	Run: func(cmd *cobra.Command, args []string) { | ||||
| 		if showVersion { | ||||
| 			version.PrintVersion() | ||||
| 			return | ||||
| 		} | ||||
| 		cmd.Usage() | ||||
| 	}, | ||||
| } | ||||
|  | @ -1,6 +1,8 @@ | |||
| package storage | ||||
| 
 | ||||
| import ( | ||||
| 	"path" | ||||
| 
 | ||||
| 	"github.com/docker/distribution" | ||||
| 	"github.com/docker/distribution/context" | ||||
| 	"github.com/docker/distribution/digest" | ||||
|  | @ -85,6 +87,36 @@ func (bs *blobStore) Put(ctx context.Context, mediaType string, p []byte) (distr | |||
| 	}, bs.driver.PutContent(ctx, bp, p) | ||||
| } | ||||
| 
 | ||||
| func (bs *blobStore) Enumerate(ctx context.Context, ingester func(dgst digest.Digest) error) error { | ||||
| 
 | ||||
| 	specPath, err := pathFor(blobsPathSpec{}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	err = Walk(ctx, bs.driver, specPath, func(fileInfo driver.FileInfo) error { | ||||
| 		// skip directories
 | ||||
| 		if fileInfo.IsDir() { | ||||
| 			return nil | ||||
| 		} | ||||
| 
 | ||||
| 		currentPath := fileInfo.Path() | ||||
| 		// we only want to parse paths that end with /data
 | ||||
| 		_, fileName := path.Split(currentPath) | ||||
| 		if fileName != "data" { | ||||
| 			return nil | ||||
| 		} | ||||
| 
 | ||||
| 		digest, err := digestFromPath(currentPath) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		return ingester(digest) | ||||
| 	}) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // path returns the canonical path for the blob identified by digest. The blob
 | ||||
| // may or may not exist.
 | ||||
| func (bs *blobStore) path(dgst digest.Digest) (string, error) { | ||||
|  |  | |||
|  | @ -64,3 +64,34 @@ func (reg *registry) Repositories(ctx context.Context, repos []string, last stri | |||
| 
 | ||||
| 	return n, errVal | ||||
| } | ||||
| 
 | ||||
| // Enumerate applies ingester to each repository
 | ||||
| func (reg *registry) Enumerate(ctx context.Context, ingester func(string) error) error { | ||||
| 	repoNameBuffer := make([]string, 100) | ||||
| 	var last string | ||||
| 	for { | ||||
| 		n, err := reg.Repositories(ctx, repoNameBuffer, last) | ||||
| 		if err != nil && err != io.EOF { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		if n == 0 { | ||||
| 			break | ||||
| 		} | ||||
| 
 | ||||
| 		last = repoNameBuffer[n-1] | ||||
| 		for i := 0; i < n; i++ { | ||||
| 			repoName := repoNameBuffer[i] | ||||
| 			err = ingester(repoName) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if err == io.EOF { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ package storage | |||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"path" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/docker/distribution" | ||||
|  | @ -37,6 +38,9 @@ type linkedBlobStore struct { | |||
| 	// removed an the blob links folder should be merged. The first entry is
 | ||||
| 	// treated as the "canonical" link location and will be used for writes.
 | ||||
| 	linkPathFns []linkPathFunc | ||||
| 
 | ||||
| 	// linkDirectoryPathSpec locates the root directories in which one might find links
 | ||||
| 	linkDirectoryPathSpec pathSpec | ||||
| } | ||||
| 
 | ||||
| var _ distribution.BlobStore = &linkedBlobStore{} | ||||
|  | @ -236,6 +240,55 @@ func (lbs *linkedBlobStore) Delete(ctx context.Context, dgst digest.Digest) erro | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (lbs *linkedBlobStore) Enumerate(ctx context.Context, ingestor func(digest.Digest) error) error { | ||||
| 	rootPath, err := pathFor(lbs.linkDirectoryPathSpec) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	err = Walk(ctx, lbs.blobStore.driver, rootPath, func(fileInfo driver.FileInfo) error { | ||||
| 		// exit early if directory...
 | ||||
| 		if fileInfo.IsDir() { | ||||
| 			return nil | ||||
| 		} | ||||
| 		filePath := fileInfo.Path() | ||||
| 
 | ||||
| 		// check if it's a link
 | ||||
| 		_, fileName := path.Split(filePath) | ||||
| 		if fileName != "link" { | ||||
| 			return nil | ||||
| 		} | ||||
| 
 | ||||
| 		// read the digest found in link
 | ||||
| 		digest, err := lbs.blobStore.readlink(ctx, filePath) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		// ensure this conforms to the linkPathFns
 | ||||
| 		_, err = lbs.Stat(ctx, digest) | ||||
| 		if err != nil { | ||||
| 			// we expect this error to occur so we move on
 | ||||
| 			if err == distribution.ErrBlobUnknown { | ||||
| 				return nil | ||||
| 			} | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		err = ingestor(digest) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		return nil | ||||
| 	}) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (lbs *linkedBlobStore) mount(ctx context.Context, sourceRepo reference.Named, dgst digest.Digest) (distribution.Descriptor, error) { | ||||
| 	repo, err := lbs.registry.Repository(ctx, sourceRepo) | ||||
| 	if err != nil { | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ package storage | |||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"path" | ||||
| 
 | ||||
| 	"encoding/json" | ||||
| 	"github.com/docker/distribution" | ||||
|  | @ -129,6 +130,52 @@ func (ms *manifestStore) Delete(ctx context.Context, dgst digest.Digest) error { | |||
| 	return ms.blobStore.Delete(ctx, dgst) | ||||
| } | ||||
| 
 | ||||
| func (ms *manifestStore) Enumerate(ctx context.Context, manifests []distribution.Manifest, last distribution.Manifest) (n int, err error) { | ||||
| 	return 0, distribution.ErrUnsupported | ||||
| func (ms *manifestStore) Enumerate(ctx context.Context, ingester func(digest.Digest) error) error { | ||||
| 	err := ms.blobStore.Enumerate(ctx, func(dgst digest.Digest) error { | ||||
| 		err := ingester(dgst) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // Only valid for schema1 signed manifests
 | ||||
| func (ms *manifestStore) GetSignatures(ctx context.Context, manifestDigest digest.Digest) ([]digest.Digest, error) { | ||||
| 	// sanity check that digest refers to a schema1 digest
 | ||||
| 	manifest, err := ms.Get(ctx, manifestDigest) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if _, ok := manifest.(*schema1.SignedManifest); !ok { | ||||
| 		return nil, fmt.Errorf("digest %v is not for schema1 manifest", manifestDigest) | ||||
| 	} | ||||
| 
 | ||||
| 	signaturesPath, err := pathFor(manifestSignaturesPathSpec{ | ||||
| 		name:     ms.repository.Named().Name(), | ||||
| 		revision: manifestDigest, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	signaturesPath = path.Join(signaturesPath, "sha256") | ||||
| 
 | ||||
| 	signaturePaths, err := ms.blobStore.driver.List(ctx, signaturesPath) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	var digests []digest.Digest | ||||
| 	for _, sigPath := range signaturePaths { | ||||
| 		sigdigest, err := digest.ParseDigest("sha256:" + path.Base(sigPath)) | ||||
| 		if err != nil { | ||||
| 			// merely found not a digest
 | ||||
| 			continue | ||||
| 		} | ||||
| 		digests = append(digests, sigdigest) | ||||
| 	} | ||||
| 	return digests, nil | ||||
| } | ||||
|  |  | |||
|  | @ -74,6 +74,7 @@ const ( | |||
| //
 | ||||
| //	Manifests:
 | ||||
| //
 | ||||
| // 	manifestRevisionsPathSpec:      <root>/v2/repositories/<name>/_manifests/revisions/
 | ||||
| // 	manifestRevisionPathSpec:      <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/
 | ||||
| // 	manifestRevisionLinkPathSpec:  <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/link
 | ||||
| // 	manifestSignaturesPathSpec:    <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/signatures/
 | ||||
|  | @ -100,6 +101,7 @@ const ( | |||
| //
 | ||||
| //	Blob Store:
 | ||||
| //
 | ||||
| //	blobsPathSpec:                  <root>/v2/blobs/
 | ||||
| // 	blobPathSpec:                   <root>/v2/blobs/<algorithm>/<first two hex bytes of digest>/<hex digest>
 | ||||
| // 	blobDataPathSpec:               <root>/v2/blobs/<algorithm>/<first two hex bytes of digest>/<hex digest>/data
 | ||||
| // 	blobMediaTypePathSpec:               <root>/v2/blobs/<algorithm>/<first two hex bytes of digest>/<hex digest>/data
 | ||||
|  | @ -125,6 +127,9 @@ func pathFor(spec pathSpec) (string, error) { | |||
| 
 | ||||
| 	switch v := spec.(type) { | ||||
| 
 | ||||
| 	case manifestRevisionsPathSpec: | ||||
| 		return path.Join(append(repoPrefix, v.name, "_manifests", "revisions")...), nil | ||||
| 
 | ||||
| 	case manifestRevisionPathSpec: | ||||
| 		components, err := digestPathComponents(v.revision, false) | ||||
| 		if err != nil { | ||||
|  | @ -246,6 +251,17 @@ func pathFor(spec pathSpec) (string, error) { | |||
| 		blobLinkPathComponents := append(repoPrefix, v.name, "_layers") | ||||
| 
 | ||||
| 		return path.Join(path.Join(append(blobLinkPathComponents, components...)...), "link"), nil | ||||
| 	case blobsPathSpec: | ||||
| 		blobsPathPrefix := append(rootPrefix, "blobs") | ||||
| 		return path.Join(blobsPathPrefix...), nil | ||||
| 	case blobPathSpec: | ||||
| 		components, err := digestPathComponents(v.digest, true) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 
 | ||||
| 		blobPathPrefix := append(rootPrefix, "blobs") | ||||
| 		return path.Join(append(blobPathPrefix, components...)...), nil | ||||
| 	case blobDataPathSpec: | ||||
| 		components, err := digestPathComponents(v.digest, true) | ||||
| 		if err != nil { | ||||
|  | @ -281,6 +297,14 @@ type pathSpec interface { | |||
| 	pathSpec() | ||||
| } | ||||
| 
 | ||||
| // manifestRevisionsPathSpec describes the directory path for
 | ||||
| // a manifest revision.
 | ||||
| type manifestRevisionsPathSpec struct { | ||||
| 	name string | ||||
| } | ||||
| 
 | ||||
| func (manifestRevisionsPathSpec) pathSpec() {} | ||||
| 
 | ||||
| // manifestRevisionPathSpec describes the components of the directory path for
 | ||||
| // a manifest revision.
 | ||||
| type manifestRevisionPathSpec struct { | ||||
|  | @ -404,12 +428,17 @@ var blobAlgorithmReplacer = strings.NewReplacer( | |||
| 	";", "/", | ||||
| ) | ||||
| 
 | ||||
| // // blobPathSpec contains the path for the registry global blob store.
 | ||||
| // type blobPathSpec struct {
 | ||||
| // 	digest digest.Digest
 | ||||
| // }
 | ||||
| // blobsPathSpec contains the path for the blobs directory
 | ||||
| type blobsPathSpec struct{} | ||||
| 
 | ||||
| // func (blobPathSpec) pathSpec() {}
 | ||||
| func (blobsPathSpec) pathSpec() {} | ||||
| 
 | ||||
| // blobPathSpec contains the path for the registry global blob store.
 | ||||
| type blobPathSpec struct { | ||||
| 	digest digest.Digest | ||||
| } | ||||
| 
 | ||||
| func (blobPathSpec) pathSpec() {} | ||||
| 
 | ||||
| // blobDataPathSpec contains the path for the registry global blob store. For
 | ||||
| // now, this contains layer data, exclusively.
 | ||||
|  | @ -491,3 +520,23 @@ func digestPathComponents(dgst digest.Digest, multilevel bool) ([]string, error) | |||
| 
 | ||||
| 	return append(prefix, suffix...), nil | ||||
| } | ||||
| 
 | ||||
| // Reconstructs a digest from a path
 | ||||
| func digestFromPath(digestPath string) (digest.Digest, error) { | ||||
| 
 | ||||
| 	digestPath = strings.TrimSuffix(digestPath, "/data") | ||||
| 	dir, hex := path.Split(digestPath) | ||||
| 	dir = path.Dir(dir) | ||||
| 	dir, next := path.Split(dir) | ||||
| 
 | ||||
| 	// next is either the algorithm OR the first two characters in the hex string
 | ||||
| 	var algo string | ||||
| 	if next == hex[:2] { | ||||
| 		algo = path.Base(dir) | ||||
| 	} else { | ||||
| 		algo = next | ||||
| 	} | ||||
| 
 | ||||
| 	dgst := digest.NewDigestFromHex(algo, hex) | ||||
| 	return dgst, dgst.Validate() | ||||
| } | ||||
|  |  | |||
|  | @ -2,6 +2,8 @@ package storage | |||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/docker/distribution/digest" | ||||
| ) | ||||
| 
 | ||||
| func TestPathMapper(t *testing.T) { | ||||
|  | @ -120,3 +122,29 @@ func TestPathMapper(t *testing.T) { | |||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func TestDigestFromPath(t *testing.T) { | ||||
| 	for _, testcase := range []struct { | ||||
| 		path       string | ||||
| 		expected   digest.Digest | ||||
| 		multilevel bool | ||||
| 		err        error | ||||
| 	}{ | ||||
| 		{ | ||||
| 			path:       "/docker/registry/v2/blobs/sha256/99/9943fffae777400c0344c58869c4c2619c329ca3ad4df540feda74d291dd7c86/data", | ||||
| 			multilevel: true, | ||||
| 			expected:   "sha256:9943fffae777400c0344c58869c4c2619c329ca3ad4df540feda74d291dd7c86", | ||||
| 			err:        nil, | ||||
| 		}, | ||||
| 	} { | ||||
| 		result, err := digestFromPath(testcase.path) | ||||
| 		if err != testcase.err { | ||||
| 			t.Fatalf("Unexpected error value %v when we wanted %v", err, testcase.err) | ||||
| 		} | ||||
| 
 | ||||
| 		if result != testcase.expected { | ||||
| 			t.Fatalf("Unexpected result value %v when we wanted %v", result, testcase.expected) | ||||
| 
 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -147,6 +147,14 @@ func (reg *registry) Repository(ctx context.Context, canonicalName reference.Nam | |||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (reg *registry) Blobs() distribution.BlobEnumerator { | ||||
| 	return reg.blobStore | ||||
| } | ||||
| 
 | ||||
| func (reg *registry) BlobStatter() distribution.BlobStatter { | ||||
| 	return reg.statter | ||||
| } | ||||
| 
 | ||||
| // repository provides name-scoped access to various services.
 | ||||
| type repository struct { | ||||
| 	*registry | ||||
|  | @ -180,6 +188,8 @@ func (repo *repository) Manifests(ctx context.Context, options ...distribution.M | |||
| 		blobLinkPath, | ||||
| 	} | ||||
| 
 | ||||
| 	manifestDirectoryPathSpec := manifestRevisionsPathSpec{name: repo.name.Name()} | ||||
| 
 | ||||
| 	blobStore := &linkedBlobStore{ | ||||
| 		ctx:           ctx, | ||||
| 		blobStore:     repo.blobStore, | ||||
|  | @ -193,7 +203,8 @@ func (repo *repository) Manifests(ctx context.Context, options ...distribution.M | |||
| 
 | ||||
| 		// TODO(stevvooe): linkPath limits this blob store to only
 | ||||
| 		// manifests. This instance cannot be used for blob checks.
 | ||||
| 		linkPathFns: manifestLinkPathFns, | ||||
| 		linkPathFns:           manifestLinkPathFns, | ||||
| 		linkDirectoryPathSpec: manifestDirectoryPathSpec, | ||||
| 	} | ||||
| 
 | ||||
| 	ms := &manifestStore{ | ||||
|  |  | |||
|  | @ -34,11 +34,13 @@ func (v Vacuum) RemoveBlob(dgst string) error { | |||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	blobPath, err := pathFor(blobDataPathSpec{digest: d}) | ||||
| 	blobPath, err := pathFor(blobPathSpec{digest: d}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	context.GetLogger(v.ctx).Infof("Deleting blob: %s", blobPath) | ||||
| 
 | ||||
| 	err = v.driver.Delete(v.ctx, blobPath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
|  |  | |||
|  | @ -0,0 +1,87 @@ | |||
| package testutil | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"github.com/docker/distribution" | ||||
| 	"github.com/docker/distribution/context" | ||||
| 	"github.com/docker/distribution/digest" | ||||
| 	"github.com/docker/distribution/manifest" | ||||
| 	"github.com/docker/distribution/manifest/manifestlist" | ||||
| 	"github.com/docker/distribution/manifest/schema1" | ||||
| 	"github.com/docker/distribution/manifest/schema2" | ||||
| 	"github.com/docker/libtrust" | ||||
| ) | ||||
| 
 | ||||
| // MakeManifestList constructs a manifest list out of a list of manifest digests
 | ||||
| func MakeManifestList(blobstatter distribution.BlobStatter, manifestDigests []digest.Digest) (*manifestlist.DeserializedManifestList, error) { | ||||
| 	ctx := context.Background() | ||||
| 
 | ||||
| 	var manifestDescriptors []manifestlist.ManifestDescriptor | ||||
| 	for _, manifestDigest := range manifestDigests { | ||||
| 		descriptor, err := blobstatter.Stat(ctx, manifestDigest) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		platformSpec := manifestlist.PlatformSpec{ | ||||
| 			Architecture: "atari2600", | ||||
| 			OS:           "CP/M", | ||||
| 			Variant:      "ternary", | ||||
| 			Features:     []string{"VLIW", "superscalaroutoforderdevnull"}, | ||||
| 		} | ||||
| 		manifestDescriptor := manifestlist.ManifestDescriptor{ | ||||
| 			Descriptor: descriptor, | ||||
| 			Platform:   platformSpec, | ||||
| 		} | ||||
| 		manifestDescriptors = append(manifestDescriptors, manifestDescriptor) | ||||
| 	} | ||||
| 
 | ||||
| 	return manifestlist.FromDescriptors(manifestDescriptors) | ||||
| } | ||||
| 
 | ||||
| // MakeSchema1Manifest constructs a schema 1 manifest from a given list of digests and returns
 | ||||
| // the digest of the manifest
 | ||||
| func MakeSchema1Manifest(digests []digest.Digest) (distribution.Manifest, error) { | ||||
| 	manifest := schema1.Manifest{ | ||||
| 		Versioned: manifest.Versioned{ | ||||
| 			SchemaVersion: 1, | ||||
| 		}, | ||||
| 		Name: "who", | ||||
| 		Tag:  "cares", | ||||
| 	} | ||||
| 
 | ||||
| 	for _, digest := range digests { | ||||
| 		manifest.FSLayers = append(manifest.FSLayers, schema1.FSLayer{BlobSum: digest}) | ||||
| 		manifest.History = append(manifest.History, schema1.History{V1Compatibility: ""}) | ||||
| 	} | ||||
| 
 | ||||
| 	pk, err := libtrust.GenerateECP256PrivateKey() | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("unexpected error generating private key: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	signedManifest, err := schema1.Sign(&manifest, pk) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error signing manifest: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return signedManifest, nil | ||||
| } | ||||
| 
 | ||||
| // MakeSchema2Manifest constructs a schema 2 manifest from a given list of digests and returns
 | ||||
| // the digest of the manifest
 | ||||
| func MakeSchema2Manifest(repository distribution.Repository, digests []digest.Digest) (distribution.Manifest, error) { | ||||
| 	ctx := context.Background() | ||||
| 	blobStore := repository.Blobs(ctx) | ||||
| 	builder := schema2.NewManifestBuilder(blobStore, []byte{}) | ||||
| 	for _, digest := range digests { | ||||
| 		builder.AppendReference(distribution.Descriptor{Digest: digest}) | ||||
| 	} | ||||
| 
 | ||||
| 	manifest, err := builder.Build(ctx) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("unexpected error generating manifest: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return manifest, nil | ||||
| } | ||||
|  | @ -9,6 +9,8 @@ import ( | |||
| 	mrand "math/rand" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/docker/distribution" | ||||
| 	"github.com/docker/distribution/context" | ||||
| 	"github.com/docker/distribution/digest" | ||||
| ) | ||||
| 
 | ||||
|  | @ -76,3 +78,39 @@ func CreateRandomTarFile() (rs io.ReadSeeker, dgst digest.Digest, err error) { | |||
| 
 | ||||
| 	return bytes.NewReader(target.Bytes()), dgst, nil | ||||
| } | ||||
| 
 | ||||
| // CreateRandomLayers returns a map of n digests. We don't particularly care
 | ||||
| // about the order of said digests (since they're all random anyway).
 | ||||
| func CreateRandomLayers(n int) (map[digest.Digest]io.ReadSeeker, error) { | ||||
| 	digestMap := map[digest.Digest]io.ReadSeeker{} | ||||
| 	for i := 0; i < n; i++ { | ||||
| 		rs, ds, err := CreateRandomTarFile() | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("unexpected error generating test layer file: %v", err) | ||||
| 		} | ||||
| 
 | ||||
| 		dgst := digest.Digest(ds) | ||||
| 		digestMap[dgst] = rs | ||||
| 	} | ||||
| 	return digestMap, nil | ||||
| } | ||||
| 
 | ||||
| // UploadBlobs lets you upload blobs to a repository
 | ||||
| func UploadBlobs(repository distribution.Repository, layers map[digest.Digest]io.ReadSeeker) error { | ||||
| 	ctx := context.Background() | ||||
| 	for digest, rs := range layers { | ||||
| 		wr, err := repository.Blobs(ctx).Create(ctx) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("unexpected error creating upload: %v", err) | ||||
| 		} | ||||
| 
 | ||||
| 		if _, err := io.Copy(wr, rs); err != nil { | ||||
| 			return fmt.Errorf("unexpected error copying to upload: %v", err) | ||||
| 		} | ||||
| 
 | ||||
| 		if _, err := wr.Commit(ctx, distribution.Descriptor{Digest: digest}); err != nil { | ||||
| 			return fmt.Errorf("unexpected error committinng upload: %v", err) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue