forked from TrueCloudLab/distribution
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>
This commit is contained in:
parent
e430d77342
commit
feab4aafbc
34 changed files with 1702 additions and 36 deletions
|
@ -16,4 +16,4 @@ RUN make PREFIX=/go clean binaries
|
||||||
VOLUME ["/var/lib/registry"]
|
VOLUME ["/var/lib/registry"]
|
||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
ENTRYPOINT ["registry"]
|
ENTRYPOINT ["registry"]
|
||||||
CMD ["/etc/docker/registry/config.yml"]
|
CMD ["serve", "/etc/docker/registry/config.yml"]
|
||||||
|
|
202
Godeps/_workspace/src/github.com/aws/aws-sdk-go/LICENSE.txt
generated
vendored
Normal file
202
Godeps/_workspace/src/github.com/aws/aws-sdk-go/LICENSE.txt
generated
vendored
Normal file
|
@ -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.
|
3
Godeps/_workspace/src/github.com/aws/aws-sdk-go/NOTICE.txt
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/aws/aws-sdk-go/NOTICE.txt
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
AWS SDK for Go
|
||||||
|
Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||||
|
Copyright 2014-2015 Stripe, Inc.
|
185
Godeps/_workspace/src/github.com/docker/goamz/LICENSE
generated
vendored
Normal file
185
Godeps/_workspace/src/github.com/docker/goamz/LICENSE
generated
vendored
Normal file
|
@ -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.
|
22
Godeps/_workspace/src/github.com/gorilla/handlers/LICENSE
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/gorilla/handlers/LICENSE
generated
vendored
Normal file
|
@ -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.
|
71
Godeps/_workspace/src/github.com/gorilla/handlers/canonical.go
generated
vendored
Normal file
71
Godeps/_workspace/src/github.com/gorilla/handlers/canonical.go
generated
vendored
Normal file
|
@ -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
|
||||||
|
}
|
9
Godeps/_workspace/src/github.com/gorilla/handlers/doc.go
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/gorilla/handlers/doc.go
generated
vendored
Normal file
|
@ -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
|
||||||
|
}
|
21
Godeps/_workspace/src/github.com/noahdesu/go-ceph/LICENSE
generated
vendored
Normal file
21
Godeps/_workspace/src/github.com/noahdesu/go-ceph/LICENSE
generated
vendored
Normal file
|
@ -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.
|
27
Godeps/_workspace/src/golang.org/x/crypto/LICENSE
generated
vendored
Normal file
27
Godeps/_workspace/src/golang.org/x/crypto/LICENSE
generated
vendored
Normal file
|
@ -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.
|
22
Godeps/_workspace/src/golang.org/x/crypto/PATENTS
generated
vendored
Normal file
22
Godeps/_workspace/src/golang.org/x/crypto/PATENTS
generated
vendored
Normal file
|
@ -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.
|
27
Godeps/_workspace/src/golang.org/x/net/LICENSE
generated
vendored
Normal file
27
Godeps/_workspace/src/golang.org/x/net/LICENSE
generated
vendored
Normal file
|
@ -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.
|
22
Godeps/_workspace/src/golang.org/x/net/PATENTS
generated
vendored
Normal file
22
Godeps/_workspace/src/golang.org/x/net/PATENTS
generated
vendored
Normal file
|
@ -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
|
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
|
// BlobDescriptorService manages metadata about a blob by digest. Most
|
||||||
// implementations will not expose such an interface explicitly. Such mappings
|
// implementations will not expose such an interface explicitly. Such mappings
|
||||||
// should be maintained by interacting with the BlobIngester. Hence, this is
|
// should be maintained by interacting with the BlobIngester. Hence, this is
|
||||||
|
|
|
@ -20,5 +20,5 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
registry.Cmd.Execute()
|
registry.RootCmd.Execute()
|
||||||
}
|
}
|
||||||
|
|
28
docs/gc.md
Normal file
28
docs/gc.md
Normal file
|
@ -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
|
// Delete removes the manifest specified by the given digest. Deleting
|
||||||
// a manifest that doesn't exist will return ErrManifestNotFound
|
// a manifest that doesn't exist will return ErrManifestNotFound
|
||||||
Delete(ctx context.Context, dgst digest.Digest) error
|
Delete(ctx context.Context, dgst digest.Digest) error
|
||||||
|
}
|
||||||
|
|
||||||
// Enumerate fills 'manifests' with the manifests in this service up
|
// ManifestEnumerator enables iterating over manifests
|
||||||
// to the size of 'manifests' and returns 'n' for the number of entries
|
type ManifestEnumerator interface {
|
||||||
// which were filled. 'last' contains an offset in the manifest set
|
// Enumerate calls ingester for each manifest.
|
||||||
// and can be used to resume iteration.
|
Enumerate(ctx context.Context, ingester func(digest.Digest) error) error
|
||||||
//Enumerate(ctx context.Context, manifests []Manifest, last Manifest) (n int, err 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
|
// 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
|
// 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.
|
// set to io.EOF if there are no more entries to obtain.
|
||||||
Repositories(ctx context.Context, repos []string, last string) (n int, err error)
|
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
|
// ManifestServiceOption is a function argument for Manifest Service methods
|
||||||
|
|
150
registry/garbagecollect.go
Normal file
150
registry/garbagecollect.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
343
registry/garbagecollect_test.go
Normal file
343
registry/garbagecollect_test.go
Normal file
|
@ -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 {
|
func (pms proxyManifestStore) Delete(ctx context.Context, dgst digest.Digest) error {
|
||||||
return distribution.ErrUnsupported
|
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
|
}, 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
|
// authChallenger encapsulates a request to the upstream to establish credential challenges
|
||||||
type authChallenger interface {
|
type authChallenger interface {
|
||||||
tryEstablishChallenges(context.Context) error
|
tryEstablishChallenges(context.Context) error
|
||||||
|
|
|
@ -24,16 +24,12 @@ import (
|
||||||
"github.com/yvasiyarov/gorelic"
|
"github.com/yvasiyarov/gorelic"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cmd is a cobra command for running the registry.
|
// ServeCmd is a cobra command for running the registry.
|
||||||
var Cmd = &cobra.Command{
|
var ServeCmd = &cobra.Command{
|
||||||
Use: "registry <config>",
|
Use: "serve <config>",
|
||||||
Short: "registry stores and distributes Docker images",
|
Short: "`serve` stores and distributes Docker images",
|
||||||
Long: "registry stores and distributes Docker images.",
|
Long: "`serve` stores and distributes Docker images.",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if showVersion {
|
|
||||||
version.PrintVersion()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup context
|
// setup context
|
||||||
ctx := context.WithVersion(context.Background(), version.Version)
|
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.
|
// A Registry represents a complete instance of the registry.
|
||||||
// TODO(aaronl): It might make sense for Registry to become an interface.
|
// TODO(aaronl): It might make sense for Registry to become an interface.
|
||||||
type Registry struct {
|
type Registry struct {
|
||||||
|
|
28
registry/root.go
Normal file
28
registry/root.go
Normal file
|
@ -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
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/digest"
|
"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)
|
}, 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
|
// path returns the canonical path for the blob identified by digest. The blob
|
||||||
// may or may not exist.
|
// may or may not exist.
|
||||||
func (bs *blobStore) path(dgst digest.Digest) (string, error) {
|
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
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
|
@ -37,6 +38,9 @@ type linkedBlobStore struct {
|
||||||
// removed an the blob links folder should be merged. The first entry is
|
// 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.
|
// treated as the "canonical" link location and will be used for writes.
|
||||||
linkPathFns []linkPathFunc
|
linkPathFns []linkPathFunc
|
||||||
|
|
||||||
|
// linkDirectoryPathSpec locates the root directories in which one might find links
|
||||||
|
linkDirectoryPathSpec pathSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ distribution.BlobStore = &linkedBlobStore{}
|
var _ distribution.BlobStore = &linkedBlobStore{}
|
||||||
|
@ -236,6 +240,55 @@ func (lbs *linkedBlobStore) Delete(ctx context.Context, dgst digest.Digest) erro
|
||||||
return nil
|
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) {
|
func (lbs *linkedBlobStore) mount(ctx context.Context, sourceRepo reference.Named, dgst digest.Digest) (distribution.Descriptor, error) {
|
||||||
repo, err := lbs.registry.Repository(ctx, sourceRepo)
|
repo, err := lbs.registry.Repository(ctx, sourceRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/docker/distribution"
|
"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)
|
return ms.blobStore.Delete(ctx, dgst)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *manifestStore) Enumerate(ctx context.Context, manifests []distribution.Manifest, last distribution.Manifest) (n int, err error) {
|
func (ms *manifestStore) Enumerate(ctx context.Context, ingester func(digest.Digest) error) error {
|
||||||
return 0, distribution.ErrUnsupported
|
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:
|
// Manifests:
|
||||||
//
|
//
|
||||||
|
// manifestRevisionsPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/
|
||||||
// manifestRevisionPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/
|
// manifestRevisionPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/
|
||||||
// manifestRevisionLinkPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/link
|
// manifestRevisionLinkPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/link
|
||||||
// manifestSignaturesPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/signatures/
|
// manifestSignaturesPathSpec: <root>/v2/repositories/<name>/_manifests/revisions/<algorithm>/<hex digest>/signatures/
|
||||||
|
@ -100,6 +101,7 @@ const (
|
||||||
//
|
//
|
||||||
// Blob Store:
|
// Blob Store:
|
||||||
//
|
//
|
||||||
|
// blobsPathSpec: <root>/v2/blobs/
|
||||||
// blobPathSpec: <root>/v2/blobs/<algorithm>/<first two hex bytes of digest>/<hex digest>
|
// 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
|
// 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
|
// 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) {
|
switch v := spec.(type) {
|
||||||
|
|
||||||
|
case manifestRevisionsPathSpec:
|
||||||
|
return path.Join(append(repoPrefix, v.name, "_manifests", "revisions")...), nil
|
||||||
|
|
||||||
case manifestRevisionPathSpec:
|
case manifestRevisionPathSpec:
|
||||||
components, err := digestPathComponents(v.revision, false)
|
components, err := digestPathComponents(v.revision, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -246,6 +251,17 @@ func pathFor(spec pathSpec) (string, error) {
|
||||||
blobLinkPathComponents := append(repoPrefix, v.name, "_layers")
|
blobLinkPathComponents := append(repoPrefix, v.name, "_layers")
|
||||||
|
|
||||||
return path.Join(path.Join(append(blobLinkPathComponents, components...)...), "link"), nil
|
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:
|
case blobDataPathSpec:
|
||||||
components, err := digestPathComponents(v.digest, true)
|
components, err := digestPathComponents(v.digest, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -281,6 +297,14 @@ type pathSpec interface {
|
||||||
pathSpec()
|
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
|
// manifestRevisionPathSpec describes the components of the directory path for
|
||||||
// a manifest revision.
|
// a manifest revision.
|
||||||
type manifestRevisionPathSpec struct {
|
type manifestRevisionPathSpec struct {
|
||||||
|
@ -404,12 +428,17 @@ var blobAlgorithmReplacer = strings.NewReplacer(
|
||||||
";", "/",
|
";", "/",
|
||||||
)
|
)
|
||||||
|
|
||||||
// // blobPathSpec contains the path for the registry global blob store.
|
// blobsPathSpec contains the path for the blobs directory
|
||||||
// type blobPathSpec struct {
|
type blobsPathSpec struct{}
|
||||||
// digest digest.Digest
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 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
|
// blobDataPathSpec contains the path for the registry global blob store. For
|
||||||
// now, this contains layer data, exclusively.
|
// now, this contains layer data, exclusively.
|
||||||
|
@ -491,3 +520,23 @@ func digestPathComponents(dgst digest.Digest, multilevel bool) ([]string, error)
|
||||||
|
|
||||||
return append(prefix, suffix...), nil
|
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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/digest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPathMapper(t *testing.T) {
|
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
|
}, 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.
|
// repository provides name-scoped access to various services.
|
||||||
type repository struct {
|
type repository struct {
|
||||||
*registry
|
*registry
|
||||||
|
@ -180,6 +188,8 @@ func (repo *repository) Manifests(ctx context.Context, options ...distribution.M
|
||||||
blobLinkPath,
|
blobLinkPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
manifestDirectoryPathSpec := manifestRevisionsPathSpec{name: repo.name.Name()}
|
||||||
|
|
||||||
blobStore := &linkedBlobStore{
|
blobStore := &linkedBlobStore{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
blobStore: repo.blobStore,
|
blobStore: repo.blobStore,
|
||||||
|
@ -194,6 +204,7 @@ func (repo *repository) Manifests(ctx context.Context, options ...distribution.M
|
||||||
// TODO(stevvooe): linkPath limits this blob store to only
|
// TODO(stevvooe): linkPath limits this blob store to only
|
||||||
// manifests. This instance cannot be used for blob checks.
|
// manifests. This instance cannot be used for blob checks.
|
||||||
linkPathFns: manifestLinkPathFns,
|
linkPathFns: manifestLinkPathFns,
|
||||||
|
linkDirectoryPathSpec: manifestDirectoryPathSpec,
|
||||||
}
|
}
|
||||||
|
|
||||||
ms := &manifestStore{
|
ms := &manifestStore{
|
||||||
|
|
|
@ -34,11 +34,13 @@ func (v Vacuum) RemoveBlob(dgst string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
blobPath, err := pathFor(blobDataPathSpec{digest: d})
|
blobPath, err := pathFor(blobPathSpec{digest: d})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
context.GetLogger(v.ctx).Infof("Deleting blob: %s", blobPath)
|
context.GetLogger(v.ctx).Infof("Deleting blob: %s", blobPath)
|
||||||
|
|
||||||
err = v.driver.Delete(v.ctx, blobPath)
|
err = v.driver.Delete(v.ctx, blobPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
87
testutil/manifests.go
Normal file
87
testutil/manifests.go
Normal file
|
@ -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"
|
mrand "math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/distribution"
|
||||||
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/digest"
|
"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
|
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 a new issue