pull in containers/image deps
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
This commit is contained in:
parent
05f679f643
commit
d8ae7178e2
584 changed files with 168546 additions and 5 deletions
37
vendor/github.com/docker/distribution/.gitignore
generated
vendored
Normal file
37
vendor/github.com/docker/distribution/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
# never checkin from the bin file (for now)
|
||||
bin/*
|
||||
|
||||
# Test key files
|
||||
*.pem
|
||||
|
||||
# Cover profiles
|
||||
*.out
|
||||
|
||||
# Editor/IDE specific files.
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
18
vendor/github.com/docker/distribution/.mailmap
generated
vendored
Normal file
18
vendor/github.com/docker/distribution/.mailmap
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@users.noreply.github.com>
|
||||
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@gmail.com>
|
||||
Olivier Gambier <olivier@docker.com> Olivier Gambier <dmp42@users.noreply.github.com>
|
||||
Brian Bland <brian.bland@docker.com> Brian Bland <r4nd0m1n4t0r@gmail.com>
|
||||
Brian Bland <brian.bland@docker.com> Brian Bland <brian.t.bland@gmail.com>
|
||||
Josh Hawn <josh.hawn@docker.com> Josh Hawn <jlhawn@berkeley.edu>
|
||||
Richard Scothern <richard.scothern@docker.com> Richard <richard.scothern@gmail.com>
|
||||
Richard Scothern <richard.scothern@docker.com> Richard Scothern <richard.scothern@gmail.com>
|
||||
Andrew Meredith <andymeredith@gmail.com> Andrew Meredith <kendru@users.noreply.github.com>
|
||||
harche <p.harshal@gmail.com> harche <harche@users.noreply.github.com>
|
||||
Jessie Frazelle <jessie@docker.com> <jfrazelle@users.noreply.github.com>
|
||||
Sharif Nassar <sharif@mrwacky.com> Sharif Nassar <mrwacky42@users.noreply.github.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> Sven Dowideit <SvenDowideit@users.noreply.github.com>
|
||||
Vincent Giersch <vincent.giersch@ovh.net> Vincent Giersch <vincent@giersch.fr>
|
||||
davidli <wenquan.li@hp.com> davidli <wenquan.li@hpe.com>
|
||||
Omer Cohen <git@omer.io> Omer Cohen <git@omerc.net>
|
||||
Eric Yang <windfarer@gmail.com> Eric Yang <Windfarer@users.noreply.github.com>
|
||||
Nikita Tarasov <nikita@mygento.ru> Nikita <luckyraul@users.noreply.github.com>
|
147
vendor/github.com/docker/distribution/AUTHORS
generated
vendored
Normal file
147
vendor/github.com/docker/distribution/AUTHORS
generated
vendored
Normal file
|
@ -0,0 +1,147 @@
|
|||
Aaron Lehmann <aaron.lehmann@docker.com>
|
||||
Aaron Schlesinger <aschlesinger@deis.com>
|
||||
Aaron Vinson <avinson.public@gmail.com>
|
||||
Adam Enger <adamenger@gmail.com>
|
||||
Adrian Mouat <adrian.mouat@gmail.com>
|
||||
Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>
|
||||
Alex Chan <alex.chan@metaswitch.com>
|
||||
Alex Elman <aelman@indeed.com>
|
||||
Alexey Gladkov <gladkov.alexey@gmail.com>
|
||||
allencloud <allen.sun@daocloud.io>
|
||||
amitshukla <ashukla73@hotmail.com>
|
||||
Amy Lindburg <amy.lindburg@docker.com>
|
||||
Andrew Hsu <andrewhsu@acm.org>
|
||||
Andrew Meredith <andymeredith@gmail.com>
|
||||
Andrew T Nguyen <andrew.nguyen@docker.com>
|
||||
Andrey Kostov <kostov.andrey@gmail.com>
|
||||
Andy Goldstein <agoldste@redhat.com>
|
||||
Anis Elleuch <vadmeste@gmail.com>
|
||||
Anton Tiurin <noxiouz@yandex.ru>
|
||||
Antonio Mercado <amercado@thinknode.com>
|
||||
Antonio Murdaca <runcom@redhat.com>
|
||||
Arien Holthuizen <aholthuizen@schubergphilis.com>
|
||||
Arnaud Porterie <arnaud.porterie@docker.com>
|
||||
Arthur Baars <arthur@semmle.com>
|
||||
Asuka Suzuki <hello@tanksuzuki.com>
|
||||
Avi Miller <avi.miller@oracle.com>
|
||||
Ayose Cazorla <ayosec@gmail.com>
|
||||
BadZen <dave.trombley@gmail.com>
|
||||
Ben Firshman <ben@firshman.co.uk>
|
||||
bin liu <liubin0329@gmail.com>
|
||||
Brian Bland <brian.bland@docker.com>
|
||||
burnettk <burnettk@gmail.com>
|
||||
Carson A <ca@carsonoid.net>
|
||||
Chris Dillon <squarism@gmail.com>
|
||||
cyli <cyli@twistedmatrix.com>
|
||||
Daisuke Fujita <dtanshi45@gmail.com>
|
||||
Daniel Huhn <daniel@danielhuhn.de>
|
||||
Darren Shepherd <darren@rancher.com>
|
||||
Dave Trombley <dave.trombley@gmail.com>
|
||||
Dave Tucker <dt@docker.com>
|
||||
David Lawrence <david.lawrence@docker.com>
|
||||
David Verhasselt <david@crowdway.com>
|
||||
David Xia <dxia@spotify.com>
|
||||
davidli <wenquan.li@hp.com>
|
||||
Dejan Golja <dejan@golja.org>
|
||||
Derek McGowan <derek@mcgstyle.net>
|
||||
Diogo Mónica <diogo.monica@gmail.com>
|
||||
DJ Enriquez <dj.enriquez@infospace.com>
|
||||
Donald Huang <don.hcd@gmail.com>
|
||||
Doug Davis <dug@us.ibm.com>
|
||||
Eric Yang <windfarer@gmail.com>
|
||||
Fabio Huser <fabio@fh1.ch>
|
||||
farmerworking <farmerworking@gmail.com>
|
||||
Felix Yan <felixonmars@archlinux.org>
|
||||
Florentin Raud <florentin.raud@gmail.com>
|
||||
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
||||
gabriell nascimento <gabriell@bluesoft.com.br>
|
||||
Gleb Schukin <gschukin@ptsecurity.com>
|
||||
harche <p.harshal@gmail.com>
|
||||
Henri Gomez <henri.gomez@gmail.com>
|
||||
Hu Keping <hukeping@huawei.com>
|
||||
Hua Wang <wanghua.humble@gmail.com>
|
||||
HuKeping <hukeping@huawei.com>
|
||||
Ian Babrou <ibobrik@gmail.com>
|
||||
igayoso <igayoso@gmail.com>
|
||||
Jack Griffin <jackpg14@gmail.com>
|
||||
Jason Freidman <jason.freidman@gmail.com>
|
||||
Jeff Nickoloff <jeff@allingeek.com>
|
||||
Jessie Frazelle <jessie@docker.com>
|
||||
jhaohai <jhaohai@foxmail.com>
|
||||
Jianqing Wang <tsing@jianqing.org>
|
||||
John Starks <jostarks@microsoft.com>
|
||||
Jon Johnson <jonjohnson@google.com>
|
||||
Jon Poler <jonathan.poler@apcera.com>
|
||||
Jonathan Boulle <jonathanboulle@gmail.com>
|
||||
Jordan Liggitt <jliggitt@redhat.com>
|
||||
Josh Hawn <josh.hawn@docker.com>
|
||||
Julien Fernandez <julien.fernandez@gmail.com>
|
||||
Ke Xu <leonhartx.k@gmail.com>
|
||||
Keerthan Mala <kmala@engineyard.com>
|
||||
Kelsey Hightower <kelsey.hightower@gmail.com>
|
||||
Kenneth Lim <kennethlimcp@gmail.com>
|
||||
Kenny Leung <kleung@google.com>
|
||||
Li Yi <denverdino@gmail.com>
|
||||
Liu Hua <sdu.liu@huawei.com>
|
||||
liuchang0812 <liuchang0812@gmail.com>
|
||||
Louis Kottmann <louis.kottmann@gmail.com>
|
||||
Luke Carpenter <x@rubynerd.net>
|
||||
Mary Anthony <mary@docker.com>
|
||||
Matt Bentley <mbentley@mbentley.net>
|
||||
Matt Duch <matt@learnmetrics.com>
|
||||
Matt Moore <mattmoor@google.com>
|
||||
Matt Robenolt <matt@ydekproductions.com>
|
||||
Michael Prokop <mika@grml.org>
|
||||
Michal Minar <miminar@redhat.com>
|
||||
Miquel Sabaté <msabate@suse.com>
|
||||
Morgan Bauer <mbauer@us.ibm.com>
|
||||
moxiegirl <mary@docker.com>
|
||||
Nathan Sullivan <nathan@nightsys.net>
|
||||
nevermosby <robolwq@qq.com>
|
||||
Nghia Tran <tcnghia@gmail.com>
|
||||
Nikita Tarasov <nikita@mygento.ru>
|
||||
Nuutti Kotivuori <nuutti.kotivuori@poplatek.fi>
|
||||
Oilbeater <liumengxinfly@gmail.com>
|
||||
Olivier Gambier <olivier@docker.com>
|
||||
Olivier Jacques <olivier.jacques@hp.com>
|
||||
Omer Cohen <git@omer.io>
|
||||
Patrick Devine <patrick.devine@docker.com>
|
||||
Phil Estes <estesp@linux.vnet.ibm.com>
|
||||
Philip Misiowiec <philip@atlashealth.com>
|
||||
Richard Scothern <richard.scothern@docker.com>
|
||||
Rodolfo Carvalho <rhcarvalho@gmail.com>
|
||||
Rusty Conover <rusty@luckydinosaur.com>
|
||||
Sean Boran <Boran@users.noreply.github.com>
|
||||
Sebastiaan van Stijn <github@gone.nl>
|
||||
Serge Dubrouski <sergeyfd@gmail.com>
|
||||
Sharif Nassar <sharif@mrwacky.com>
|
||||
Shawn Falkner-Horine <dreadpirateshawn@gmail.com>
|
||||
Shreyas Karnik <karnik.shreyas@gmail.com>
|
||||
Simon Thulbourn <simon+github@thulbourn.com>
|
||||
Spencer Rinehart <anubis@overthemonkey.com>
|
||||
Stefan Majewsky <stefan.majewsky@sap.com>
|
||||
Stefan Weil <sw@weilnetz.de>
|
||||
Stephen J Day <stephen.day@docker.com>
|
||||
Sungho Moon <sungho.moon@navercorp.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au>
|
||||
Sylvain Baubeau <sbaubeau@redhat.com>
|
||||
Ted Reed <ted.reed@gmail.com>
|
||||
tgic <farmer1992@gmail.com>
|
||||
Thomas Sjögren <konstruktoid@users.noreply.github.com>
|
||||
Tianon Gravi <admwiggin@gmail.com>
|
||||
Tibor Vass <teabee89@gmail.com>
|
||||
Tonis Tiigi <tonistiigi@gmail.com>
|
||||
Tony Holdstock-Brown <tony@docker.com>
|
||||
Trevor Pounds <trevor.pounds@gmail.com>
|
||||
Troels Thomsen <troels@thomsen.io>
|
||||
Vincent Batts <vbatts@redhat.com>
|
||||
Vincent Demeester <vincent@sbr.pm>
|
||||
Vincent Giersch <vincent.giersch@ovh.net>
|
||||
W. Trevor King <wking@tremily.us>
|
||||
weiyuan.yl <weiyuan.yl@alibaba-inc.com>
|
||||
xg.song <xg.song@venusource.com>
|
||||
xiekeyang <xiekeyang@huawei.com>
|
||||
Yann ROBERT <yann.robert@anantaplex.fr>
|
||||
yuzou <zouyu7@huawei.com>
|
||||
zhouhaibing089 <zhouhaibing089@gmail.com>
|
||||
姜继忠 <jizhong.jiangjz@alibaba-inc.com>
|
119
vendor/github.com/docker/distribution/BUILDING.md
generated
vendored
Normal file
119
vendor/github.com/docker/distribution/BUILDING.md
generated
vendored
Normal file
|
@ -0,0 +1,119 @@
|
|||
|
||||
# Building the registry source
|
||||
|
||||
## Use-case
|
||||
|
||||
This is useful if you intend to actively work on the registry.
|
||||
|
||||
### Alternatives
|
||||
|
||||
Most people should use the [official Registry docker image](https://hub.docker.com/r/library/registry/).
|
||||
|
||||
People looking for advanced operational use cases might consider rolling their own image with a custom Dockerfile inheriting `FROM registry:2`.
|
||||
|
||||
OS X users who want to run natively can do so following [the instructions here](osx-setup-guide.md).
|
||||
|
||||
### Gotchas
|
||||
|
||||
You are expected to know your way around with go & git.
|
||||
|
||||
If you are a casual user with no development experience, and no preliminary knowledge of go, building from source is probably not a good solution for you.
|
||||
|
||||
## Build the development environment
|
||||
|
||||
The first prerequisite of properly building distribution targets is to have a Go
|
||||
development environment setup. Please follow [How to Write Go Code](https://golang.org/doc/code.html)
|
||||
for proper setup. If done correctly, you should have a GOROOT and GOPATH set in the
|
||||
environment.
|
||||
|
||||
If a Go development environment is setup, one can use `go get` to install the
|
||||
`registry` command from the current latest:
|
||||
|
||||
go get github.com/docker/distribution/cmd/registry
|
||||
|
||||
The above will install the source repository into the `GOPATH`.
|
||||
|
||||
Now create the directory for the registry data (this might require you to set permissions properly)
|
||||
|
||||
mkdir -p /var/lib/registry
|
||||
|
||||
... or alternatively `export REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/somewhere` if you want to store data into another location.
|
||||
|
||||
The `registry`
|
||||
binary can then be run with the following:
|
||||
|
||||
$ $GOPATH/bin/registry --version
|
||||
$GOPATH/bin/registry github.com/docker/distribution v2.0.0-alpha.1+unknown
|
||||
|
||||
> __NOTE:__ While you do not need to use `go get` to checkout the distribution
|
||||
> project, for these build instructions to work, the project must be checked
|
||||
> out in the correct location in the `GOPATH`. This should almost always be
|
||||
> `$GOPATH/src/github.com/docker/distribution`.
|
||||
|
||||
The registry can be run with the default config using the following
|
||||
incantation:
|
||||
|
||||
$ $GOPATH/bin/registry serve $GOPATH/src/github.com/docker/distribution/cmd/registry/config-example.yml
|
||||
INFO[0000] endpoint local-5003 disabled, skipping app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
|
||||
INFO[0000] endpoint local-8083 disabled, skipping app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
|
||||
INFO[0000] listening on :5000 app.id=34bbec38-a91a-494a-9a3f-b72f9010081f version=v2.0.0-alpha.1+unknown
|
||||
INFO[0000] debug server listening localhost:5001
|
||||
|
||||
If it is working, one should see the above log messages.
|
||||
|
||||
### Repeatable Builds
|
||||
|
||||
For the full development experience, one should `cd` into
|
||||
`$GOPATH/src/github.com/docker/distribution`. From there, the regular `go`
|
||||
commands, such as `go test`, should work per package (please see
|
||||
[Developing](#developing) if they don't work).
|
||||
|
||||
A `Makefile` has been provided as a convenience to support repeatable builds.
|
||||
Please install the following into `GOPATH` for it to work:
|
||||
|
||||
go get github.com/tools/godep github.com/golang/lint/golint
|
||||
|
||||
**TODO(stevvooe):** Add a `make setup` command to Makefile to run this. Have to think about how to interact with Godeps properly.
|
||||
|
||||
Once these commands are available in the `GOPATH`, run `make` to get a full
|
||||
build:
|
||||
|
||||
$ make
|
||||
+ clean
|
||||
+ fmt
|
||||
+ vet
|
||||
+ lint
|
||||
+ build
|
||||
github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar
|
||||
github.com/Sirupsen/logrus
|
||||
github.com/docker/libtrust
|
||||
...
|
||||
github.com/yvasiyarov/gorelic
|
||||
github.com/docker/distribution/registry/handlers
|
||||
github.com/docker/distribution/cmd/registry
|
||||
+ test
|
||||
...
|
||||
ok github.com/docker/distribution/digest 7.875s
|
||||
ok github.com/docker/distribution/manifest 0.028s
|
||||
ok github.com/docker/distribution/notifications 17.322s
|
||||
? github.com/docker/distribution/registry [no test files]
|
||||
ok github.com/docker/distribution/registry/api/v2 0.101s
|
||||
? github.com/docker/distribution/registry/auth [no test files]
|
||||
ok github.com/docker/distribution/registry/auth/silly 0.011s
|
||||
...
|
||||
+ /Users/sday/go/src/github.com/docker/distribution/bin/registry
|
||||
+ /Users/sday/go/src/github.com/docker/distribution/bin/registry-api-descriptor-template
|
||||
+ binaries
|
||||
|
||||
The above provides a repeatable build using the contents of the vendored
|
||||
Godeps directory. This includes formatting, vetting, linting, building,
|
||||
testing and generating tagged binaries. We can verify this worked by running
|
||||
the registry binary generated in the "./bin" directory:
|
||||
|
||||
$ ./bin/registry -version
|
||||
./bin/registry github.com/docker/distribution v2.0.0-alpha.2-80-g16d8b2c.m
|
||||
|
||||
### Optional build tags
|
||||
|
||||
Optional [build tags](http://golang.org/pkg/go/build/) can be provided using
|
||||
the environment variable `DOCKER_BUILDTAGS`.
|
35
vendor/github.com/docker/distribution/CHANGELOG.md
generated
vendored
Normal file
35
vendor/github.com/docker/distribution/CHANGELOG.md
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Changelog
|
||||
|
||||
## 2.5.0 (2016-06-14)
|
||||
|
||||
### Storage
|
||||
- Ensure uploads directory is cleaned after upload is commited
|
||||
- Add ability to cap concurrent operations in filesystem driver
|
||||
- S3: Add 'us-gov-west-1' to the valid region list
|
||||
- Swift: Handle ceph not returning Last-Modified header for HEAD requests
|
||||
- Add redirect middleware
|
||||
|
||||
#### Registry
|
||||
- Add support for blobAccessController middleware
|
||||
- Add support for layers from foreign sources
|
||||
- Remove signature store
|
||||
- Add support for Let's Encrypt
|
||||
- Correct yaml key names in configuration
|
||||
|
||||
#### Client
|
||||
- Add option to get content digest from manifest get
|
||||
|
||||
#### Spec
|
||||
- Update the auth spec scope grammar to reflect the fact that hostnames are optionally supported
|
||||
- Clarify API documentation around catalog fetch behavior
|
||||
|
||||
### API
|
||||
- Support returning HTTP 429 (Too Many Requests)
|
||||
|
||||
### Documentation
|
||||
- Update auth documentation examples to show "expires in" as int
|
||||
|
||||
### Docker Image
|
||||
- Use Alpine Linux as base image
|
||||
|
||||
|
140
vendor/github.com/docker/distribution/CONTRIBUTING.md
generated
vendored
Normal file
140
vendor/github.com/docker/distribution/CONTRIBUTING.md
generated
vendored
Normal file
|
@ -0,0 +1,140 @@
|
|||
# Contributing to the registry
|
||||
|
||||
## Before reporting an issue...
|
||||
|
||||
### If your problem is with...
|
||||
|
||||
- automated builds
|
||||
- your account on the [Docker Hub](https://hub.docker.com/)
|
||||
- any other [Docker Hub](https://hub.docker.com/) issue
|
||||
|
||||
Then please do not report your issue here - you should instead report it to [https://support.docker.com](https://support.docker.com)
|
||||
|
||||
### If you...
|
||||
|
||||
- need help setting up your registry
|
||||
- can't figure out something
|
||||
- are not sure what's going on or what your problem is
|
||||
|
||||
Then please do not open an issue here yet - you should first try one of the following support forums:
|
||||
|
||||
- irc: #docker-distribution on freenode
|
||||
- mailing-list: <distribution@dockerproject.org> or https://groups.google.com/a/dockerproject.org/forum/#!forum/distribution
|
||||
|
||||
## Reporting an issue properly
|
||||
|
||||
By following these simple rules you will get better and faster feedback on your issue.
|
||||
|
||||
- search the bugtracker for an already reported issue
|
||||
|
||||
### If you found an issue that describes your problem:
|
||||
|
||||
- please read other user comments first, and confirm this is the same issue: a given error condition might be indicative of different problems - you may also find a workaround in the comments
|
||||
- please refrain from adding "same thing here" or "+1" comments
|
||||
- you don't need to comment on an issue to get notified of updates: just hit the "subscribe" button
|
||||
- comment if you have some new, technical and relevant information to add to the case
|
||||
- __DO NOT__ comment on closed issues or merged PRs. If you think you have a related problem, open up a new issue and reference the PR or issue.
|
||||
|
||||
### If you have not found an existing issue that describes your problem:
|
||||
|
||||
1. create a new issue, with a succinct title that describes your issue:
|
||||
- bad title: "It doesn't work with my docker"
|
||||
- good title: "Private registry push fail: 400 error with E_INVALID_DIGEST"
|
||||
2. copy the output of:
|
||||
- `docker version`
|
||||
- `docker info`
|
||||
- `docker exec <registry-container> registry -version`
|
||||
3. copy the command line you used to launch your Registry
|
||||
4. restart your docker daemon in debug mode (add `-D` to the daemon launch arguments)
|
||||
5. reproduce your problem and get your docker daemon logs showing the error
|
||||
6. if relevant, copy your registry logs that show the error
|
||||
7. provide any relevant detail about your specific Registry configuration (e.g., storage backend used)
|
||||
8. indicate if you are using an enterprise proxy, Nginx, or anything else between you and your Registry
|
||||
|
||||
## Contributing a patch for a known bug, or a small correction
|
||||
|
||||
You should follow the basic GitHub workflow:
|
||||
|
||||
1. fork
|
||||
2. commit a change
|
||||
3. make sure the tests pass
|
||||
4. PR
|
||||
|
||||
Additionally, you must [sign your commits](https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work). It's very simple:
|
||||
|
||||
- configure your name with git: `git config user.name "Real Name" && git config user.email mail@example.com`
|
||||
- sign your commits using `-s`: `git commit -s -m "My commit"`
|
||||
|
||||
Some simple rules to ensure quick merge:
|
||||
|
||||
- clearly point to the issue(s) you want to fix in your PR comment (e.g., `closes #12345`)
|
||||
- prefer multiple (smaller) PRs addressing individual issues over a big one trying to address multiple issues at once
|
||||
- if you need to amend your PR following comments, please squash instead of adding more commits
|
||||
|
||||
## Contributing new features
|
||||
|
||||
You are heavily encouraged to first discuss what you want to do. You can do so on the irc channel, or by opening an issue that clearly describes the use case you want to fulfill, or the problem you are trying to solve.
|
||||
|
||||
If this is a major new feature, you should then submit a proposal that describes your technical solution and reasoning.
|
||||
If you did discuss it first, this will likely be greenlighted very fast. It's advisable to address all feedback on this proposal before starting actual work.
|
||||
|
||||
Then you should submit your implementation, clearly linking to the issue (and possible proposal).
|
||||
|
||||
Your PR will be reviewed by the community, then ultimately by the project maintainers, before being merged.
|
||||
|
||||
It's mandatory to:
|
||||
|
||||
- interact respectfully with other community members and maintainers - more generally, you are expected to abide by the [Docker community rules](https://github.com/docker/docker/blob/master/CONTRIBUTING.md#docker-community-guidelines)
|
||||
- address maintainers' comments and modify your submission accordingly
|
||||
- write tests for any new code
|
||||
|
||||
Complying to these simple rules will greatly accelerate the review process, and will ensure you have a pleasant experience in contributing code to the Registry.
|
||||
|
||||
Have a look at a great, successful contribution: the [Swift driver PR](https://github.com/docker/distribution/pull/493)
|
||||
|
||||
## Coding Style
|
||||
|
||||
Unless explicitly stated, we follow all coding guidelines from the Go
|
||||
community. While some of these standards may seem arbitrary, they somehow seem
|
||||
to result in a solid, consistent codebase.
|
||||
|
||||
It is possible that the code base does not currently comply with these
|
||||
guidelines. We are not looking for a massive PR that fixes this, since that
|
||||
goes against the spirit of the guidelines. All new contributions should make a
|
||||
best effort to clean up and make the code base better than they left it.
|
||||
Obviously, apply your best judgement. Remember, the goal here is to make the
|
||||
code base easier for humans to navigate and understand. Always keep that in
|
||||
mind when nudging others to comply.
|
||||
|
||||
The rules:
|
||||
|
||||
1. All code should be formatted with `gofmt -s`.
|
||||
2. All code should pass the default levels of
|
||||
[`golint`](https://github.com/golang/lint).
|
||||
3. All code should follow the guidelines covered in [Effective
|
||||
Go](http://golang.org/doc/effective_go.html) and [Go Code Review
|
||||
Comments](https://github.com/golang/go/wiki/CodeReviewComments).
|
||||
4. Comment the code. Tell us the why, the history and the context.
|
||||
5. Document _all_ declarations and methods, even private ones. Declare
|
||||
expectations, caveats and anything else that may be important. If a type
|
||||
gets exported, having the comments already there will ensure it's ready.
|
||||
6. Variable name length should be proportional to its context and no longer.
|
||||
`noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
|
||||
In practice, short methods will have short variable names and globals will
|
||||
have longer names.
|
||||
7. No underscores in package names. If you need a compound name, step back,
|
||||
and re-examine why you need a compound name. If you still think you need a
|
||||
compound name, lose the underscore.
|
||||
8. No utils or helpers packages. If a function is not general enough to
|
||||
warrant its own package, it has not been written generally enough to be a
|
||||
part of a util package. Just leave it unexported and well-documented.
|
||||
9. All tests should run with `go test` and outside tooling should not be
|
||||
required. No, we don't need another unit testing framework. Assertion
|
||||
packages are acceptable if they provide _real_ incremental value.
|
||||
10. Even though we call these "rules" above, they are actually just
|
||||
guidelines. Since you've read all the rules, you now know that.
|
||||
|
||||
If you are having trouble getting into the mood of idiomatic Go, we recommend
|
||||
reading through [Effective Go](http://golang.org/doc/effective_go.html). The
|
||||
[Go Blog](http://blog.golang.org/) is also a great resource. Drinking the
|
||||
kool-aid is a lot easier than going thirsty.
|
18
vendor/github.com/docker/distribution/Dockerfile
generated
vendored
Normal file
18
vendor/github.com/docker/distribution/Dockerfile
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
FROM golang:1.6-alpine
|
||||
|
||||
ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
|
||||
ENV DOCKER_BUILDTAGS include_oss include_gcs
|
||||
|
||||
WORKDIR $DISTRIBUTION_DIR
|
||||
COPY . $DISTRIBUTION_DIR
|
||||
COPY cmd/registry/config-dev.yml /etc/docker/registry/config.yml
|
||||
|
||||
RUN set -ex \
|
||||
&& apk add --no-cache make git
|
||||
|
||||
RUN make PREFIX=/go clean binaries
|
||||
|
||||
VOLUME ["/var/lib/registry"]
|
||||
EXPOSE 5000
|
||||
ENTRYPOINT ["registry"]
|
||||
CMD ["serve", "/etc/docker/registry/config.yml"]
|
202
vendor/github.com/docker/distribution/LICENSE
generated
vendored
Normal file
202
vendor/github.com/docker/distribution/LICENSE
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.
|
||||
|
58
vendor/github.com/docker/distribution/MAINTAINERS
generated
vendored
Normal file
58
vendor/github.com/docker/distribution/MAINTAINERS
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
# Distribution maintainers file
|
||||
#
|
||||
# This file describes who runs the docker/distribution project and how.
|
||||
# This is a living document - if you see something out of date or missing, speak up!
|
||||
#
|
||||
# It is structured to be consumable by both humans and programs.
|
||||
# To extract its contents programmatically, use any TOML-compliant parser.
|
||||
#
|
||||
# This file is compiled into the MAINTAINERS file in docker/opensource.
|
||||
#
|
||||
[Org]
|
||||
[Org."Core maintainers"]
|
||||
people = [
|
||||
"aaronlehmann",
|
||||
"dmcgowan",
|
||||
"dmp42",
|
||||
"richardscothern",
|
||||
"shykes",
|
||||
"stevvooe",
|
||||
]
|
||||
|
||||
[people]
|
||||
|
||||
# A reference list of all people associated with the project.
|
||||
# All other sections should refer to people by their canonical key
|
||||
# in the people section.
|
||||
|
||||
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
|
||||
|
||||
[people.aaronlehmann]
|
||||
Name = "Aaron Lehmann"
|
||||
Email = "aaron.lehmann@docker.com"
|
||||
GitHub = "aaronlehmann"
|
||||
|
||||
[people.dmcgowan]
|
||||
Name = "Derek McGowan"
|
||||
Email = "derek@mcgstyle.net"
|
||||
GitHub = "dmcgowan"
|
||||
|
||||
[people.dmp42]
|
||||
Name = "Olivier Gambier"
|
||||
Email = "olivier@docker.com"
|
||||
GitHub = "dmp42"
|
||||
|
||||
[people.richardscothern]
|
||||
Name = "Richard Scothern"
|
||||
Email = "richard.scothern@gmail.com"
|
||||
GitHub = "richardscothern"
|
||||
|
||||
[people.shykes]
|
||||
Name = "Solomon Hykes"
|
||||
Email = "solomon@docker.com"
|
||||
GitHub = "shykes"
|
||||
|
||||
[people.stevvooe]
|
||||
Name = "Stephen Day"
|
||||
Email = "stephen.day@docker.com"
|
||||
GitHub = "stevvooe"
|
106
vendor/github.com/docker/distribution/Makefile
generated
vendored
Normal file
106
vendor/github.com/docker/distribution/Makefile
generated
vendored
Normal file
|
@ -0,0 +1,106 @@
|
|||
# Set an output prefix, which is the local directory if not specified
|
||||
PREFIX?=$(shell pwd)
|
||||
|
||||
|
||||
# Used to populate version variable in main package.
|
||||
VERSION=$(shell git describe --match 'v[0-9]*' --dirty='.m' --always)
|
||||
|
||||
# Allow turning off function inlining and variable registerization
|
||||
ifeq (${DISABLE_OPTIMIZATION},true)
|
||||
GO_GCFLAGS=-gcflags "-N -l"
|
||||
VERSION:="$(VERSION)-noopt"
|
||||
endif
|
||||
|
||||
GO_LDFLAGS=-ldflags "-X `go list ./version`.Version=$(VERSION)"
|
||||
|
||||
.PHONY: clean all fmt vet lint build test binaries
|
||||
.DEFAULT: all
|
||||
all: fmt vet lint build test binaries
|
||||
|
||||
AUTHORS: .mailmap .git/HEAD
|
||||
git log --format='%aN <%aE>' | sort -fu > $@
|
||||
|
||||
# This only needs to be generated by hand when cutting full releases.
|
||||
version/version.go:
|
||||
./version/version.sh > $@
|
||||
|
||||
# Required for go 1.5 to build
|
||||
GO15VENDOREXPERIMENT := 1
|
||||
|
||||
# Package list
|
||||
PKGS := $(shell go list -tags "${DOCKER_BUILDTAGS}" ./... | grep -v ^github.com/docker/distribution/vendor/)
|
||||
|
||||
# Resolving binary dependencies for specific targets
|
||||
GOLINT := $(shell which golint || echo '')
|
||||
GODEP := $(shell which godep || echo '')
|
||||
|
||||
${PREFIX}/bin/registry: $(wildcard **/*.go)
|
||||
@echo "+ $@"
|
||||
@go build -tags "${DOCKER_BUILDTAGS}" -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./cmd/registry
|
||||
|
||||
${PREFIX}/bin/digest: $(wildcard **/*.go)
|
||||
@echo "+ $@"
|
||||
@go build -tags "${DOCKER_BUILDTAGS}" -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./cmd/digest
|
||||
|
||||
${PREFIX}/bin/registry-api-descriptor-template: $(wildcard **/*.go)
|
||||
@echo "+ $@"
|
||||
@go build -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./cmd/registry-api-descriptor-template
|
||||
|
||||
docs/spec/api.md: docs/spec/api.md.tmpl ${PREFIX}/bin/registry-api-descriptor-template
|
||||
./bin/registry-api-descriptor-template $< > $@
|
||||
|
||||
vet:
|
||||
@echo "+ $@"
|
||||
@go vet -tags "${DOCKER_BUILDTAGS}" $(PKGS)
|
||||
|
||||
fmt:
|
||||
@echo "+ $@"
|
||||
@test -z "$$(gofmt -s -l . 2>&1 | grep -v ^vendor/ | tee /dev/stderr)" || \
|
||||
(echo >&2 "+ please format Go code with 'gofmt -s'" && false)
|
||||
|
||||
lint:
|
||||
@echo "+ $@"
|
||||
$(if $(GOLINT), , \
|
||||
$(error Please install golint: `go get -u github.com/golang/lint/golint`))
|
||||
@test -z "$$($(GOLINT) ./... 2>&1 | grep -v ^vendor/ | tee /dev/stderr)"
|
||||
|
||||
build:
|
||||
@echo "+ $@"
|
||||
@go build -tags "${DOCKER_BUILDTAGS}" -v ${GO_LDFLAGS} $(PKGS)
|
||||
|
||||
test:
|
||||
@echo "+ $@"
|
||||
@go test -test.short -tags "${DOCKER_BUILDTAGS}" $(PKGS)
|
||||
|
||||
test-full:
|
||||
@echo "+ $@"
|
||||
@go test -tags "${DOCKER_BUILDTAGS}" $(PKGS)
|
||||
|
||||
binaries: ${PREFIX}/bin/registry ${PREFIX}/bin/digest ${PREFIX}/bin/registry-api-descriptor-template
|
||||
@echo "+ $@"
|
||||
|
||||
clean:
|
||||
@echo "+ $@"
|
||||
@rm -rf "${PREFIX}/bin/registry" "${PREFIX}/bin/digest" "${PREFIX}/bin/registry-api-descriptor-template"
|
||||
|
||||
dep-save:
|
||||
@echo "+ $@"
|
||||
$(if $(GODEP), , \
|
||||
$(error Please install godep: go get github.com/tools/godep))
|
||||
@$(GODEP) save $(PKGS)
|
||||
|
||||
dep-restore:
|
||||
@echo "+ $@"
|
||||
$(if $(GODEP), , \
|
||||
$(error Please install godep: go get github.com/tools/godep))
|
||||
@$(GODEP) restore -v
|
||||
|
||||
dep-validate: dep-restore
|
||||
@echo "+ $@"
|
||||
@rm -Rf .vendor.bak
|
||||
@mv vendor .vendor.bak
|
||||
@rm -Rf Godeps
|
||||
@$(GODEP) save ./...
|
||||
@test -z "$$(diff -r vendor .vendor.bak 2>&1 | tee /dev/stderr)" || \
|
||||
(echo >&2 "+ borked dependencies! what you have in Godeps/Godeps.json does not match with what you have in vendor" && false)
|
||||
@rm -Rf .vendor.bak
|
131
vendor/github.com/docker/distribution/README.md
generated
vendored
Normal file
131
vendor/github.com/docker/distribution/README.md
generated
vendored
Normal file
|
@ -0,0 +1,131 @@
|
|||
# Distribution
|
||||
|
||||
The Docker toolset to pack, ship, store, and deliver content.
|
||||
|
||||
This repository's main product is the Docker Registry 2.0 implementation
|
||||
for storing and distributing Docker images. It supersedes the
|
||||
[docker/docker-registry](https://github.com/docker/docker-registry)
|
||||
project with a new API design, focused around security and performance.
|
||||
|
||||
<img src="https://www.docker.com/sites/default/files/oyster-registry-3.png" width=200px/>
|
||||
|
||||
[](https://circleci.com/gh/docker/distribution/tree/master)
|
||||
[](https://godoc.org/github.com/docker/distribution)
|
||||
|
||||
This repository contains the following components:
|
||||
|
||||
|**Component** |Description |
|
||||
|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **registry** | An implementation of the [Docker Registry HTTP API V2](docs/spec/api.md) for use with docker 1.6+. |
|
||||
| **libraries** | A rich set of libraries for interacting with distribution components. Please see [godoc](https://godoc.org/github.com/docker/distribution) for details. **Note**: These libraries are **unstable**. |
|
||||
| **specifications** | _Distribution_ related specifications are available in [docs/spec](docs/spec) |
|
||||
| **documentation** | Docker's full documentation set is available at [docs.docker.com](https://docs.docker.com). This repository [contains the subset](docs/index.md) related just to the registry. |
|
||||
|
||||
### How does this integrate with Docker engine?
|
||||
|
||||
This project should provide an implementation to a V2 API for use in the [Docker
|
||||
core project](https://github.com/docker/docker). The API should be embeddable
|
||||
and simplify the process of securely pulling and pushing content from `docker`
|
||||
daemons.
|
||||
|
||||
### What are the long term goals of the Distribution project?
|
||||
|
||||
The _Distribution_ project has the further long term goal of providing a
|
||||
secure tool chain for distributing content. The specifications, APIs and tools
|
||||
should be as useful with Docker as they are without.
|
||||
|
||||
Our goal is to design a professional grade and extensible content distribution
|
||||
system that allow users to:
|
||||
|
||||
* Enjoy an efficient, secured and reliable way to store, manage, package and
|
||||
exchange content
|
||||
* Hack/roll their own on top of healthy open-source components
|
||||
* Implement their own home made solution through good specs, and solid
|
||||
extensions mechanism.
|
||||
|
||||
## More about Registry 2.0
|
||||
|
||||
The new registry implementation provides the following benefits:
|
||||
|
||||
- faster push and pull
|
||||
- new, more efficient implementation
|
||||
- simplified deployment
|
||||
- pluggable storage backend
|
||||
- webhook notifications
|
||||
|
||||
For information on upcoming functionality, please see [ROADMAP.md](ROADMAP.md).
|
||||
|
||||
### Who needs to deploy a registry?
|
||||
|
||||
By default, Docker users pull images from Docker's public registry instance.
|
||||
[Installing Docker](https://docs.docker.com/engine/installation/) gives users this
|
||||
ability. Users can also push images to a repository on Docker's public registry,
|
||||
if they have a [Docker Hub](https://hub.docker.com/) account.
|
||||
|
||||
For some users and even companies, this default behavior is sufficient. For
|
||||
others, it is not.
|
||||
|
||||
For example, users with their own software products may want to maintain a
|
||||
registry for private, company images. Also, you may wish to deploy your own
|
||||
image repository for images used to test or in continuous integration. For these
|
||||
use cases and others, [deploying your own registry instance](docs/deploying.md)
|
||||
may be the better choice.
|
||||
|
||||
### Migration to Registry 2.0
|
||||
|
||||
For those who have previously deployed their own registry based on the Registry
|
||||
1.0 implementation and wish to deploy a Registry 2.0 while retaining images,
|
||||
data migration is required. A tool to assist with migration efforts has been
|
||||
created. For more information see [docker/migrator]
|
||||
(https://github.com/docker/migrator).
|
||||
|
||||
## Contribute
|
||||
|
||||
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute
|
||||
issues, fixes, and patches to this project. If you are contributing code, see
|
||||
the instructions for [building a development environment](docs/recipes/building.md).
|
||||
|
||||
## Support
|
||||
|
||||
If any issues are encountered while using the _Distribution_ project, several
|
||||
avenues are available for support:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th align="left">
|
||||
IRC
|
||||
</th>
|
||||
<td>
|
||||
#docker-distribution on FreeNode
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th align="left">
|
||||
Issue Tracker
|
||||
</th>
|
||||
<td>
|
||||
github.com/docker/distribution/issues
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th align="left">
|
||||
Google Groups
|
||||
</th>
|
||||
<td>
|
||||
https://groups.google.com/a/dockerproject.org/forum/#!forum/distribution
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th align="left">
|
||||
Mailing List
|
||||
</th>
|
||||
<td>
|
||||
docker@dockerproject.org
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This project is distributed under [Apache License, Version 2.0](LICENSE).
|
267
vendor/github.com/docker/distribution/ROADMAP.md
generated
vendored
Normal file
267
vendor/github.com/docker/distribution/ROADMAP.md
generated
vendored
Normal file
|
@ -0,0 +1,267 @@
|
|||
# Roadmap
|
||||
|
||||
The Distribution Project consists of several components, some of which are
|
||||
still being defined. This document defines the high-level goals of the
|
||||
project, identifies the current components, and defines the release-
|
||||
relationship to the Docker Platform.
|
||||
|
||||
* [Distribution Goals](#distribution-goals)
|
||||
* [Distribution Components](#distribution-components)
|
||||
* [Project Planning](#project-planning): release-relationship to the Docker Platform.
|
||||
|
||||
This road map is a living document, providing an overview of the goals and
|
||||
considerations made in respect of the future of the project.
|
||||
|
||||
## Distribution Goals
|
||||
|
||||
- Replace the existing [docker registry](github.com/docker/docker-registry)
|
||||
implementation as the primary implementation.
|
||||
- Replace the existing push and pull code in the docker engine with the
|
||||
distribution package.
|
||||
- Define a strong data model for distributing docker images
|
||||
- Provide a flexible distribution tool kit for use in the docker platform
|
||||
- Unlock new distribution models
|
||||
|
||||
## Distribution Components
|
||||
|
||||
Components of the Distribution Project are managed via github [milestones](https://github.com/docker/distribution/milestones). Upcoming
|
||||
features and bugfixes for a component will be added to the relevant milestone. If a feature or
|
||||
bugfix is not part of a milestone, it is currently unscheduled for
|
||||
implementation.
|
||||
|
||||
* [Registry](#registry)
|
||||
* [Distribution Package](#distribution-package)
|
||||
|
||||
***
|
||||
|
||||
### Registry
|
||||
|
||||
The new Docker registry is the main portion of the distribution repository.
|
||||
Registry 2.0 is the first release of the next-generation registry. This was
|
||||
primarily focused on implementing the [new registry
|
||||
API](https://github.com/docker/distribution/blob/master/docs/spec/api.md),
|
||||
with a focus on security and performance.
|
||||
|
||||
Following from the Distribution project goals above, we have a set of goals
|
||||
for registry v2 that we would like to follow in the design. New features
|
||||
should be compared against these goals.
|
||||
|
||||
#### Data Storage and Distribution First
|
||||
|
||||
The registry's first goal is to provide a reliable, consistent storage
|
||||
location for Docker images. The registry should only provide the minimal
|
||||
amount of indexing required to fetch image data and no more.
|
||||
|
||||
This means we should be selective in new features and API additions, including
|
||||
those that may require expensive, ever growing indexes. Requests should be
|
||||
servable in "constant time".
|
||||
|
||||
#### Content Addressability
|
||||
|
||||
All data objects used in the registry API should be content addressable.
|
||||
Content identifiers should be secure and verifiable. This provides a secure,
|
||||
reliable base from which to build more advanced content distribution systems.
|
||||
|
||||
#### Content Agnostic
|
||||
|
||||
In the past, changes to the image format would require large changes in Docker
|
||||
and the Registry. By decoupling the distribution and image format, we can
|
||||
allow the formats to progress without having to coordinate between the two.
|
||||
This means that we should be focused on decoupling Docker from the registry
|
||||
just as much as decoupling the registry from Docker. Such an approach will
|
||||
allow us to unlock new distribution models that haven't been possible before.
|
||||
|
||||
We can take this further by saying that the new registry should be content
|
||||
agnostic. The registry provides a model of names, tags, manifests and content
|
||||
addresses and that model can be used to work with content.
|
||||
|
||||
#### Simplicity
|
||||
|
||||
The new registry should be closer to a microservice component than its
|
||||
predecessor. This means it should have a narrower API and a low number of
|
||||
service dependencies. It should be easy to deploy.
|
||||
|
||||
This means that other solutions should be explored before changing the API or
|
||||
adding extra dependencies. If functionality is required, can it be added as an
|
||||
extension or companion service.
|
||||
|
||||
#### Extensibility
|
||||
|
||||
The registry should provide extension points to add functionality. By keeping
|
||||
the scope narrow, but providing the ability to add functionality.
|
||||
|
||||
Features like search, indexing, synchronization and registry explorers fall
|
||||
into this category. No such feature should be added unless we've found it
|
||||
impossible to do through an extension.
|
||||
|
||||
#### Active Feature Discussions
|
||||
|
||||
The following are feature discussions that are currently active.
|
||||
|
||||
If you don't see your favorite, unimplemented feature, feel free to contact us
|
||||
via IRC or the mailing list and we can talk about adding it. The goal here is
|
||||
to make sure that new features go through a rigid design process before
|
||||
landing in the registry.
|
||||
|
||||
##### Proxying to other Registries
|
||||
|
||||
A _pull-through caching_ mode exists for the registry, but is restricted from
|
||||
within the docker client to only mirror the official Docker Hub. This functionality
|
||||
can be expanded when image provenance has been specified and implemented in the
|
||||
distribution project.
|
||||
|
||||
##### Metadata storage
|
||||
|
||||
Metadata for the registry is currently stored with the manifest and layer data on
|
||||
the storage backend. While this is a big win for simplicity and reliably maintaining
|
||||
state, it comes with the cost of consistency and high latency. The mutable registry
|
||||
metadata operations should be abstracted behind an API which will allow ACID compliant
|
||||
storage systems to handle metadata.
|
||||
|
||||
##### Peer to Peer transfer
|
||||
|
||||
Discussion has started here: https://docs.google.com/document/d/1rYDpSpJiQWmCQy8Cuiaa3NH-Co33oK_SC9HeXYo87QA/edit
|
||||
|
||||
##### Indexing, Search and Discovery
|
||||
|
||||
The original registry provided some implementation of search for use with
|
||||
private registries. Support has been elided from V2 since we'd like to both
|
||||
decouple search functionality from the registry. The makes the registry
|
||||
simpler to deploy, especially in use cases where search is not needed, and
|
||||
let's us decouple the image format from the registry.
|
||||
|
||||
There are explorations into using the catalog API and notification system to
|
||||
build external indexes. The current line of thought is that we will define a
|
||||
common search API to index and query docker images. Such a system could be run
|
||||
as a companion to a registry or set of registries to power discovery.
|
||||
|
||||
The main issue with search and discovery is that there are so many ways to
|
||||
accomplish it. There are two aspects to this project. The first is deciding on
|
||||
how it will be done, including an API definition that can work with changing
|
||||
data formats. The second is the process of integrating with `docker search`.
|
||||
We expect that someone attempts to address the problem with the existing tools
|
||||
and propose it as a standard search API or uses it to inform a standardization
|
||||
process. Once this has been explored, we integrate with the docker client.
|
||||
|
||||
Please see the following for more detail:
|
||||
|
||||
- https://github.com/docker/distribution/issues/206
|
||||
|
||||
##### Deletes
|
||||
|
||||
> __NOTE:__ Deletes are a much asked for feature. Before requesting this
|
||||
feature or participating in discussion, we ask that you read this section in
|
||||
full and understand the problems behind deletes.
|
||||
|
||||
While, at first glance, implementing deleting seems simple, there are a number
|
||||
mitigating factors that make many solutions not ideal or even pathological in
|
||||
the context of a registry. The following paragraph discuss the background and
|
||||
approaches that could be applied to arrive at a solution.
|
||||
|
||||
The goal of deletes in any system is to remove unused or unneeded data. Only
|
||||
data requested for deletion should be removed and no other data. Removing
|
||||
unintended data is worse than _not_ removing data that was requested for
|
||||
removal but ideally, both are supported. Generally, according to this rule, we
|
||||
err on holding data longer than needed, ensuring that it is only removed when
|
||||
we can be certain that it can be removed. With the current behavior, we opt to
|
||||
hold onto the data forever, ensuring that data cannot be incorrectly removed.
|
||||
|
||||
To understand the problems with implementing deletes, one must understand the
|
||||
data model. All registry data is stored in a filesystem layout, implemented on
|
||||
a "storage driver", effectively a _virtual file system_ (VFS). The storage
|
||||
system must assume that this VFS layer will be eventually consistent and has
|
||||
poor read- after-write consistency, since this is the lower common denominator
|
||||
among the storage drivers. This is mitigated by writing values in reverse-
|
||||
dependent order, but makes wider transactional operations unsafe.
|
||||
|
||||
Layered on the VFS model is a content-addressable _directed, acyclic graph_
|
||||
(DAG) made up of blobs. Manifests reference layers. Tags reference manifests.
|
||||
Since the same data can be referenced by multiple manifests, we only store
|
||||
data once, even if it is in different repositories. Thus, we have a set of
|
||||
blobs, referenced by tags and manifests. If we want to delete a blob we need
|
||||
to be certain that it is no longer referenced by another manifest or tag. When
|
||||
we delete a manifest, we also can try to delete the referenced blobs. Deciding
|
||||
whether or not a blob has an active reference is the crux of the problem.
|
||||
|
||||
Conceptually, deleting a manifest and its resources is quite simple. Just find
|
||||
all the manifests, enumerate the referenced blobs and delete the blobs not in
|
||||
that set. An astute observer will recognize this as a garbage collection
|
||||
problem. As with garbage collection in programming languages, this is very
|
||||
simple when one always has a consistent view. When one adds parallelism and an
|
||||
inconsistent view of data, it becomes very challenging.
|
||||
|
||||
A simple example can demonstrate this. Let's say we are deleting a manifest
|
||||
_A_ in one process. We scan the manifest and decide that all the blobs are
|
||||
ready for deletion. Concurrently, we have another process accepting a new
|
||||
manifest _B_ referencing one or more blobs from the manifest _A_. Manifest _B_
|
||||
is accepted and all the blobs are considered present, so the operation
|
||||
proceeds. The original process then deletes the referenced blobs, assuming
|
||||
they were unreferenced. The manifest _B_, which we thought had all of its data
|
||||
present, can no longer be served by the registry, since the dependent data has
|
||||
been deleted.
|
||||
|
||||
Deleting data from the registry safely requires some way to coordinate this
|
||||
operation. The following approaches are being considered:
|
||||
|
||||
- _Reference Counting_ - Maintain a count of references to each blob. This is
|
||||
challenging for a number of reasons: 1. maintaining a consistent consensus
|
||||
of reference counts across a set of Registries and 2. Building the initial
|
||||
list of reference counts for an existing registry. These challenges can be
|
||||
met with a consensus protocol like Paxos or Raft in the first case and a
|
||||
necessary but simple scan in the second..
|
||||
- _Lock the World GC_ - Halt all writes to the data store. Walk the data store
|
||||
and find all blob references. Delete all unreferenced blobs. This approach
|
||||
is very simple but requires disabling writes for a period of time while the
|
||||
service reads all data. This is slow and expensive but very accurate and
|
||||
effective.
|
||||
- _Generational GC_ - Do something similar to above but instead of blocking
|
||||
writes, writes are sent to another storage backend while reads are broadcast
|
||||
to the new and old backends. GC is then performed on the read-only portion.
|
||||
Because writes land in the new backend, the data in the read-only section
|
||||
can be safely deleted. The main drawbacks of this approach are complexity
|
||||
and coordination.
|
||||
- _Centralized Oracle_ - Using a centralized, transactional database, we can
|
||||
know exactly which data is referenced at any given time. This avoids
|
||||
coordination problem by managing this data in a single location. We trade
|
||||
off metadata scalability for simplicity and performance. This is a very good
|
||||
option for most registry deployments. This would create a bottleneck for
|
||||
registry metadata. However, metadata is generally not the main bottleneck
|
||||
when serving images.
|
||||
|
||||
Please let us know if other solutions exist that we have yet to enumerate.
|
||||
Note that for any approach, implementation is a massive consideration. For
|
||||
example, a mark-sweep based solution may seem simple but the amount of work in
|
||||
coordination offset the extra work it might take to build a _Centralized
|
||||
Oracle_. We'll accept proposals for any solution but please coordinate with us
|
||||
before dropping code.
|
||||
|
||||
At this time, we have traded off simplicity and ease of deployment for disk
|
||||
space. Simplicity and ease of deployment tend to reduce developer involvement,
|
||||
which is currently the most expensive resource in software engineering. Taking
|
||||
on any solution for deletes will greatly effect these factors, trading off
|
||||
very cheap disk space for a complex deployment and operational story.
|
||||
|
||||
Please see the following issues for more detail:
|
||||
|
||||
- https://github.com/docker/distribution/issues/422
|
||||
- https://github.com/docker/distribution/issues/461
|
||||
- https://github.com/docker/distribution/issues/462
|
||||
|
||||
### Distribution Package
|
||||
|
||||
At its core, the Distribution Project is a set of Go packages that make up
|
||||
Distribution Components. At this time, most of these packages make up the
|
||||
Registry implementation.
|
||||
|
||||
The package itself is considered unstable. If you're using it, please take care to vendor the dependent version.
|
||||
|
||||
For feature additions, please see the Registry section. In the future, we may break out a
|
||||
separate Roadmap for distribution-specific features that apply to more than
|
||||
just the registry.
|
||||
|
||||
***
|
||||
|
||||
### Project Planning
|
||||
|
||||
An [Open-Source Planning Process](https://github.com/docker/distribution/wiki/Open-Source-Planning-Process) is used to define the Roadmap. [Project Pages](https://github.com/docker/distribution/wiki) define the goals for each Milestone and identify current progress.
|
||||
|
245
vendor/github.com/docker/distribution/blobs.go
generated
vendored
Normal file
245
vendor/github.com/docker/distribution/blobs.go
generated
vendored
Normal file
|
@ -0,0 +1,245 @@
|
|||
package distribution
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/distribution/reference"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrBlobExists returned when blob already exists
|
||||
ErrBlobExists = errors.New("blob exists")
|
||||
|
||||
// ErrBlobDigestUnsupported when blob digest is an unsupported version.
|
||||
ErrBlobDigestUnsupported = errors.New("unsupported blob digest")
|
||||
|
||||
// ErrBlobUnknown when blob is not found.
|
||||
ErrBlobUnknown = errors.New("unknown blob")
|
||||
|
||||
// ErrBlobUploadUnknown returned when upload is not found.
|
||||
ErrBlobUploadUnknown = errors.New("blob upload unknown")
|
||||
|
||||
// ErrBlobInvalidLength returned when the blob has an expected length on
|
||||
// commit, meaning mismatched with the descriptor or an invalid value.
|
||||
ErrBlobInvalidLength = errors.New("blob invalid length")
|
||||
)
|
||||
|
||||
// ErrBlobInvalidDigest returned when digest check fails.
|
||||
type ErrBlobInvalidDigest struct {
|
||||
Digest digest.Digest
|
||||
Reason error
|
||||
}
|
||||
|
||||
func (err ErrBlobInvalidDigest) Error() string {
|
||||
return fmt.Sprintf("invalid digest for referenced layer: %v, %v",
|
||||
err.Digest, err.Reason)
|
||||
}
|
||||
|
||||
// ErrBlobMounted returned when a blob is mounted from another repository
|
||||
// instead of initiating an upload session.
|
||||
type ErrBlobMounted struct {
|
||||
From reference.Canonical
|
||||
Descriptor Descriptor
|
||||
}
|
||||
|
||||
func (err ErrBlobMounted) Error() string {
|
||||
return fmt.Sprintf("blob mounted from: %v to: %v",
|
||||
err.From, err.Descriptor)
|
||||
}
|
||||
|
||||
// Descriptor describes targeted content. Used in conjunction with a blob
|
||||
// store, a descriptor can be used to fetch, store and target any kind of
|
||||
// blob. The struct also describes the wire protocol format. Fields should
|
||||
// only be added but never changed.
|
||||
type Descriptor struct {
|
||||
// MediaType describe the type of the content. All text based formats are
|
||||
// encoded as utf-8.
|
||||
MediaType string `json:"mediaType,omitempty"`
|
||||
|
||||
// Size in bytes of content.
|
||||
Size int64 `json:"size,omitempty"`
|
||||
|
||||
// Digest uniquely identifies the content. A byte stream can be verified
|
||||
// against against this digest.
|
||||
Digest digest.Digest `json:"digest,omitempty"`
|
||||
|
||||
// URLs contains the source URLs of this content.
|
||||
URLs []string `json:"urls,omitempty"`
|
||||
|
||||
// NOTE: Before adding a field here, please ensure that all
|
||||
// other options have been exhausted. Much of the type relationships
|
||||
// depend on the simplicity of this type.
|
||||
}
|
||||
|
||||
// Descriptor returns the descriptor, to make it satisfy the Describable
|
||||
// interface. Note that implementations of Describable are generally objects
|
||||
// which can be described, not simply descriptors; this exception is in place
|
||||
// to make it more convenient to pass actual descriptors to functions that
|
||||
// expect Describable objects.
|
||||
func (d Descriptor) Descriptor() Descriptor {
|
||||
return d
|
||||
}
|
||||
|
||||
// BlobStatter makes blob descriptors available by digest. The service may
|
||||
// provide a descriptor of a different digest if the provided digest is not
|
||||
// canonical.
|
||||
type BlobStatter interface {
|
||||
// Stat provides metadata about a blob identified by the digest. If the
|
||||
// blob is unknown to the describer, ErrBlobUnknown will be returned.
|
||||
Stat(ctx context.Context, dgst digest.Digest) (Descriptor, error)
|
||||
}
|
||||
|
||||
// BlobDeleter enables deleting blobs from storage.
|
||||
type BlobDeleter interface {
|
||||
Delete(ctx context.Context, dgst digest.Digest) error
|
||||
}
|
||||
|
||||
// BlobEnumerator enables iterating over blobs from storage
|
||||
type BlobEnumerator interface {
|
||||
Enumerate(ctx context.Context, ingester func(dgst digest.Digest) error) error
|
||||
}
|
||||
|
||||
// BlobDescriptorService manages metadata about a blob by digest. Most
|
||||
// implementations will not expose such an interface explicitly. Such mappings
|
||||
// should be maintained by interacting with the BlobIngester. Hence, this is
|
||||
// left off of BlobService and BlobStore.
|
||||
type BlobDescriptorService interface {
|
||||
BlobStatter
|
||||
|
||||
// SetDescriptor assigns the descriptor to the digest. The provided digest and
|
||||
// the digest in the descriptor must map to identical content but they may
|
||||
// differ on their algorithm. The descriptor must have the canonical
|
||||
// digest of the content and the digest algorithm must match the
|
||||
// annotators canonical algorithm.
|
||||
//
|
||||
// Such a facility can be used to map blobs between digest domains, with
|
||||
// the restriction that the algorithm of the descriptor must match the
|
||||
// canonical algorithm (ie sha256) of the annotator.
|
||||
SetDescriptor(ctx context.Context, dgst digest.Digest, desc Descriptor) error
|
||||
|
||||
// Clear enables descriptors to be unlinked
|
||||
Clear(ctx context.Context, dgst digest.Digest) error
|
||||
}
|
||||
|
||||
// BlobDescriptorServiceFactory creates middleware for BlobDescriptorService.
|
||||
type BlobDescriptorServiceFactory interface {
|
||||
BlobAccessController(svc BlobDescriptorService) BlobDescriptorService
|
||||
}
|
||||
|
||||
// ReadSeekCloser is the primary reader type for blob data, combining
|
||||
// io.ReadSeeker with io.Closer.
|
||||
type ReadSeekCloser interface {
|
||||
io.ReadSeeker
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// BlobProvider describes operations for getting blob data.
|
||||
type BlobProvider interface {
|
||||
// Get returns the entire blob identified by digest along with the descriptor.
|
||||
Get(ctx context.Context, dgst digest.Digest) ([]byte, error)
|
||||
|
||||
// Open provides a ReadSeekCloser to the blob identified by the provided
|
||||
// descriptor. If the blob is not known to the service, an error will be
|
||||
// returned.
|
||||
Open(ctx context.Context, dgst digest.Digest) (ReadSeekCloser, error)
|
||||
}
|
||||
|
||||
// BlobServer can serve blobs via http.
|
||||
type BlobServer interface {
|
||||
// ServeBlob attempts to serve the blob, identifed by dgst, via http. The
|
||||
// service may decide to redirect the client elsewhere or serve the data
|
||||
// directly.
|
||||
//
|
||||
// This handler only issues successful responses, such as 2xx or 3xx,
|
||||
// meaning it serves data or issues a redirect. If the blob is not
|
||||
// available, an error will be returned and the caller may still issue a
|
||||
// response.
|
||||
//
|
||||
// The implementation may serve the same blob from a different digest
|
||||
// domain. The appropriate headers will be set for the blob, unless they
|
||||
// have already been set by the caller.
|
||||
ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error
|
||||
}
|
||||
|
||||
// BlobIngester ingests blob data.
|
||||
type BlobIngester interface {
|
||||
// Put inserts the content p into the blob service, returning a descriptor
|
||||
// or an error.
|
||||
Put(ctx context.Context, mediaType string, p []byte) (Descriptor, error)
|
||||
|
||||
// Create allocates a new blob writer to add a blob to this service. The
|
||||
// returned handle can be written to and later resumed using an opaque
|
||||
// identifier. With this approach, one can Close and Resume a BlobWriter
|
||||
// multiple times until the BlobWriter is committed or cancelled.
|
||||
Create(ctx context.Context, options ...BlobCreateOption) (BlobWriter, error)
|
||||
|
||||
// Resume attempts to resume a write to a blob, identified by an id.
|
||||
Resume(ctx context.Context, id string) (BlobWriter, error)
|
||||
}
|
||||
|
||||
// BlobCreateOption is a general extensible function argument for blob creation
|
||||
// methods. A BlobIngester may choose to honor any or none of the given
|
||||
// BlobCreateOptions, which can be specific to the implementation of the
|
||||
// BlobIngester receiving them.
|
||||
// TODO (brianbland): unify this with ManifestServiceOption in the future
|
||||
type BlobCreateOption interface {
|
||||
Apply(interface{}) error
|
||||
}
|
||||
|
||||
// BlobWriter provides a handle for inserting data into a blob store.
|
||||
// Instances should be obtained from BlobWriteService.Writer and
|
||||
// BlobWriteService.Resume. If supported by the store, a writer can be
|
||||
// recovered with the id.
|
||||
type BlobWriter interface {
|
||||
io.WriteCloser
|
||||
io.ReaderFrom
|
||||
|
||||
// Size returns the number of bytes written to this blob.
|
||||
Size() int64
|
||||
|
||||
// ID returns the identifier for this writer. The ID can be used with the
|
||||
// Blob service to later resume the write.
|
||||
ID() string
|
||||
|
||||
// StartedAt returns the time this blob write was started.
|
||||
StartedAt() time.Time
|
||||
|
||||
// Commit completes the blob writer process. The content is verified
|
||||
// against the provided provisional descriptor, which may result in an
|
||||
// error. Depending on the implementation, written data may be validated
|
||||
// against the provisional descriptor fields. If MediaType is not present,
|
||||
// the implementation may reject the commit or assign "application/octet-
|
||||
// stream" to the blob. The returned descriptor may have a different
|
||||
// digest depending on the blob store, referred to as the canonical
|
||||
// descriptor.
|
||||
Commit(ctx context.Context, provisional Descriptor) (canonical Descriptor, err error)
|
||||
|
||||
// Cancel ends the blob write without storing any data and frees any
|
||||
// associated resources. Any data written thus far will be lost. Cancel
|
||||
// implementations should allow multiple calls even after a commit that
|
||||
// result in a no-op. This allows use of Cancel in a defer statement,
|
||||
// increasing the assurance that it is correctly called.
|
||||
Cancel(ctx context.Context) error
|
||||
}
|
||||
|
||||
// BlobService combines the operations to access, read and write blobs. This
|
||||
// can be used to describe remote blob services.
|
||||
type BlobService interface {
|
||||
BlobStatter
|
||||
BlobProvider
|
||||
BlobIngester
|
||||
}
|
||||
|
||||
// BlobStore represent the entire suite of blob related operations. Such an
|
||||
// implementation can access, read, write, delete and serve blobs.
|
||||
type BlobStore interface {
|
||||
BlobService
|
||||
BlobServer
|
||||
BlobDeleter
|
||||
}
|
89
vendor/github.com/docker/distribution/circle.yml
generated
vendored
Normal file
89
vendor/github.com/docker/distribution/circle.yml
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
|||
# Pony-up!
|
||||
machine:
|
||||
pre:
|
||||
# Install gvm
|
||||
- bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/1.0.22/binscripts/gvm-installer)
|
||||
# Install codecov for coverage
|
||||
- pip install --user codecov
|
||||
|
||||
post:
|
||||
# go
|
||||
- gvm install go1.6 --prefer-binary --name=stable
|
||||
|
||||
environment:
|
||||
# Convenient shortcuts to "common" locations
|
||||
CHECKOUT: /home/ubuntu/$CIRCLE_PROJECT_REPONAME
|
||||
BASE_DIR: src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
|
||||
# Trick circle brainflat "no absolute path" behavior
|
||||
BASE_STABLE: ../../../$HOME/.gvm/pkgsets/stable/global/$BASE_DIR
|
||||
DOCKER_BUILDTAGS: "include_oss include_gcs"
|
||||
# Workaround Circle parsing dumb bugs and/or YAML wonkyness
|
||||
CIRCLE_PAIN: "mode: set"
|
||||
|
||||
hosts:
|
||||
# Not used yet
|
||||
fancy: 127.0.0.1
|
||||
|
||||
dependencies:
|
||||
pre:
|
||||
# Copy the code to the gopath of all go versions
|
||||
- >
|
||||
gvm use stable &&
|
||||
mkdir -p "$(dirname $BASE_STABLE)" &&
|
||||
cp -R "$CHECKOUT" "$BASE_STABLE"
|
||||
|
||||
override:
|
||||
# Install dependencies for every copied clone/go version
|
||||
- gvm use stable && go get github.com/tools/godep:
|
||||
pwd: $BASE_STABLE
|
||||
|
||||
post:
|
||||
# For the stable go version, additionally install linting tools
|
||||
- >
|
||||
gvm use stable &&
|
||||
go get github.com/axw/gocov/gocov github.com/golang/lint/golint
|
||||
|
||||
test:
|
||||
pre:
|
||||
# Output the go versions we are going to test
|
||||
# - gvm use old && go version
|
||||
- gvm use stable && go version
|
||||
|
||||
# Ensure validation of dependencies
|
||||
- gvm use stable && if test -n "`git diff --stat=1000 master | grep -Ei \"vendor|godeps\"`"; then make dep-validate; fi:
|
||||
pwd: $BASE_STABLE
|
||||
|
||||
# First thing: build everything. This will catch compile errors, and it's
|
||||
# also necessary for go vet to work properly (see #807).
|
||||
- gvm use stable && godep go install $(go list ./... | grep -v "/vendor/"):
|
||||
pwd: $BASE_STABLE
|
||||
|
||||
# FMT
|
||||
- gvm use stable && make fmt:
|
||||
pwd: $BASE_STABLE
|
||||
|
||||
# VET
|
||||
- gvm use stable && make vet:
|
||||
pwd: $BASE_STABLE
|
||||
|
||||
# LINT
|
||||
- gvm use stable && make lint:
|
||||
pwd: $BASE_STABLE
|
||||
|
||||
override:
|
||||
# Test stable, and report
|
||||
- gvm use stable; export ROOT_PACKAGE=$(go list .); go list -tags "$DOCKER_BUILDTAGS" ./... | grep -v "/vendor/" | xargs -L 1 -I{} bash -c 'export PACKAGE={}; godep go test -tags "$DOCKER_BUILDTAGS" -test.short -coverprofile=$GOPATH/src/$PACKAGE/coverage.out -coverpkg=$(./coverpkg.sh $PACKAGE $ROOT_PACKAGE) $PACKAGE':
|
||||
timeout: 600
|
||||
pwd: $BASE_STABLE
|
||||
|
||||
post:
|
||||
# Report to codecov
|
||||
- bash <(curl -s https://codecov.io/bash):
|
||||
pwd: $BASE_STABLE
|
||||
|
||||
## Notes
|
||||
# Disabled the -race detector due to massive memory usage.
|
||||
# Do we want these as well?
|
||||
# - go get code.google.com/p/go.tools/cmd/goimports
|
||||
# - test -z "$(goimports -l -w ./... | tee /dev/stderr)"
|
||||
# http://labix.org/gocheck
|
85
vendor/github.com/docker/distribution/context/context.go
generated
vendored
Normal file
85
vendor/github.com/docker/distribution/context/context.go
generated
vendored
Normal file
|
@ -0,0 +1,85 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/docker/distribution/uuid"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Context is a copy of Context from the golang.org/x/net/context package.
|
||||
type Context interface {
|
||||
context.Context
|
||||
}
|
||||
|
||||
// instanceContext is a context that provides only an instance id. It is
|
||||
// provided as the main background context.
|
||||
type instanceContext struct {
|
||||
Context
|
||||
id string // id of context, logged as "instance.id"
|
||||
once sync.Once // once protect generation of the id
|
||||
}
|
||||
|
||||
func (ic *instanceContext) Value(key interface{}) interface{} {
|
||||
if key == "instance.id" {
|
||||
ic.once.Do(func() {
|
||||
// We want to lazy initialize the UUID such that we don't
|
||||
// call a random generator from the package initialization
|
||||
// code. For various reasons random could not be available
|
||||
// https://github.com/docker/distribution/issues/782
|
||||
ic.id = uuid.Generate().String()
|
||||
})
|
||||
return ic.id
|
||||
}
|
||||
|
||||
return ic.Context.Value(key)
|
||||
}
|
||||
|
||||
var background = &instanceContext{
|
||||
Context: context.Background(),
|
||||
}
|
||||
|
||||
// Background returns a non-nil, empty Context. The background context
|
||||
// provides a single key, "instance.id" that is globally unique to the
|
||||
// process.
|
||||
func Background() Context {
|
||||
return background
|
||||
}
|
||||
|
||||
// WithValue returns a copy of parent in which the value associated with key is
|
||||
// val. Use context Values only for request-scoped data that transits processes
|
||||
// and APIs, not for passing optional parameters to functions.
|
||||
func WithValue(parent Context, key, val interface{}) Context {
|
||||
return context.WithValue(parent, key, val)
|
||||
}
|
||||
|
||||
// stringMapContext is a simple context implementation that checks a map for a
|
||||
// key, falling back to a parent if not present.
|
||||
type stringMapContext struct {
|
||||
context.Context
|
||||
m map[string]interface{}
|
||||
}
|
||||
|
||||
// WithValues returns a context that proxies lookups through a map. Only
|
||||
// supports string keys.
|
||||
func WithValues(ctx context.Context, m map[string]interface{}) context.Context {
|
||||
mo := make(map[string]interface{}, len(m)) // make our own copy.
|
||||
for k, v := range m {
|
||||
mo[k] = v
|
||||
}
|
||||
|
||||
return stringMapContext{
|
||||
Context: ctx,
|
||||
m: mo,
|
||||
}
|
||||
}
|
||||
|
||||
func (smc stringMapContext) Value(key interface{}) interface{} {
|
||||
if ks, ok := key.(string); ok {
|
||||
if v, ok := smc.m[ks]; ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return smc.Context.Value(key)
|
||||
}
|
89
vendor/github.com/docker/distribution/context/doc.go
generated
vendored
Normal file
89
vendor/github.com/docker/distribution/context/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
|||
// Package context provides several utilities for working with
|
||||
// golang.org/x/net/context in http requests. Primarily, the focus is on
|
||||
// logging relevant request information but this package is not limited to
|
||||
// that purpose.
|
||||
//
|
||||
// The easiest way to get started is to get the background context:
|
||||
//
|
||||
// ctx := context.Background()
|
||||
//
|
||||
// The returned context should be passed around your application and be the
|
||||
// root of all other context instances. If the application has a version, this
|
||||
// line should be called before anything else:
|
||||
//
|
||||
// ctx := context.WithVersion(context.Background(), version)
|
||||
//
|
||||
// The above will store the version in the context and will be available to
|
||||
// the logger.
|
||||
//
|
||||
// Logging
|
||||
//
|
||||
// The most useful aspect of this package is GetLogger. This function takes
|
||||
// any context.Context interface and returns the current logger from the
|
||||
// context. Canonical usage looks like this:
|
||||
//
|
||||
// GetLogger(ctx).Infof("something interesting happened")
|
||||
//
|
||||
// GetLogger also takes optional key arguments. The keys will be looked up in
|
||||
// the context and reported with the logger. The following example would
|
||||
// return a logger that prints the version with each log message:
|
||||
//
|
||||
// ctx := context.Context(context.Background(), "version", version)
|
||||
// GetLogger(ctx, "version").Infof("this log message has a version field")
|
||||
//
|
||||
// The above would print out a log message like this:
|
||||
//
|
||||
// INFO[0000] this log message has a version field version=v2.0.0-alpha.2.m
|
||||
//
|
||||
// When used with WithLogger, we gain the ability to decorate the context with
|
||||
// loggers that have information from disparate parts of the call stack.
|
||||
// Following from the version example, we can build a new context with the
|
||||
// configured logger such that we always print the version field:
|
||||
//
|
||||
// ctx = WithLogger(ctx, GetLogger(ctx, "version"))
|
||||
//
|
||||
// Since the logger has been pushed to the context, we can now get the version
|
||||
// field for free with our log messages. Future calls to GetLogger on the new
|
||||
// context will have the version field:
|
||||
//
|
||||
// GetLogger(ctx).Infof("this log message has a version field")
|
||||
//
|
||||
// This becomes more powerful when we start stacking loggers. Let's say we
|
||||
// have the version logger from above but also want a request id. Using the
|
||||
// context above, in our request scoped function, we place another logger in
|
||||
// the context:
|
||||
//
|
||||
// ctx = context.WithValue(ctx, "http.request.id", "unique id") // called when building request context
|
||||
// ctx = WithLogger(ctx, GetLogger(ctx, "http.request.id"))
|
||||
//
|
||||
// When GetLogger is called on the new context, "http.request.id" will be
|
||||
// included as a logger field, along with the original "version" field:
|
||||
//
|
||||
// INFO[0000] this log message has a version field http.request.id=unique id version=v2.0.0-alpha.2.m
|
||||
//
|
||||
// Note that this only affects the new context, the previous context, with the
|
||||
// version field, can be used independently. Put another way, the new logger,
|
||||
// added to the request context, is unique to that context and can have
|
||||
// request scoped varaibles.
|
||||
//
|
||||
// HTTP Requests
|
||||
//
|
||||
// This package also contains several methods for working with http requests.
|
||||
// The concepts are very similar to those described above. We simply place the
|
||||
// request in the context using WithRequest. This makes the request variables
|
||||
// available. GetRequestLogger can then be called to get request specific
|
||||
// variables in a log line:
|
||||
//
|
||||
// ctx = WithRequest(ctx, req)
|
||||
// GetRequestLogger(ctx).Infof("request variables")
|
||||
//
|
||||
// Like above, if we want to include the request data in all log messages in
|
||||
// the context, we push the logger to a new context and use that one:
|
||||
//
|
||||
// ctx = WithLogger(ctx, GetRequestLogger(ctx))
|
||||
//
|
||||
// The concept is fairly powerful and ensures that calls throughout the stack
|
||||
// can be traced in log messages. Using the fields like "http.request.id", one
|
||||
// can analyze call flow for a particular request with a simple grep of the
|
||||
// logs.
|
||||
package context
|
366
vendor/github.com/docker/distribution/context/http.go
generated
vendored
Normal file
366
vendor/github.com/docker/distribution/context/http.go
generated
vendored
Normal file
|
@ -0,0 +1,366 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// Common errors used with this package.
|
||||
var (
|
||||
ErrNoRequestContext = errors.New("no http request in context")
|
||||
ErrNoResponseWriterContext = errors.New("no http response in context")
|
||||
)
|
||||
|
||||
func parseIP(ipStr string) net.IP {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
log.Warnf("invalid remote IP address: %q", ipStr)
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
// RemoteAddr extracts the remote address of the request, taking into
|
||||
// account proxy headers.
|
||||
func RemoteAddr(r *http.Request) string {
|
||||
if prior := r.Header.Get("X-Forwarded-For"); prior != "" {
|
||||
proxies := strings.Split(prior, ",")
|
||||
if len(proxies) > 0 {
|
||||
remoteAddr := strings.Trim(proxies[0], " ")
|
||||
if parseIP(remoteAddr) != nil {
|
||||
return remoteAddr
|
||||
}
|
||||
}
|
||||
}
|
||||
// X-Real-Ip is less supported, but worth checking in the
|
||||
// absence of X-Forwarded-For
|
||||
if realIP := r.Header.Get("X-Real-Ip"); realIP != "" {
|
||||
if parseIP(realIP) != nil {
|
||||
return realIP
|
||||
}
|
||||
}
|
||||
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
// RemoteIP extracts the remote IP of the request, taking into
|
||||
// account proxy headers.
|
||||
func RemoteIP(r *http.Request) string {
|
||||
addr := RemoteAddr(r)
|
||||
|
||||
// Try parsing it as "IP:port"
|
||||
if ip, _, err := net.SplitHostPort(addr); err == nil {
|
||||
return ip
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
// WithRequest places the request on the context. The context of the request
|
||||
// is assigned a unique id, available at "http.request.id". The request itself
|
||||
// is available at "http.request". Other common attributes are available under
|
||||
// the prefix "http.request.". If a request is already present on the context,
|
||||
// this method will panic.
|
||||
func WithRequest(ctx Context, r *http.Request) Context {
|
||||
if ctx.Value("http.request") != nil {
|
||||
// NOTE(stevvooe): This needs to be considered a programming error. It
|
||||
// is unlikely that we'd want to have more than one request in
|
||||
// context.
|
||||
panic("only one request per context")
|
||||
}
|
||||
|
||||
return &httpRequestContext{
|
||||
Context: ctx,
|
||||
startedAt: time.Now(),
|
||||
id: uuid.Generate().String(),
|
||||
r: r,
|
||||
}
|
||||
}
|
||||
|
||||
// GetRequest returns the http request in the given context. Returns
|
||||
// ErrNoRequestContext if the context does not have an http request associated
|
||||
// with it.
|
||||
func GetRequest(ctx Context) (*http.Request, error) {
|
||||
if r, ok := ctx.Value("http.request").(*http.Request); r != nil && ok {
|
||||
return r, nil
|
||||
}
|
||||
return nil, ErrNoRequestContext
|
||||
}
|
||||
|
||||
// GetRequestID attempts to resolve the current request id, if possible. An
|
||||
// error is return if it is not available on the context.
|
||||
func GetRequestID(ctx Context) string {
|
||||
return GetStringValue(ctx, "http.request.id")
|
||||
}
|
||||
|
||||
// WithResponseWriter returns a new context and response writer that makes
|
||||
// interesting response statistics available within the context.
|
||||
func WithResponseWriter(ctx Context, w http.ResponseWriter) (Context, http.ResponseWriter) {
|
||||
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
||||
irwCN := &instrumentedResponseWriterCN{
|
||||
instrumentedResponseWriter: instrumentedResponseWriter{
|
||||
ResponseWriter: w,
|
||||
Context: ctx,
|
||||
},
|
||||
CloseNotifier: closeNotifier,
|
||||
}
|
||||
|
||||
return irwCN, irwCN
|
||||
}
|
||||
|
||||
irw := instrumentedResponseWriter{
|
||||
ResponseWriter: w,
|
||||
Context: ctx,
|
||||
}
|
||||
return &irw, &irw
|
||||
}
|
||||
|
||||
// GetResponseWriter returns the http.ResponseWriter from the provided
|
||||
// context. If not present, ErrNoResponseWriterContext is returned. The
|
||||
// returned instance provides instrumentation in the context.
|
||||
func GetResponseWriter(ctx Context) (http.ResponseWriter, error) {
|
||||
v := ctx.Value("http.response")
|
||||
|
||||
rw, ok := v.(http.ResponseWriter)
|
||||
if !ok || rw == nil {
|
||||
return nil, ErrNoResponseWriterContext
|
||||
}
|
||||
|
||||
return rw, nil
|
||||
}
|
||||
|
||||
// getVarsFromRequest let's us change request vars implementation for testing
|
||||
// and maybe future changes.
|
||||
var getVarsFromRequest = mux.Vars
|
||||
|
||||
// WithVars extracts gorilla/mux vars and makes them available on the returned
|
||||
// context. Variables are available at keys with the prefix "vars.". For
|
||||
// example, if looking for the variable "name", it can be accessed as
|
||||
// "vars.name". Implementations that are accessing values need not know that
|
||||
// the underlying context is implemented with gorilla/mux vars.
|
||||
func WithVars(ctx Context, r *http.Request) Context {
|
||||
return &muxVarsContext{
|
||||
Context: ctx,
|
||||
vars: getVarsFromRequest(r),
|
||||
}
|
||||
}
|
||||
|
||||
// GetRequestLogger returns a logger that contains fields from the request in
|
||||
// the current context. If the request is not available in the context, no
|
||||
// fields will display. Request loggers can safely be pushed onto the context.
|
||||
func GetRequestLogger(ctx Context) Logger {
|
||||
return GetLogger(ctx,
|
||||
"http.request.id",
|
||||
"http.request.method",
|
||||
"http.request.host",
|
||||
"http.request.uri",
|
||||
"http.request.referer",
|
||||
"http.request.useragent",
|
||||
"http.request.remoteaddr",
|
||||
"http.request.contenttype")
|
||||
}
|
||||
|
||||
// GetResponseLogger reads the current response stats and builds a logger.
|
||||
// Because the values are read at call time, pushing a logger returned from
|
||||
// this function on the context will lead to missing or invalid data. Only
|
||||
// call this at the end of a request, after the response has been written.
|
||||
func GetResponseLogger(ctx Context) Logger {
|
||||
l := getLogrusLogger(ctx,
|
||||
"http.response.written",
|
||||
"http.response.status",
|
||||
"http.response.contenttype")
|
||||
|
||||
duration := Since(ctx, "http.request.startedat")
|
||||
|
||||
if duration > 0 {
|
||||
l = l.WithField("http.response.duration", duration.String())
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// httpRequestContext makes information about a request available to context.
|
||||
type httpRequestContext struct {
|
||||
Context
|
||||
|
||||
startedAt time.Time
|
||||
id string
|
||||
r *http.Request
|
||||
}
|
||||
|
||||
// Value returns a keyed element of the request for use in the context. To get
|
||||
// the request itself, query "request". For other components, access them as
|
||||
// "request.<component>". For example, r.RequestURI
|
||||
func (ctx *httpRequestContext) Value(key interface{}) interface{} {
|
||||
if keyStr, ok := key.(string); ok {
|
||||
if keyStr == "http.request" {
|
||||
return ctx.r
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(keyStr, "http.request.") {
|
||||
goto fallback
|
||||
}
|
||||
|
||||
parts := strings.Split(keyStr, ".")
|
||||
|
||||
if len(parts) != 3 {
|
||||
goto fallback
|
||||
}
|
||||
|
||||
switch parts[2] {
|
||||
case "uri":
|
||||
return ctx.r.RequestURI
|
||||
case "remoteaddr":
|
||||
return RemoteAddr(ctx.r)
|
||||
case "method":
|
||||
return ctx.r.Method
|
||||
case "host":
|
||||
return ctx.r.Host
|
||||
case "referer":
|
||||
referer := ctx.r.Referer()
|
||||
if referer != "" {
|
||||
return referer
|
||||
}
|
||||
case "useragent":
|
||||
return ctx.r.UserAgent()
|
||||
case "id":
|
||||
return ctx.id
|
||||
case "startedat":
|
||||
return ctx.startedAt
|
||||
case "contenttype":
|
||||
ct := ctx.r.Header.Get("Content-Type")
|
||||
if ct != "" {
|
||||
return ct
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fallback:
|
||||
return ctx.Context.Value(key)
|
||||
}
|
||||
|
||||
type muxVarsContext struct {
|
||||
Context
|
||||
vars map[string]string
|
||||
}
|
||||
|
||||
func (ctx *muxVarsContext) Value(key interface{}) interface{} {
|
||||
if keyStr, ok := key.(string); ok {
|
||||
if keyStr == "vars" {
|
||||
return ctx.vars
|
||||
}
|
||||
|
||||
if strings.HasPrefix(keyStr, "vars.") {
|
||||
keyStr = strings.TrimPrefix(keyStr, "vars.")
|
||||
}
|
||||
|
||||
if v, ok := ctx.vars[keyStr]; ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.Context.Value(key)
|
||||
}
|
||||
|
||||
// instrumentedResponseWriterCN provides response writer information in a
|
||||
// context. It implements http.CloseNotifier so that users can detect
|
||||
// early disconnects.
|
||||
type instrumentedResponseWriterCN struct {
|
||||
instrumentedResponseWriter
|
||||
http.CloseNotifier
|
||||
}
|
||||
|
||||
// instrumentedResponseWriter provides response writer information in a
|
||||
// context. This variant is only used in the case where CloseNotifier is not
|
||||
// implemented by the parent ResponseWriter.
|
||||
type instrumentedResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
Context
|
||||
|
||||
mu sync.Mutex
|
||||
status int
|
||||
written int64
|
||||
}
|
||||
|
||||
func (irw *instrumentedResponseWriter) Write(p []byte) (n int, err error) {
|
||||
n, err = irw.ResponseWriter.Write(p)
|
||||
|
||||
irw.mu.Lock()
|
||||
irw.written += int64(n)
|
||||
|
||||
// Guess the likely status if not set.
|
||||
if irw.status == 0 {
|
||||
irw.status = http.StatusOK
|
||||
}
|
||||
|
||||
irw.mu.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (irw *instrumentedResponseWriter) WriteHeader(status int) {
|
||||
irw.ResponseWriter.WriteHeader(status)
|
||||
|
||||
irw.mu.Lock()
|
||||
irw.status = status
|
||||
irw.mu.Unlock()
|
||||
}
|
||||
|
||||
func (irw *instrumentedResponseWriter) Flush() {
|
||||
if flusher, ok := irw.ResponseWriter.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (irw *instrumentedResponseWriter) Value(key interface{}) interface{} {
|
||||
if keyStr, ok := key.(string); ok {
|
||||
if keyStr == "http.response" {
|
||||
return irw
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(keyStr, "http.response.") {
|
||||
goto fallback
|
||||
}
|
||||
|
||||
parts := strings.Split(keyStr, ".")
|
||||
|
||||
if len(parts) != 3 {
|
||||
goto fallback
|
||||
}
|
||||
|
||||
irw.mu.Lock()
|
||||
defer irw.mu.Unlock()
|
||||
|
||||
switch parts[2] {
|
||||
case "written":
|
||||
return irw.written
|
||||
case "status":
|
||||
return irw.status
|
||||
case "contenttype":
|
||||
contentType := irw.Header().Get("Content-Type")
|
||||
if contentType != "" {
|
||||
return contentType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fallback:
|
||||
return irw.Context.Value(key)
|
||||
}
|
||||
|
||||
func (irw *instrumentedResponseWriterCN) Value(key interface{}) interface{} {
|
||||
if keyStr, ok := key.(string); ok {
|
||||
if keyStr == "http.response" {
|
||||
return irw
|
||||
}
|
||||
}
|
||||
|
||||
return irw.instrumentedResponseWriter.Value(key)
|
||||
}
|
116
vendor/github.com/docker/distribution/context/logger.go
generated
vendored
Normal file
116
vendor/github.com/docker/distribution/context/logger.go
generated
vendored
Normal file
|
@ -0,0 +1,116 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Logger provides a leveled-logging interface.
|
||||
type Logger interface {
|
||||
// standard logger methods
|
||||
Print(args ...interface{})
|
||||
Printf(format string, args ...interface{})
|
||||
Println(args ...interface{})
|
||||
|
||||
Fatal(args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
Fatalln(args ...interface{})
|
||||
|
||||
Panic(args ...interface{})
|
||||
Panicf(format string, args ...interface{})
|
||||
Panicln(args ...interface{})
|
||||
|
||||
// Leveled methods, from logrus
|
||||
Debug(args ...interface{})
|
||||
Debugf(format string, args ...interface{})
|
||||
Debugln(args ...interface{})
|
||||
|
||||
Error(args ...interface{})
|
||||
Errorf(format string, args ...interface{})
|
||||
Errorln(args ...interface{})
|
||||
|
||||
Info(args ...interface{})
|
||||
Infof(format string, args ...interface{})
|
||||
Infoln(args ...interface{})
|
||||
|
||||
Warn(args ...interface{})
|
||||
Warnf(format string, args ...interface{})
|
||||
Warnln(args ...interface{})
|
||||
}
|
||||
|
||||
// WithLogger creates a new context with provided logger.
|
||||
func WithLogger(ctx Context, logger Logger) Context {
|
||||
return WithValue(ctx, "logger", logger)
|
||||
}
|
||||
|
||||
// GetLoggerWithField returns a logger instance with the specified field key
|
||||
// and value without affecting the context. Extra specified keys will be
|
||||
// resolved from the context.
|
||||
func GetLoggerWithField(ctx Context, key, value interface{}, keys ...interface{}) Logger {
|
||||
return getLogrusLogger(ctx, keys...).WithField(fmt.Sprint(key), value)
|
||||
}
|
||||
|
||||
// GetLoggerWithFields returns a logger instance with the specified fields
|
||||
// without affecting the context. Extra specified keys will be resolved from
|
||||
// the context.
|
||||
func GetLoggerWithFields(ctx Context, fields map[interface{}]interface{}, keys ...interface{}) Logger {
|
||||
// must convert from interface{} -> interface{} to string -> interface{} for logrus.
|
||||
lfields := make(logrus.Fields, len(fields))
|
||||
for key, value := range fields {
|
||||
lfields[fmt.Sprint(key)] = value
|
||||
}
|
||||
|
||||
return getLogrusLogger(ctx, keys...).WithFields(lfields)
|
||||
}
|
||||
|
||||
// GetLogger returns the logger from the current context, if present. If one
|
||||
// or more keys are provided, they will be resolved on the context and
|
||||
// included in the logger. While context.Value takes an interface, any key
|
||||
// argument passed to GetLogger will be passed to fmt.Sprint when expanded as
|
||||
// a logging key field. If context keys are integer constants, for example,
|
||||
// its recommended that a String method is implemented.
|
||||
func GetLogger(ctx Context, keys ...interface{}) Logger {
|
||||
return getLogrusLogger(ctx, keys...)
|
||||
}
|
||||
|
||||
// GetLogrusLogger returns the logrus logger for the context. If one more keys
|
||||
// are provided, they will be resolved on the context and included in the
|
||||
// logger. Only use this function if specific logrus functionality is
|
||||
// required.
|
||||
func getLogrusLogger(ctx Context, keys ...interface{}) *logrus.Entry {
|
||||
var logger *logrus.Entry
|
||||
|
||||
// Get a logger, if it is present.
|
||||
loggerInterface := ctx.Value("logger")
|
||||
if loggerInterface != nil {
|
||||
if lgr, ok := loggerInterface.(*logrus.Entry); ok {
|
||||
logger = lgr
|
||||
}
|
||||
}
|
||||
|
||||
if logger == nil {
|
||||
fields := logrus.Fields{}
|
||||
|
||||
// Fill in the instance id, if we have it.
|
||||
instanceID := ctx.Value("instance.id")
|
||||
if instanceID != nil {
|
||||
fields["instance.id"] = instanceID
|
||||
}
|
||||
|
||||
fields["go.version"] = runtime.Version()
|
||||
// If no logger is found, just return the standard logger.
|
||||
logger = logrus.StandardLogger().WithFields(fields)
|
||||
}
|
||||
|
||||
fields := logrus.Fields{}
|
||||
for _, key := range keys {
|
||||
v := ctx.Value(key)
|
||||
if v != nil {
|
||||
fields[fmt.Sprint(key)] = v
|
||||
}
|
||||
}
|
||||
|
||||
return logger.WithFields(fields)
|
||||
}
|
104
vendor/github.com/docker/distribution/context/trace.go
generated
vendored
Normal file
104
vendor/github.com/docker/distribution/context/trace.go
generated
vendored
Normal file
|
@ -0,0 +1,104 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/uuid"
|
||||
)
|
||||
|
||||
// WithTrace allocates a traced timing span in a new context. This allows a
|
||||
// caller to track the time between calling WithTrace and the returned done
|
||||
// function. When the done function is called, a log message is emitted with a
|
||||
// "trace.duration" field, corresponding to the elapsed time and a
|
||||
// "trace.func" field, corresponding to the function that called WithTrace.
|
||||
//
|
||||
// The logging keys "trace.id" and "trace.parent.id" are provided to implement
|
||||
// dapper-like tracing. This function should be complemented with a WithSpan
|
||||
// method that could be used for tracing distributed RPC calls.
|
||||
//
|
||||
// The main benefit of this function is to post-process log messages or
|
||||
// intercept them in a hook to provide timing data. Trace ids and parent ids
|
||||
// can also be linked to provide call tracing, if so required.
|
||||
//
|
||||
// Here is an example of the usage:
|
||||
//
|
||||
// func timedOperation(ctx Context) {
|
||||
// ctx, done := WithTrace(ctx)
|
||||
// defer done("this will be the log message")
|
||||
// // ... function body ...
|
||||
// }
|
||||
//
|
||||
// If the function ran for roughly 1s, such a usage would emit a log message
|
||||
// as follows:
|
||||
//
|
||||
// INFO[0001] this will be the log message trace.duration=1.004575763s trace.func=github.com/docker/distribution/context.traceOperation trace.id=<id> ...
|
||||
//
|
||||
// Notice that the function name is automatically resolved, along with the
|
||||
// package and a trace id is emitted that can be linked with parent ids.
|
||||
func WithTrace(ctx Context) (Context, func(format string, a ...interface{})) {
|
||||
if ctx == nil {
|
||||
ctx = Background()
|
||||
}
|
||||
|
||||
pc, file, line, _ := runtime.Caller(1)
|
||||
f := runtime.FuncForPC(pc)
|
||||
ctx = &traced{
|
||||
Context: ctx,
|
||||
id: uuid.Generate().String(),
|
||||
start: time.Now(),
|
||||
parent: GetStringValue(ctx, "trace.id"),
|
||||
fnname: f.Name(),
|
||||
file: file,
|
||||
line: line,
|
||||
}
|
||||
|
||||
return ctx, func(format string, a ...interface{}) {
|
||||
GetLogger(ctx,
|
||||
"trace.duration",
|
||||
"trace.id",
|
||||
"trace.parent.id",
|
||||
"trace.func",
|
||||
"trace.file",
|
||||
"trace.line").
|
||||
Debugf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// traced represents a context that is traced for function call timing. It
|
||||
// also provides fast lookup for the various attributes that are available on
|
||||
// the trace.
|
||||
type traced struct {
|
||||
Context
|
||||
id string
|
||||
parent string
|
||||
start time.Time
|
||||
fnname string
|
||||
file string
|
||||
line int
|
||||
}
|
||||
|
||||
func (ts *traced) Value(key interface{}) interface{} {
|
||||
switch key {
|
||||
case "trace.start":
|
||||
return ts.start
|
||||
case "trace.duration":
|
||||
return time.Since(ts.start)
|
||||
case "trace.id":
|
||||
return ts.id
|
||||
case "trace.parent.id":
|
||||
if ts.parent == "" {
|
||||
return nil // must return nil to signal no parent.
|
||||
}
|
||||
|
||||
return ts.parent
|
||||
case "trace.func":
|
||||
return ts.fnname
|
||||
case "trace.file":
|
||||
return ts.file
|
||||
case "trace.line":
|
||||
return ts.line
|
||||
}
|
||||
|
||||
return ts.Context.Value(key)
|
||||
}
|
24
vendor/github.com/docker/distribution/context/util.go
generated
vendored
Normal file
24
vendor/github.com/docker/distribution/context/util.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Since looks up key, which should be a time.Time, and returns the duration
|
||||
// since that time. If the key is not found, the value returned will be zero.
|
||||
// This is helpful when inferring metrics related to context execution times.
|
||||
func Since(ctx Context, key interface{}) time.Duration {
|
||||
if startedAt, ok := ctx.Value(key).(time.Time); ok {
|
||||
return time.Since(startedAt)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetStringValue returns a string value from the context. The empty string
|
||||
// will be returned if not found.
|
||||
func GetStringValue(ctx Context, key interface{}) (value string) {
|
||||
if valuev, ok := ctx.Value(key).(string); ok {
|
||||
value = valuev
|
||||
}
|
||||
return value
|
||||
}
|
16
vendor/github.com/docker/distribution/context/version.go
generated
vendored
Normal file
16
vendor/github.com/docker/distribution/context/version.go
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
package context
|
||||
|
||||
// WithVersion stores the application version in the context. The new context
|
||||
// gets a logger to ensure log messages are marked with the application
|
||||
// version.
|
||||
func WithVersion(ctx Context, version string) Context {
|
||||
ctx = WithValue(ctx, "version", version)
|
||||
// push a new logger onto the stack
|
||||
return WithLogger(ctx, GetLogger(ctx, "version"))
|
||||
}
|
||||
|
||||
// GetVersion returns the application version from the context. An empty
|
||||
// string may returned if the version was not set on the context.
|
||||
func GetVersion(ctx Context) string {
|
||||
return GetStringValue(ctx, "version")
|
||||
}
|
7
vendor/github.com/docker/distribution/coverpkg.sh
generated
vendored
Executable file
7
vendor/github.com/docker/distribution/coverpkg.sh
generated
vendored
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
# Given a subpackage and the containing package, figures out which packages
|
||||
# need to be passed to `go test -coverpkg`: this includes all of the
|
||||
# subpackage's dependencies within the containing package, as well as the
|
||||
# subpackage itself.
|
||||
DEPENDENCIES="$(go list -f $'{{range $f := .Deps}}{{$f}}\n{{end}}' ${1} | grep ${2} | grep -v github.com/docker/distribution/vendor)"
|
||||
echo "${1} ${DEPENDENCIES}" | xargs echo -n | tr ' ' ','
|
139
vendor/github.com/docker/distribution/digest/digest.go
generated
vendored
Normal file
139
vendor/github.com/docker/distribution/digest/digest.go
generated
vendored
Normal file
|
@ -0,0 +1,139 @@
|
|||
package digest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// DigestSha256EmptyTar is the canonical sha256 digest of empty data
|
||||
DigestSha256EmptyTar = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
)
|
||||
|
||||
// Digest allows simple protection of hex formatted digest strings, prefixed
|
||||
// by their algorithm. Strings of type Digest have some guarantee of being in
|
||||
// the correct format and it provides quick access to the components of a
|
||||
// digest string.
|
||||
//
|
||||
// The following is an example of the contents of Digest types:
|
||||
//
|
||||
// sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc
|
||||
//
|
||||
// This allows to abstract the digest behind this type and work only in those
|
||||
// terms.
|
||||
type Digest string
|
||||
|
||||
// NewDigest returns a Digest from alg and a hash.Hash object.
|
||||
func NewDigest(alg Algorithm, h hash.Hash) Digest {
|
||||
return NewDigestFromBytes(alg, h.Sum(nil))
|
||||
}
|
||||
|
||||
// NewDigestFromBytes returns a new digest from the byte contents of p.
|
||||
// Typically, this can come from hash.Hash.Sum(...) or xxx.SumXXX(...)
|
||||
// functions. This is also useful for rebuilding digests from binary
|
||||
// serializations.
|
||||
func NewDigestFromBytes(alg Algorithm, p []byte) Digest {
|
||||
return Digest(fmt.Sprintf("%s:%x", alg, p))
|
||||
}
|
||||
|
||||
// NewDigestFromHex returns a Digest from alg and a the hex encoded digest.
|
||||
func NewDigestFromHex(alg, hex string) Digest {
|
||||
return Digest(fmt.Sprintf("%s:%s", alg, hex))
|
||||
}
|
||||
|
||||
// DigestRegexp matches valid digest types.
|
||||
var DigestRegexp = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+`)
|
||||
|
||||
// DigestRegexpAnchored matches valid digest types, anchored to the start and end of the match.
|
||||
var DigestRegexpAnchored = regexp.MustCompile(`^` + DigestRegexp.String() + `$`)
|
||||
|
||||
var (
|
||||
// ErrDigestInvalidFormat returned when digest format invalid.
|
||||
ErrDigestInvalidFormat = fmt.Errorf("invalid checksum digest format")
|
||||
|
||||
// ErrDigestInvalidLength returned when digest has invalid length.
|
||||
ErrDigestInvalidLength = fmt.Errorf("invalid checksum digest length")
|
||||
|
||||
// ErrDigestUnsupported returned when the digest algorithm is unsupported.
|
||||
ErrDigestUnsupported = fmt.Errorf("unsupported digest algorithm")
|
||||
)
|
||||
|
||||
// ParseDigest parses s and returns the validated digest object. An error will
|
||||
// be returned if the format is invalid.
|
||||
func ParseDigest(s string) (Digest, error) {
|
||||
d := Digest(s)
|
||||
|
||||
return d, d.Validate()
|
||||
}
|
||||
|
||||
// FromReader returns the most valid digest for the underlying content using
|
||||
// the canonical digest algorithm.
|
||||
func FromReader(rd io.Reader) (Digest, error) {
|
||||
return Canonical.FromReader(rd)
|
||||
}
|
||||
|
||||
// FromBytes digests the input and returns a Digest.
|
||||
func FromBytes(p []byte) Digest {
|
||||
return Canonical.FromBytes(p)
|
||||
}
|
||||
|
||||
// Validate checks that the contents of d is a valid digest, returning an
|
||||
// error if not.
|
||||
func (d Digest) Validate() error {
|
||||
s := string(d)
|
||||
|
||||
if !DigestRegexpAnchored.MatchString(s) {
|
||||
return ErrDigestInvalidFormat
|
||||
}
|
||||
|
||||
i := strings.Index(s, ":")
|
||||
if i < 0 {
|
||||
return ErrDigestInvalidFormat
|
||||
}
|
||||
|
||||
// case: "sha256:" with no hex.
|
||||
if i+1 == len(s) {
|
||||
return ErrDigestInvalidFormat
|
||||
}
|
||||
|
||||
switch algorithm := Algorithm(s[:i]); algorithm {
|
||||
case SHA256, SHA384, SHA512:
|
||||
if algorithm.Size()*2 != len(s[i+1:]) {
|
||||
return ErrDigestInvalidLength
|
||||
}
|
||||
break
|
||||
default:
|
||||
return ErrDigestUnsupported
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Algorithm returns the algorithm portion of the digest. This will panic if
|
||||
// the underlying digest is not in a valid format.
|
||||
func (d Digest) Algorithm() Algorithm {
|
||||
return Algorithm(d[:d.sepIndex()])
|
||||
}
|
||||
|
||||
// Hex returns the hex digest portion of the digest. This will panic if the
|
||||
// underlying digest is not in a valid format.
|
||||
func (d Digest) Hex() string {
|
||||
return string(d[d.sepIndex()+1:])
|
||||
}
|
||||
|
||||
func (d Digest) String() string {
|
||||
return string(d)
|
||||
}
|
||||
|
||||
func (d Digest) sepIndex() int {
|
||||
i := strings.Index(string(d), ":")
|
||||
|
||||
if i < 0 {
|
||||
panic("could not find ':' in digest: " + d)
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
155
vendor/github.com/docker/distribution/digest/digester.go
generated
vendored
Normal file
155
vendor/github.com/docker/distribution/digest/digester.go
generated
vendored
Normal file
|
@ -0,0 +1,155 @@
|
|||
package digest
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Algorithm identifies and implementation of a digester by an identifier.
|
||||
// Note the that this defines both the hash algorithm used and the string
|
||||
// encoding.
|
||||
type Algorithm string
|
||||
|
||||
// supported digest types
|
||||
const (
|
||||
SHA256 Algorithm = "sha256" // sha256 with hex encoding
|
||||
SHA384 Algorithm = "sha384" // sha384 with hex encoding
|
||||
SHA512 Algorithm = "sha512" // sha512 with hex encoding
|
||||
|
||||
// Canonical is the primary digest algorithm used with the distribution
|
||||
// project. Other digests may be used but this one is the primary storage
|
||||
// digest.
|
||||
Canonical = SHA256
|
||||
)
|
||||
|
||||
var (
|
||||
// TODO(stevvooe): Follow the pattern of the standard crypto package for
|
||||
// registration of digests. Effectively, we are a registerable set and
|
||||
// common symbol access.
|
||||
|
||||
// algorithms maps values to hash.Hash implementations. Other algorithms
|
||||
// may be available but they cannot be calculated by the digest package.
|
||||
algorithms = map[Algorithm]crypto.Hash{
|
||||
SHA256: crypto.SHA256,
|
||||
SHA384: crypto.SHA384,
|
||||
SHA512: crypto.SHA512,
|
||||
}
|
||||
)
|
||||
|
||||
// Available returns true if the digest type is available for use. If this
|
||||
// returns false, New and Hash will return nil.
|
||||
func (a Algorithm) Available() bool {
|
||||
h, ok := algorithms[a]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// check availability of the hash, as well
|
||||
return h.Available()
|
||||
}
|
||||
|
||||
func (a Algorithm) String() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
// Size returns number of bytes returned by the hash.
|
||||
func (a Algorithm) Size() int {
|
||||
h, ok := algorithms[a]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return h.Size()
|
||||
}
|
||||
|
||||
// Set implemented to allow use of Algorithm as a command line flag.
|
||||
func (a *Algorithm) Set(value string) error {
|
||||
if value == "" {
|
||||
*a = Canonical
|
||||
} else {
|
||||
// just do a type conversion, support is queried with Available.
|
||||
*a = Algorithm(value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// New returns a new digester for the specified algorithm. If the algorithm
|
||||
// does not have a digester implementation, nil will be returned. This can be
|
||||
// checked by calling Available before calling New.
|
||||
func (a Algorithm) New() Digester {
|
||||
return &digester{
|
||||
alg: a,
|
||||
hash: a.Hash(),
|
||||
}
|
||||
}
|
||||
|
||||
// Hash returns a new hash as used by the algorithm. If not available, the
|
||||
// method will panic. Check Algorithm.Available() before calling.
|
||||
func (a Algorithm) Hash() hash.Hash {
|
||||
if !a.Available() {
|
||||
// NOTE(stevvooe): A missing hash is usually a programming error that
|
||||
// must be resolved at compile time. We don't import in the digest
|
||||
// package to allow users to choose their hash implementation (such as
|
||||
// when using stevvooe/resumable or a hardware accelerated package).
|
||||
//
|
||||
// Applications that may want to resolve the hash at runtime should
|
||||
// call Algorithm.Available before call Algorithm.Hash().
|
||||
panic(fmt.Sprintf("%v not available (make sure it is imported)", a))
|
||||
}
|
||||
|
||||
return algorithms[a].New()
|
||||
}
|
||||
|
||||
// FromReader returns the digest of the reader using the algorithm.
|
||||
func (a Algorithm) FromReader(rd io.Reader) (Digest, error) {
|
||||
digester := a.New()
|
||||
|
||||
if _, err := io.Copy(digester.Hash(), rd); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return digester.Digest(), nil
|
||||
}
|
||||
|
||||
// FromBytes digests the input and returns a Digest.
|
||||
func (a Algorithm) FromBytes(p []byte) Digest {
|
||||
digester := a.New()
|
||||
|
||||
if _, err := digester.Hash().Write(p); err != nil {
|
||||
// Writes to a Hash should never fail. None of the existing
|
||||
// hash implementations in the stdlib or hashes vendored
|
||||
// here can return errors from Write. Having a panic in this
|
||||
// condition instead of having FromBytes return an error value
|
||||
// avoids unnecessary error handling paths in all callers.
|
||||
panic("write to hash function returned error: " + err.Error())
|
||||
}
|
||||
|
||||
return digester.Digest()
|
||||
}
|
||||
|
||||
// TODO(stevvooe): Allow resolution of verifiers using the digest type and
|
||||
// this registration system.
|
||||
|
||||
// Digester calculates the digest of written data. Writes should go directly
|
||||
// to the return value of Hash, while calling Digest will return the current
|
||||
// value of the digest.
|
||||
type Digester interface {
|
||||
Hash() hash.Hash // provides direct access to underlying hash instance.
|
||||
Digest() Digest
|
||||
}
|
||||
|
||||
// digester provides a simple digester definition that embeds a hasher.
|
||||
type digester struct {
|
||||
alg Algorithm
|
||||
hash hash.Hash
|
||||
}
|
||||
|
||||
func (d *digester) Hash() hash.Hash {
|
||||
return d.hash
|
||||
}
|
||||
|
||||
func (d *digester) Digest() Digest {
|
||||
return NewDigest(d.alg, d.hash)
|
||||
}
|
42
vendor/github.com/docker/distribution/digest/doc.go
generated
vendored
Normal file
42
vendor/github.com/docker/distribution/digest/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Package digest provides a generalized type to opaquely represent message
|
||||
// digests and their operations within the registry. The Digest type is
|
||||
// designed to serve as a flexible identifier in a content-addressable system.
|
||||
// More importantly, it provides tools and wrappers to work with
|
||||
// hash.Hash-based digests with little effort.
|
||||
//
|
||||
// Basics
|
||||
//
|
||||
// The format of a digest is simply a string with two parts, dubbed the
|
||||
// "algorithm" and the "digest", separated by a colon:
|
||||
//
|
||||
// <algorithm>:<digest>
|
||||
//
|
||||
// An example of a sha256 digest representation follows:
|
||||
//
|
||||
// sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc
|
||||
//
|
||||
// In this case, the string "sha256" is the algorithm and the hex bytes are
|
||||
// the "digest".
|
||||
//
|
||||
// Because the Digest type is simply a string, once a valid Digest is
|
||||
// obtained, comparisons are cheap, quick and simple to express with the
|
||||
// standard equality operator.
|
||||
//
|
||||
// Verification
|
||||
//
|
||||
// The main benefit of using the Digest type is simple verification against a
|
||||
// given digest. The Verifier interface, modeled after the stdlib hash.Hash
|
||||
// interface, provides a common write sink for digest verification. After
|
||||
// writing is complete, calling the Verifier.Verified method will indicate
|
||||
// whether or not the stream of bytes matches the target digest.
|
||||
//
|
||||
// Missing Features
|
||||
//
|
||||
// In addition to the above, we intend to add the following features to this
|
||||
// package:
|
||||
//
|
||||
// 1. A Digester type that supports write sink digest calculation.
|
||||
//
|
||||
// 2. Suspend and resume of ongoing digest calculations to support efficient digest verification in the registry.
|
||||
//
|
||||
package digest
|
245
vendor/github.com/docker/distribution/digest/set.go
generated
vendored
Normal file
245
vendor/github.com/docker/distribution/digest/set.go
generated
vendored
Normal file
|
@ -0,0 +1,245 @@
|
|||
package digest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrDigestNotFound is used when a matching digest
|
||||
// could not be found in a set.
|
||||
ErrDigestNotFound = errors.New("digest not found")
|
||||
|
||||
// ErrDigestAmbiguous is used when multiple digests
|
||||
// are found in a set. None of the matching digests
|
||||
// should be considered valid matches.
|
||||
ErrDigestAmbiguous = errors.New("ambiguous digest string")
|
||||
)
|
||||
|
||||
// Set is used to hold a unique set of digests which
|
||||
// may be easily referenced by easily referenced by a string
|
||||
// representation of the digest as well as short representation.
|
||||
// The uniqueness of the short representation is based on other
|
||||
// digests in the set. If digests are omitted from this set,
|
||||
// collisions in a larger set may not be detected, therefore it
|
||||
// is important to always do short representation lookups on
|
||||
// the complete set of digests. To mitigate collisions, an
|
||||
// appropriately long short code should be used.
|
||||
type Set struct {
|
||||
mutex sync.RWMutex
|
||||
entries digestEntries
|
||||
}
|
||||
|
||||
// NewSet creates an empty set of digests
|
||||
// which may have digests added.
|
||||
func NewSet() *Set {
|
||||
return &Set{
|
||||
entries: digestEntries{},
|
||||
}
|
||||
}
|
||||
|
||||
// checkShortMatch checks whether two digests match as either whole
|
||||
// values or short values. This function does not test equality,
|
||||
// rather whether the second value could match against the first
|
||||
// value.
|
||||
func checkShortMatch(alg Algorithm, hex, shortAlg, shortHex string) bool {
|
||||
if len(hex) == len(shortHex) {
|
||||
if hex != shortHex {
|
||||
return false
|
||||
}
|
||||
if len(shortAlg) > 0 && string(alg) != shortAlg {
|
||||
return false
|
||||
}
|
||||
} else if !strings.HasPrefix(hex, shortHex) {
|
||||
return false
|
||||
} else if len(shortAlg) > 0 && string(alg) != shortAlg {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Lookup looks for a digest matching the given string representation.
|
||||
// If no digests could be found ErrDigestNotFound will be returned
|
||||
// with an empty digest value. If multiple matches are found
|
||||
// ErrDigestAmbiguous will be returned with an empty digest value.
|
||||
func (dst *Set) Lookup(d string) (Digest, error) {
|
||||
dst.mutex.RLock()
|
||||
defer dst.mutex.RUnlock()
|
||||
if len(dst.entries) == 0 {
|
||||
return "", ErrDigestNotFound
|
||||
}
|
||||
var (
|
||||
searchFunc func(int) bool
|
||||
alg Algorithm
|
||||
hex string
|
||||
)
|
||||
dgst, err := ParseDigest(d)
|
||||
if err == ErrDigestInvalidFormat {
|
||||
hex = d
|
||||
searchFunc = func(i int) bool {
|
||||
return dst.entries[i].val >= d
|
||||
}
|
||||
} else {
|
||||
hex = dgst.Hex()
|
||||
alg = dgst.Algorithm()
|
||||
searchFunc = func(i int) bool {
|
||||
if dst.entries[i].val == hex {
|
||||
return dst.entries[i].alg >= alg
|
||||
}
|
||||
return dst.entries[i].val >= hex
|
||||
}
|
||||
}
|
||||
idx := sort.Search(len(dst.entries), searchFunc)
|
||||
if idx == len(dst.entries) || !checkShortMatch(dst.entries[idx].alg, dst.entries[idx].val, string(alg), hex) {
|
||||
return "", ErrDigestNotFound
|
||||
}
|
||||
if dst.entries[idx].alg == alg && dst.entries[idx].val == hex {
|
||||
return dst.entries[idx].digest, nil
|
||||
}
|
||||
if idx+1 < len(dst.entries) && checkShortMatch(dst.entries[idx+1].alg, dst.entries[idx+1].val, string(alg), hex) {
|
||||
return "", ErrDigestAmbiguous
|
||||
}
|
||||
|
||||
return dst.entries[idx].digest, nil
|
||||
}
|
||||
|
||||
// Add adds the given digest to the set. An error will be returned
|
||||
// if the given digest is invalid. If the digest already exists in the
|
||||
// set, this operation will be a no-op.
|
||||
func (dst *Set) Add(d Digest) error {
|
||||
if err := d.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
dst.mutex.Lock()
|
||||
defer dst.mutex.Unlock()
|
||||
entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d}
|
||||
searchFunc := func(i int) bool {
|
||||
if dst.entries[i].val == entry.val {
|
||||
return dst.entries[i].alg >= entry.alg
|
||||
}
|
||||
return dst.entries[i].val >= entry.val
|
||||
}
|
||||
idx := sort.Search(len(dst.entries), searchFunc)
|
||||
if idx == len(dst.entries) {
|
||||
dst.entries = append(dst.entries, entry)
|
||||
return nil
|
||||
} else if dst.entries[idx].digest == d {
|
||||
return nil
|
||||
}
|
||||
|
||||
entries := append(dst.entries, nil)
|
||||
copy(entries[idx+1:], entries[idx:len(entries)-1])
|
||||
entries[idx] = entry
|
||||
dst.entries = entries
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes the given digest from the set. An err will be
|
||||
// returned if the given digest is invalid. If the digest does
|
||||
// not exist in the set, this operation will be a no-op.
|
||||
func (dst *Set) Remove(d Digest) error {
|
||||
if err := d.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
dst.mutex.Lock()
|
||||
defer dst.mutex.Unlock()
|
||||
entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d}
|
||||
searchFunc := func(i int) bool {
|
||||
if dst.entries[i].val == entry.val {
|
||||
return dst.entries[i].alg >= entry.alg
|
||||
}
|
||||
return dst.entries[i].val >= entry.val
|
||||
}
|
||||
idx := sort.Search(len(dst.entries), searchFunc)
|
||||
// Not found if idx is after or value at idx is not digest
|
||||
if idx == len(dst.entries) || dst.entries[idx].digest != d {
|
||||
return nil
|
||||
}
|
||||
|
||||
entries := dst.entries
|
||||
copy(entries[idx:], entries[idx+1:])
|
||||
entries = entries[:len(entries)-1]
|
||||
dst.entries = entries
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// All returns all the digests in the set
|
||||
func (dst *Set) All() []Digest {
|
||||
dst.mutex.RLock()
|
||||
defer dst.mutex.RUnlock()
|
||||
retValues := make([]Digest, len(dst.entries))
|
||||
for i := range dst.entries {
|
||||
retValues[i] = dst.entries[i].digest
|
||||
}
|
||||
|
||||
return retValues
|
||||
}
|
||||
|
||||
// ShortCodeTable returns a map of Digest to unique short codes. The
|
||||
// length represents the minimum value, the maximum length may be the
|
||||
// entire value of digest if uniqueness cannot be achieved without the
|
||||
// full value. This function will attempt to make short codes as short
|
||||
// as possible to be unique.
|
||||
func ShortCodeTable(dst *Set, length int) map[Digest]string {
|
||||
dst.mutex.RLock()
|
||||
defer dst.mutex.RUnlock()
|
||||
m := make(map[Digest]string, len(dst.entries))
|
||||
l := length
|
||||
resetIdx := 0
|
||||
for i := 0; i < len(dst.entries); i++ {
|
||||
var short string
|
||||
extended := true
|
||||
for extended {
|
||||
extended = false
|
||||
if len(dst.entries[i].val) <= l {
|
||||
short = dst.entries[i].digest.String()
|
||||
} else {
|
||||
short = dst.entries[i].val[:l]
|
||||
for j := i + 1; j < len(dst.entries); j++ {
|
||||
if checkShortMatch(dst.entries[j].alg, dst.entries[j].val, "", short) {
|
||||
if j > resetIdx {
|
||||
resetIdx = j
|
||||
}
|
||||
extended = true
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if extended {
|
||||
l++
|
||||
}
|
||||
}
|
||||
}
|
||||
m[dst.entries[i].digest] = short
|
||||
if i >= resetIdx {
|
||||
l = length
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
type digestEntry struct {
|
||||
alg Algorithm
|
||||
val string
|
||||
digest Digest
|
||||
}
|
||||
|
||||
type digestEntries []*digestEntry
|
||||
|
||||
func (d digestEntries) Len() int {
|
||||
return len(d)
|
||||
}
|
||||
|
||||
func (d digestEntries) Less(i, j int) bool {
|
||||
if d[i].val != d[j].val {
|
||||
return d[i].val < d[j].val
|
||||
}
|
||||
return d[i].alg < d[j].alg
|
||||
}
|
||||
|
||||
func (d digestEntries) Swap(i, j int) {
|
||||
d[i], d[j] = d[j], d[i]
|
||||
}
|
44
vendor/github.com/docker/distribution/digest/verifiers.go
generated
vendored
Normal file
44
vendor/github.com/docker/distribution/digest/verifiers.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
package digest
|
||||
|
||||
import (
|
||||
"hash"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Verifier presents a general verification interface to be used with message
|
||||
// digests and other byte stream verifications. Users instantiate a Verifier
|
||||
// from one of the various methods, write the data under test to it then check
|
||||
// the result with the Verified method.
|
||||
type Verifier interface {
|
||||
io.Writer
|
||||
|
||||
// Verified will return true if the content written to Verifier matches
|
||||
// the digest.
|
||||
Verified() bool
|
||||
}
|
||||
|
||||
// NewDigestVerifier returns a verifier that compares the written bytes
|
||||
// against a passed in digest.
|
||||
func NewDigestVerifier(d Digest) (Verifier, error) {
|
||||
if err := d.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return hashVerifier{
|
||||
hash: d.Algorithm().Hash(),
|
||||
digest: d,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type hashVerifier struct {
|
||||
digest Digest
|
||||
hash hash.Hash
|
||||
}
|
||||
|
||||
func (hv hashVerifier) Write(p []byte) (n int, err error) {
|
||||
return hv.hash.Write(p)
|
||||
}
|
||||
|
||||
func (hv hashVerifier) Verified() bool {
|
||||
return hv.digest == NewDigest(hv.digest.Algorithm(), hv.hash)
|
||||
}
|
7
vendor/github.com/docker/distribution/doc.go
generated
vendored
Normal file
7
vendor/github.com/docker/distribution/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
// Package distribution will define the interfaces for the components of
|
||||
// docker distribution. The goal is to allow users to reliably package, ship
|
||||
// and store content related to docker images.
|
||||
//
|
||||
// This is currently a work in progress. More details are available in the
|
||||
// README.md.
|
||||
package distribution
|
115
vendor/github.com/docker/distribution/errors.go
generated
vendored
Normal file
115
vendor/github.com/docker/distribution/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,115 @@
|
|||
package distribution
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/distribution/digest"
|
||||
)
|
||||
|
||||
// ErrAccessDenied is returned when an access to a requested resource is
|
||||
// denied.
|
||||
var ErrAccessDenied = errors.New("access denied")
|
||||
|
||||
// ErrManifestNotModified is returned when a conditional manifest GetByTag
|
||||
// returns nil due to the client indicating it has the latest version
|
||||
var ErrManifestNotModified = errors.New("manifest not modified")
|
||||
|
||||
// ErrUnsupported is returned when an unimplemented or unsupported action is
|
||||
// performed
|
||||
var ErrUnsupported = errors.New("operation unsupported")
|
||||
|
||||
// ErrTagUnknown is returned if the given tag is not known by the tag service
|
||||
type ErrTagUnknown struct {
|
||||
Tag string
|
||||
}
|
||||
|
||||
func (err ErrTagUnknown) Error() string {
|
||||
return fmt.Sprintf("unknown tag=%s", err.Tag)
|
||||
}
|
||||
|
||||
// ErrRepositoryUnknown is returned if the named repository is not known by
|
||||
// the registry.
|
||||
type ErrRepositoryUnknown struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (err ErrRepositoryUnknown) Error() string {
|
||||
return fmt.Sprintf("unknown repository name=%s", err.Name)
|
||||
}
|
||||
|
||||
// ErrRepositoryNameInvalid should be used to denote an invalid repository
|
||||
// name. Reason may set, indicating the cause of invalidity.
|
||||
type ErrRepositoryNameInvalid struct {
|
||||
Name string
|
||||
Reason error
|
||||
}
|
||||
|
||||
func (err ErrRepositoryNameInvalid) Error() string {
|
||||
return fmt.Sprintf("repository name %q invalid: %v", err.Name, err.Reason)
|
||||
}
|
||||
|
||||
// ErrManifestUnknown is returned if the manifest is not known by the
|
||||
// registry.
|
||||
type ErrManifestUnknown struct {
|
||||
Name string
|
||||
Tag string
|
||||
}
|
||||
|
||||
func (err ErrManifestUnknown) Error() string {
|
||||
return fmt.Sprintf("unknown manifest name=%s tag=%s", err.Name, err.Tag)
|
||||
}
|
||||
|
||||
// ErrManifestUnknownRevision is returned when a manifest cannot be found by
|
||||
// revision within a repository.
|
||||
type ErrManifestUnknownRevision struct {
|
||||
Name string
|
||||
Revision digest.Digest
|
||||
}
|
||||
|
||||
func (err ErrManifestUnknownRevision) Error() string {
|
||||
return fmt.Sprintf("unknown manifest name=%s revision=%s", err.Name, err.Revision)
|
||||
}
|
||||
|
||||
// ErrManifestUnverified is returned when the registry is unable to verify
|
||||
// the manifest.
|
||||
type ErrManifestUnverified struct{}
|
||||
|
||||
func (ErrManifestUnverified) Error() string {
|
||||
return fmt.Sprintf("unverified manifest")
|
||||
}
|
||||
|
||||
// ErrManifestVerification provides a type to collect errors encountered
|
||||
// during manifest verification. Currently, it accepts errors of all types,
|
||||
// but it may be narrowed to those involving manifest verification.
|
||||
type ErrManifestVerification []error
|
||||
|
||||
func (errs ErrManifestVerification) Error() string {
|
||||
var parts []string
|
||||
for _, err := range errs {
|
||||
parts = append(parts, err.Error())
|
||||
}
|
||||
|
||||
return fmt.Sprintf("errors verifying manifest: %v", strings.Join(parts, ","))
|
||||
}
|
||||
|
||||
// ErrManifestBlobUnknown returned when a referenced blob cannot be found.
|
||||
type ErrManifestBlobUnknown struct {
|
||||
Digest digest.Digest
|
||||
}
|
||||
|
||||
func (err ErrManifestBlobUnknown) Error() string {
|
||||
return fmt.Sprintf("unknown blob %v on manifest", err.Digest)
|
||||
}
|
||||
|
||||
// ErrManifestNameInvalid should be used to denote an invalid manifest
|
||||
// name. Reason may set, indicating the cause of invalidity.
|
||||
type ErrManifestNameInvalid struct {
|
||||
Name string
|
||||
Reason error
|
||||
}
|
||||
|
||||
func (err ErrManifestNameInvalid) Error() string {
|
||||
return fmt.Sprintf("manifest name %q invalid: %v", err.Name, err.Reason)
|
||||
}
|
117
vendor/github.com/docker/distribution/manifests.go
generated
vendored
Normal file
117
vendor/github.com/docker/distribution/manifests.go
generated
vendored
Normal file
|
@ -0,0 +1,117 @@
|
|||
package distribution
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/docker/distribution/digest"
|
||||
)
|
||||
|
||||
// Manifest represents a registry object specifying a set of
|
||||
// references and an optional target
|
||||
type Manifest interface {
|
||||
// References returns a list of objects which make up this manifest.
|
||||
// The references are strictly ordered from base to head. A reference
|
||||
// is anything which can be represented by a distribution.Descriptor
|
||||
References() []Descriptor
|
||||
|
||||
// Payload provides the serialized format of the manifest, in addition to
|
||||
// the mediatype.
|
||||
Payload() (mediatype string, payload []byte, err error)
|
||||
}
|
||||
|
||||
// ManifestBuilder creates a manifest allowing one to include dependencies.
|
||||
// Instances can be obtained from a version-specific manifest package. Manifest
|
||||
// specific data is passed into the function which creates the builder.
|
||||
type ManifestBuilder interface {
|
||||
// Build creates the manifest from his builder.
|
||||
Build(ctx context.Context) (Manifest, error)
|
||||
|
||||
// References returns a list of objects which have been added to this
|
||||
// builder. The dependencies are returned in the order they were added,
|
||||
// which should be from base to head.
|
||||
References() []Descriptor
|
||||
|
||||
// AppendReference includes the given object in the manifest after any
|
||||
// existing dependencies. If the add fails, such as when adding an
|
||||
// unsupported dependency, an error may be returned.
|
||||
AppendReference(dependency Describable) error
|
||||
}
|
||||
|
||||
// ManifestService describes operations on image manifests.
|
||||
type ManifestService interface {
|
||||
// Exists returns true if the manifest exists.
|
||||
Exists(ctx context.Context, dgst digest.Digest) (bool, error)
|
||||
|
||||
// Get retrieves the manifest specified by the given digest
|
||||
Get(ctx context.Context, dgst digest.Digest, options ...ManifestServiceOption) (Manifest, error)
|
||||
|
||||
// Put creates or updates the given manifest returning the manifest digest
|
||||
Put(ctx context.Context, manifest Manifest, options ...ManifestServiceOption) (digest.Digest, error)
|
||||
|
||||
// Delete removes the manifest specified by the given digest. Deleting
|
||||
// a manifest that doesn't exist will return ErrManifestNotFound
|
||||
Delete(ctx context.Context, dgst digest.Digest) error
|
||||
}
|
||||
|
||||
// ManifestEnumerator enables iterating over manifests
|
||||
type ManifestEnumerator interface {
|
||||
// Enumerate calls ingester for each manifest.
|
||||
Enumerate(ctx context.Context, ingester func(digest.Digest) error) error
|
||||
}
|
||||
|
||||
// Describable is an interface for descriptors
|
||||
type Describable interface {
|
||||
Descriptor() Descriptor
|
||||
}
|
||||
|
||||
// ManifestMediaTypes returns the supported media types for manifests.
|
||||
func ManifestMediaTypes() (mediaTypes []string) {
|
||||
for t := range mappings {
|
||||
if t != "" {
|
||||
mediaTypes = append(mediaTypes, t)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalFunc implements manifest unmarshalling a given MediaType
|
||||
type UnmarshalFunc func([]byte) (Manifest, Descriptor, error)
|
||||
|
||||
var mappings = make(map[string]UnmarshalFunc, 0)
|
||||
|
||||
// UnmarshalManifest looks up manifest unmarshal functions based on
|
||||
// MediaType
|
||||
func UnmarshalManifest(ctHeader string, p []byte) (Manifest, Descriptor, error) {
|
||||
// Need to look up by the actual media type, not the raw contents of
|
||||
// the header. Strip semicolons and anything following them.
|
||||
var mediatype string
|
||||
if ctHeader != "" {
|
||||
var err error
|
||||
mediatype, _, err = mime.ParseMediaType(ctHeader)
|
||||
if err != nil {
|
||||
return nil, Descriptor{}, err
|
||||
}
|
||||
}
|
||||
|
||||
unmarshalFunc, ok := mappings[mediatype]
|
||||
if !ok {
|
||||
unmarshalFunc, ok = mappings[""]
|
||||
if !ok {
|
||||
return nil, Descriptor{}, fmt.Errorf("unsupported manifest mediatype and no default available: %s", mediatype)
|
||||
}
|
||||
}
|
||||
|
||||
return unmarshalFunc(p)
|
||||
}
|
||||
|
||||
// RegisterManifestSchema registers an UnmarshalFunc for a given schema type. This
|
||||
// should be called from specific
|
||||
func RegisterManifestSchema(mediatype string, u UnmarshalFunc) error {
|
||||
if _, ok := mappings[mediatype]; ok {
|
||||
return fmt.Errorf("manifest mediatype registration would overwrite existing: %s", mediatype)
|
||||
}
|
||||
mappings[mediatype] = u
|
||||
return nil
|
||||
}
|
334
vendor/github.com/docker/distribution/reference/reference.go
generated
vendored
Normal file
334
vendor/github.com/docker/distribution/reference/reference.go
generated
vendored
Normal file
|
@ -0,0 +1,334 @@
|
|||
// Package reference provides a general type to represent any way of referencing images within the registry.
|
||||
// Its main purpose is to abstract tags and digests (content-addressable hash).
|
||||
//
|
||||
// Grammar
|
||||
//
|
||||
// reference := name [ ":" tag ] [ "@" digest ]
|
||||
// name := [hostname '/'] component ['/' component]*
|
||||
// hostname := hostcomponent ['.' hostcomponent]* [':' port-number]
|
||||
// hostcomponent := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
|
||||
// port-number := /[0-9]+/
|
||||
// component := alpha-numeric [separator alpha-numeric]*
|
||||
// alpha-numeric := /[a-z0-9]+/
|
||||
// separator := /[_.]|__|[-]*/
|
||||
//
|
||||
// tag := /[\w][\w.-]{0,127}/
|
||||
//
|
||||
// digest := digest-algorithm ":" digest-hex
|
||||
// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]
|
||||
// digest-algorithm-separator := /[+.-_]/
|
||||
// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/
|
||||
// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
|
||||
package reference
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/distribution/digest"
|
||||
)
|
||||
|
||||
const (
|
||||
// NameTotalLengthMax is the maximum total number of characters in a repository name.
|
||||
NameTotalLengthMax = 255
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
|
||||
ErrReferenceInvalidFormat = errors.New("invalid reference format")
|
||||
|
||||
// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
|
||||
ErrTagInvalidFormat = errors.New("invalid tag format")
|
||||
|
||||
// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
|
||||
ErrDigestInvalidFormat = errors.New("invalid digest format")
|
||||
|
||||
// ErrNameEmpty is returned for empty, invalid repository names.
|
||||
ErrNameEmpty = errors.New("repository name must have at least one component")
|
||||
|
||||
// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
|
||||
ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax)
|
||||
)
|
||||
|
||||
// Reference is an opaque object reference identifier that may include
|
||||
// modifiers such as a hostname, name, tag, and digest.
|
||||
type Reference interface {
|
||||
// String returns the full reference
|
||||
String() string
|
||||
}
|
||||
|
||||
// Field provides a wrapper type for resolving correct reference types when
|
||||
// working with encoding.
|
||||
type Field struct {
|
||||
reference Reference
|
||||
}
|
||||
|
||||
// AsField wraps a reference in a Field for encoding.
|
||||
func AsField(reference Reference) Field {
|
||||
return Field{reference}
|
||||
}
|
||||
|
||||
// Reference unwraps the reference type from the field to
|
||||
// return the Reference object. This object should be
|
||||
// of the appropriate type to further check for different
|
||||
// reference types.
|
||||
func (f Field) Reference() Reference {
|
||||
return f.reference
|
||||
}
|
||||
|
||||
// MarshalText serializes the field to byte text which
|
||||
// is the string of the reference.
|
||||
func (f Field) MarshalText() (p []byte, err error) {
|
||||
return []byte(f.reference.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText parses text bytes by invoking the
|
||||
// reference parser to ensure the appropriately
|
||||
// typed reference object is wrapped by field.
|
||||
func (f *Field) UnmarshalText(p []byte) error {
|
||||
r, err := Parse(string(p))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.reference = r
|
||||
return nil
|
||||
}
|
||||
|
||||
// Named is an object with a full name
|
||||
type Named interface {
|
||||
Reference
|
||||
Name() string
|
||||
}
|
||||
|
||||
// Tagged is an object which has a tag
|
||||
type Tagged interface {
|
||||
Reference
|
||||
Tag() string
|
||||
}
|
||||
|
||||
// NamedTagged is an object including a name and tag.
|
||||
type NamedTagged interface {
|
||||
Named
|
||||
Tag() string
|
||||
}
|
||||
|
||||
// Digested is an object which has a digest
|
||||
// in which it can be referenced by
|
||||
type Digested interface {
|
||||
Reference
|
||||
Digest() digest.Digest
|
||||
}
|
||||
|
||||
// Canonical reference is an object with a fully unique
|
||||
// name including a name with hostname and digest
|
||||
type Canonical interface {
|
||||
Named
|
||||
Digest() digest.Digest
|
||||
}
|
||||
|
||||
// SplitHostname splits a named reference into a
|
||||
// hostname and name string. If no valid hostname is
|
||||
// found, the hostname is empty and the full value
|
||||
// is returned as name
|
||||
func SplitHostname(named Named) (string, string) {
|
||||
name := named.Name()
|
||||
match := anchoredNameRegexp.FindStringSubmatch(name)
|
||||
if match == nil || len(match) != 3 {
|
||||
return "", name
|
||||
}
|
||||
return match[1], match[2]
|
||||
}
|
||||
|
||||
// Parse parses s and returns a syntactically valid Reference.
|
||||
// If an error was encountered it is returned, along with a nil Reference.
|
||||
// NOTE: Parse will not handle short digests.
|
||||
func Parse(s string) (Reference, error) {
|
||||
matches := ReferenceRegexp.FindStringSubmatch(s)
|
||||
if matches == nil {
|
||||
if s == "" {
|
||||
return nil, ErrNameEmpty
|
||||
}
|
||||
// TODO(dmcgowan): Provide more specific and helpful error
|
||||
return nil, ErrReferenceInvalidFormat
|
||||
}
|
||||
|
||||
if len(matches[1]) > NameTotalLengthMax {
|
||||
return nil, ErrNameTooLong
|
||||
}
|
||||
|
||||
ref := reference{
|
||||
name: matches[1],
|
||||
tag: matches[2],
|
||||
}
|
||||
if matches[3] != "" {
|
||||
var err error
|
||||
ref.digest, err = digest.ParseDigest(matches[3])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
r := getBestReferenceType(ref)
|
||||
if r == nil {
|
||||
return nil, ErrNameEmpty
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// ParseNamed parses s and returns a syntactically valid reference implementing
|
||||
// the Named interface. The reference must have a name, otherwise an error is
|
||||
// returned.
|
||||
// If an error was encountered it is returned, along with a nil Reference.
|
||||
// NOTE: ParseNamed will not handle short digests.
|
||||
func ParseNamed(s string) (Named, error) {
|
||||
ref, err := Parse(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
named, isNamed := ref.(Named)
|
||||
if !isNamed {
|
||||
return nil, fmt.Errorf("reference %s has no name", ref.String())
|
||||
}
|
||||
return named, nil
|
||||
}
|
||||
|
||||
// WithName returns a named object representing the given string. If the input
|
||||
// is invalid ErrReferenceInvalidFormat will be returned.
|
||||
func WithName(name string) (Named, error) {
|
||||
if len(name) > NameTotalLengthMax {
|
||||
return nil, ErrNameTooLong
|
||||
}
|
||||
if !anchoredNameRegexp.MatchString(name) {
|
||||
return nil, ErrReferenceInvalidFormat
|
||||
}
|
||||
return repository(name), nil
|
||||
}
|
||||
|
||||
// WithTag combines the name from "name" and the tag from "tag" to form a
|
||||
// reference incorporating both the name and the tag.
|
||||
func WithTag(name Named, tag string) (NamedTagged, error) {
|
||||
if !anchoredTagRegexp.MatchString(tag) {
|
||||
return nil, ErrTagInvalidFormat
|
||||
}
|
||||
return taggedReference{
|
||||
name: name.Name(),
|
||||
tag: tag,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WithDigest combines the name from "name" and the digest from "digest" to form
|
||||
// a reference incorporating both the name and the digest.
|
||||
func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
|
||||
if !anchoredDigestRegexp.MatchString(digest.String()) {
|
||||
return nil, ErrDigestInvalidFormat
|
||||
}
|
||||
return canonicalReference{
|
||||
name: name.Name(),
|
||||
digest: digest,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getBestReferenceType(ref reference) Reference {
|
||||
if ref.name == "" {
|
||||
// Allow digest only references
|
||||
if ref.digest != "" {
|
||||
return digestReference(ref.digest)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if ref.tag == "" {
|
||||
if ref.digest != "" {
|
||||
return canonicalReference{
|
||||
name: ref.name,
|
||||
digest: ref.digest,
|
||||
}
|
||||
}
|
||||
return repository(ref.name)
|
||||
}
|
||||
if ref.digest == "" {
|
||||
return taggedReference{
|
||||
name: ref.name,
|
||||
tag: ref.tag,
|
||||
}
|
||||
}
|
||||
|
||||
return ref
|
||||
}
|
||||
|
||||
type reference struct {
|
||||
name string
|
||||
tag string
|
||||
digest digest.Digest
|
||||
}
|
||||
|
||||
func (r reference) String() string {
|
||||
return r.name + ":" + r.tag + "@" + r.digest.String()
|
||||
}
|
||||
|
||||
func (r reference) Name() string {
|
||||
return r.name
|
||||
}
|
||||
|
||||
func (r reference) Tag() string {
|
||||
return r.tag
|
||||
}
|
||||
|
||||
func (r reference) Digest() digest.Digest {
|
||||
return r.digest
|
||||
}
|
||||
|
||||
type repository string
|
||||
|
||||
func (r repository) String() string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
func (r repository) Name() string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
type digestReference digest.Digest
|
||||
|
||||
func (d digestReference) String() string {
|
||||
return d.String()
|
||||
}
|
||||
|
||||
func (d digestReference) Digest() digest.Digest {
|
||||
return digest.Digest(d)
|
||||
}
|
||||
|
||||
type taggedReference struct {
|
||||
name string
|
||||
tag string
|
||||
}
|
||||
|
||||
func (t taggedReference) String() string {
|
||||
return t.name + ":" + t.tag
|
||||
}
|
||||
|
||||
func (t taggedReference) Name() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
func (t taggedReference) Tag() string {
|
||||
return t.tag
|
||||
}
|
||||
|
||||
type canonicalReference struct {
|
||||
name string
|
||||
digest digest.Digest
|
||||
}
|
||||
|
||||
func (c canonicalReference) String() string {
|
||||
return c.name + "@" + c.digest.String()
|
||||
}
|
||||
|
||||
func (c canonicalReference) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
func (c canonicalReference) Digest() digest.Digest {
|
||||
return c.digest
|
||||
}
|
124
vendor/github.com/docker/distribution/reference/regexp.go
generated
vendored
Normal file
124
vendor/github.com/docker/distribution/reference/regexp.go
generated
vendored
Normal file
|
@ -0,0 +1,124 @@
|
|||
package reference
|
||||
|
||||
import "regexp"
|
||||
|
||||
var (
|
||||
// alphaNumericRegexp defines the alpha numeric atom, typically a
|
||||
// component of names. This only allows lower case characters and digits.
|
||||
alphaNumericRegexp = match(`[a-z0-9]+`)
|
||||
|
||||
// separatorRegexp defines the separators allowed to be embedded in name
|
||||
// components. This allow one period, one or two underscore and multiple
|
||||
// dashes.
|
||||
separatorRegexp = match(`(?:[._]|__|[-]*)`)
|
||||
|
||||
// nameComponentRegexp restricts registry path component names to start
|
||||
// with at least one letter or number, with following parts able to be
|
||||
// separated by one period, one or two underscore and multiple dashes.
|
||||
nameComponentRegexp = expression(
|
||||
alphaNumericRegexp,
|
||||
optional(repeated(separatorRegexp, alphaNumericRegexp)))
|
||||
|
||||
// hostnameComponentRegexp restricts the registry hostname component of a
|
||||
// repository name to start with a component as defined by hostnameRegexp
|
||||
// and followed by an optional port.
|
||||
hostnameComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
|
||||
|
||||
// hostnameRegexp defines the structure of potential hostname components
|
||||
// that may be part of image names. This is purposely a subset of what is
|
||||
// allowed by DNS to ensure backwards compatibility with Docker image
|
||||
// names.
|
||||
hostnameRegexp = expression(
|
||||
hostnameComponentRegexp,
|
||||
optional(repeated(literal(`.`), hostnameComponentRegexp)),
|
||||
optional(literal(`:`), match(`[0-9]+`)))
|
||||
|
||||
// TagRegexp matches valid tag names. From docker/docker:graph/tags.go.
|
||||
TagRegexp = match(`[\w][\w.-]{0,127}`)
|
||||
|
||||
// anchoredTagRegexp matches valid tag names, anchored at the start and
|
||||
// end of the matched string.
|
||||
anchoredTagRegexp = anchored(TagRegexp)
|
||||
|
||||
// DigestRegexp matches valid digests.
|
||||
DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`)
|
||||
|
||||
// anchoredDigestRegexp matches valid digests, anchored at the start and
|
||||
// end of the matched string.
|
||||
anchoredDigestRegexp = anchored(DigestRegexp)
|
||||
|
||||
// NameRegexp is the format for the name component of references. The
|
||||
// regexp has capturing groups for the hostname and name part omitting
|
||||
// the separating forward slash from either.
|
||||
NameRegexp = expression(
|
||||
optional(hostnameRegexp, literal(`/`)),
|
||||
nameComponentRegexp,
|
||||
optional(repeated(literal(`/`), nameComponentRegexp)))
|
||||
|
||||
// anchoredNameRegexp is used to parse a name value, capturing the
|
||||
// hostname and trailing components.
|
||||
anchoredNameRegexp = anchored(
|
||||
optional(capture(hostnameRegexp), literal(`/`)),
|
||||
capture(nameComponentRegexp,
|
||||
optional(repeated(literal(`/`), nameComponentRegexp))))
|
||||
|
||||
// ReferenceRegexp is the full supported format of a reference. The regexp
|
||||
// is anchored and has capturing groups for name, tag, and digest
|
||||
// components.
|
||||
ReferenceRegexp = anchored(capture(NameRegexp),
|
||||
optional(literal(":"), capture(TagRegexp)),
|
||||
optional(literal("@"), capture(DigestRegexp)))
|
||||
)
|
||||
|
||||
// match compiles the string to a regular expression.
|
||||
var match = regexp.MustCompile
|
||||
|
||||
// literal compiles s into a literal regular expression, escaping any regexp
|
||||
// reserved characters.
|
||||
func literal(s string) *regexp.Regexp {
|
||||
re := match(regexp.QuoteMeta(s))
|
||||
|
||||
if _, complete := re.LiteralPrefix(); !complete {
|
||||
panic("must be a literal")
|
||||
}
|
||||
|
||||
return re
|
||||
}
|
||||
|
||||
// expression defines a full expression, where each regular expression must
|
||||
// follow the previous.
|
||||
func expression(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
var s string
|
||||
for _, re := range res {
|
||||
s += re.String()
|
||||
}
|
||||
|
||||
return match(s)
|
||||
}
|
||||
|
||||
// optional wraps the expression in a non-capturing group and makes the
|
||||
// production optional.
|
||||
func optional(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
return match(group(expression(res...)).String() + `?`)
|
||||
}
|
||||
|
||||
// repeated wraps the regexp in a non-capturing group to get one or more
|
||||
// matches.
|
||||
func repeated(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
return match(group(expression(res...)).String() + `+`)
|
||||
}
|
||||
|
||||
// group wraps the regexp in a non-capturing group.
|
||||
func group(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
return match(`(?:` + expression(res...).String() + `)`)
|
||||
}
|
||||
|
||||
// capture wraps the expression in a capturing group.
|
||||
func capture(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
return match(`(` + expression(res...).String() + `)`)
|
||||
}
|
||||
|
||||
// anchored anchors the regular expression by adding start and end delimiters.
|
||||
func anchored(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
return match(`^` + expression(res...).String() + `$`)
|
||||
}
|
97
vendor/github.com/docker/distribution/registry.go
generated
vendored
Normal file
97
vendor/github.com/docker/distribution/registry.go
generated
vendored
Normal file
|
@ -0,0 +1,97 @@
|
|||
package distribution
|
||||
|
||||
import (
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/docker/distribution/reference"
|
||||
)
|
||||
|
||||
// Scope defines the set of items that match a namespace.
|
||||
type Scope interface {
|
||||
// Contains returns true if the name belongs to the namespace.
|
||||
Contains(name string) bool
|
||||
}
|
||||
|
||||
type fullScope struct{}
|
||||
|
||||
func (f fullScope) Contains(string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GlobalScope represents the full namespace scope which contains
|
||||
// all other scopes.
|
||||
var GlobalScope = Scope(fullScope{})
|
||||
|
||||
// Namespace represents a collection of repositories, addressable by name.
|
||||
// Generally, a namespace is backed by a set of one or more services,
|
||||
// providing facilities such as registry access, trust, and indexing.
|
||||
type Namespace interface {
|
||||
// Scope describes the names that can be used with this Namespace. The
|
||||
// global namespace will have a scope that matches all names. The scope
|
||||
// effectively provides an identity for the namespace.
|
||||
Scope() Scope
|
||||
|
||||
// Repository should return a reference to the named repository. The
|
||||
// registry may or may not have the repository but should always return a
|
||||
// reference.
|
||||
Repository(ctx context.Context, name reference.Named) (Repository, error)
|
||||
|
||||
// Repositories fills 'repos' with a lexigraphically sorted catalog of repositories
|
||||
// up to the size of 'repos' and returns the value 'n' for the number of entries
|
||||
// which were filled. 'last' contains an offset in the catalog, and 'err' will be
|
||||
// set to io.EOF if there are no more entries to obtain.
|
||||
Repositories(ctx context.Context, repos []string, last string) (n int, err error)
|
||||
|
||||
// Blobs returns a blob enumerator to access all blobs
|
||||
Blobs() BlobEnumerator
|
||||
|
||||
// BlobStatter returns a BlobStatter to control
|
||||
BlobStatter() BlobStatter
|
||||
}
|
||||
|
||||
// RepositoryEnumerator describes an operation to enumerate repositories
|
||||
type RepositoryEnumerator interface {
|
||||
Enumerate(ctx context.Context, ingester func(string) error) error
|
||||
}
|
||||
|
||||
// ManifestServiceOption is a function argument for Manifest Service methods
|
||||
type ManifestServiceOption interface {
|
||||
Apply(ManifestService) error
|
||||
}
|
||||
|
||||
// WithTag allows a tag to be passed into Put
|
||||
func WithTag(tag string) ManifestServiceOption {
|
||||
return WithTagOption{tag}
|
||||
}
|
||||
|
||||
// WithTagOption holds a tag
|
||||
type WithTagOption struct{ Tag string }
|
||||
|
||||
// Apply conforms to the ManifestServiceOption interface
|
||||
func (o WithTagOption) Apply(m ManifestService) error {
|
||||
// no implementation
|
||||
return nil
|
||||
}
|
||||
|
||||
// Repository is a named collection of manifests and layers.
|
||||
type Repository interface {
|
||||
// Named returns the name of the repository.
|
||||
Named() reference.Named
|
||||
|
||||
// Manifests returns a reference to this repository's manifest service.
|
||||
// with the supplied options applied.
|
||||
Manifests(ctx context.Context, options ...ManifestServiceOption) (ManifestService, error)
|
||||
|
||||
// Blobs returns a reference to this repository's blob service.
|
||||
Blobs(ctx context.Context) BlobStore
|
||||
|
||||
// TODO(stevvooe): The above BlobStore return can probably be relaxed to
|
||||
// be a BlobService for use with clients. This will allow such
|
||||
// implementations to avoid implementing ServeBlob.
|
||||
|
||||
// Tags returns a reference to this repositories tag service
|
||||
Tags(ctx context.Context) TagService
|
||||
}
|
||||
|
||||
// TODO(stevvooe): Must add close methods to all these. May want to change the
|
||||
// way instances are created to better reflect internal dependency
|
||||
// relationships.
|
27
vendor/github.com/docker/distribution/tags.go
generated
vendored
Normal file
27
vendor/github.com/docker/distribution/tags.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
package distribution
|
||||
|
||||
import (
|
||||
"github.com/docker/distribution/context"
|
||||
)
|
||||
|
||||
// TagService provides access to information about tagged objects.
|
||||
type TagService interface {
|
||||
// Get retrieves the descriptor identified by the tag. Some
|
||||
// implementations may differentiate between "trusted" tags and
|
||||
// "untrusted" tags. If a tag is "untrusted", the mapping will be returned
|
||||
// as an ErrTagUntrusted error, with the target descriptor.
|
||||
Get(ctx context.Context, tag string) (Descriptor, error)
|
||||
|
||||
// Tag associates the tag with the provided descriptor, updating the
|
||||
// current association, if needed.
|
||||
Tag(ctx context.Context, tag string, desc Descriptor) error
|
||||
|
||||
// Untag removes the given tag association
|
||||
Untag(ctx context.Context, tag string) error
|
||||
|
||||
// All returns the set of tags managed by this tag service
|
||||
All(ctx context.Context) ([]string, error)
|
||||
|
||||
// Lookup returns the set of tags referencing the given digest.
|
||||
Lookup(ctx context.Context, digest Descriptor) ([]string, error)
|
||||
}
|
126
vendor/github.com/docker/distribution/uuid/uuid.go
generated
vendored
Normal file
126
vendor/github.com/docker/distribution/uuid/uuid.go
generated
vendored
Normal file
|
@ -0,0 +1,126 @@
|
|||
// Package uuid provides simple UUID generation. Only version 4 style UUIDs
|
||||
// can be generated.
|
||||
//
|
||||
// Please see http://tools.ietf.org/html/rfc4122 for details on UUIDs.
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// Bits is the number of bits in a UUID
|
||||
Bits = 128
|
||||
|
||||
// Size is the number of bytes in a UUID
|
||||
Size = Bits / 8
|
||||
|
||||
format = "%08x-%04x-%04x-%04x-%012x"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUUIDInvalid indicates a parsed string is not a valid uuid.
|
||||
ErrUUIDInvalid = fmt.Errorf("invalid uuid")
|
||||
|
||||
// Loggerf can be used to override the default logging destination. Such
|
||||
// log messages in this library should be logged at warning or higher.
|
||||
Loggerf = func(format string, args ...interface{}) {}
|
||||
)
|
||||
|
||||
// UUID represents a UUID value. UUIDs can be compared and set to other values
|
||||
// and accessed by byte.
|
||||
type UUID [Size]byte
|
||||
|
||||
// Generate creates a new, version 4 uuid.
|
||||
func Generate() (u UUID) {
|
||||
const (
|
||||
// ensures we backoff for less than 450ms total. Use the following to
|
||||
// select new value, in units of 10ms:
|
||||
// n*(n+1)/2 = d -> n^2 + n - 2d -> n = (sqrt(8d + 1) - 1)/2
|
||||
maxretries = 9
|
||||
backoff = time.Millisecond * 10
|
||||
)
|
||||
|
||||
var (
|
||||
totalBackoff time.Duration
|
||||
count int
|
||||
retries int
|
||||
)
|
||||
|
||||
for {
|
||||
// This should never block but the read may fail. Because of this,
|
||||
// we just try to read the random number generator until we get
|
||||
// something. This is a very rare condition but may happen.
|
||||
b := time.Duration(retries) * backoff
|
||||
time.Sleep(b)
|
||||
totalBackoff += b
|
||||
|
||||
n, err := io.ReadFull(rand.Reader, u[count:])
|
||||
if err != nil {
|
||||
if retryOnError(err) && retries < maxretries {
|
||||
count += n
|
||||
retries++
|
||||
Loggerf("error generating version 4 uuid, retrying: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Any other errors represent a system problem. What did someone
|
||||
// do to /dev/urandom?
|
||||
panic(fmt.Errorf("error reading random number generator, retried for %v: %v", totalBackoff.String(), err))
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
u[6] = (u[6] & 0x0f) | 0x40 // set version byte
|
||||
u[8] = (u[8] & 0x3f) | 0x80 // set high order byte 0b10{8,9,a,b}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
// Parse attempts to extract a uuid from the string or returns an error.
|
||||
func Parse(s string) (u UUID, err error) {
|
||||
if len(s) != 36 {
|
||||
return UUID{}, ErrUUIDInvalid
|
||||
}
|
||||
|
||||
// create stack addresses for each section of the uuid.
|
||||
p := make([][]byte, 5)
|
||||
|
||||
if _, err := fmt.Sscanf(s, format, &p[0], &p[1], &p[2], &p[3], &p[4]); err != nil {
|
||||
return u, err
|
||||
}
|
||||
|
||||
copy(u[0:4], p[0])
|
||||
copy(u[4:6], p[1])
|
||||
copy(u[6:8], p[2])
|
||||
copy(u[8:10], p[3])
|
||||
copy(u[10:16], p[4])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (u UUID) String() string {
|
||||
return fmt.Sprintf(format, u[:4], u[4:6], u[6:8], u[8:10], u[10:])
|
||||
}
|
||||
|
||||
// retryOnError tries to detect whether or not retrying would be fruitful.
|
||||
func retryOnError(err error) bool {
|
||||
switch err := err.(type) {
|
||||
case *os.PathError:
|
||||
return retryOnError(err.Err) // unpack the target error
|
||||
case syscall.Errno:
|
||||
if err == syscall.EPERM {
|
||||
// EPERM represents an entropy pool exhaustion, a condition under
|
||||
// which we backoff and retry.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
191
vendor/github.com/docker/docker/LICENSE
generated
vendored
Normal file
191
vendor/github.com/docker/docker/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,191 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2013-2016 Docker, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://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.
|
19
vendor/github.com/docker/docker/NOTICE
generated
vendored
Normal file
19
vendor/github.com/docker/docker/NOTICE
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
Docker
|
||||
Copyright 2012-2016 Docker, Inc.
|
||||
|
||||
This product includes software developed at Docker, Inc. (https://www.docker.com).
|
||||
|
||||
This product contains software (https://github.com/kr/pty) developed
|
||||
by Keith Rarick, licensed under the MIT License.
|
||||
|
||||
The following is courtesy of our legal counsel:
|
||||
|
||||
|
||||
Use and transfer of Docker may be subject to certain restrictions by the
|
||||
United States and other governments.
|
||||
It is your responsibility to ensure that your use and/or transfer does not
|
||||
violate applicable laws.
|
||||
|
||||
For more information, please see https://www.bis.doc.gov
|
||||
|
||||
See also https://www.apache.org/dev/crypto.html and/or seek legal counsel.
|
67
vendor/github.com/docker/docker/daemon/graphdriver/counter.go
generated
vendored
Normal file
67
vendor/github.com/docker/docker/daemon/graphdriver/counter.go
generated
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
package graphdriver
|
||||
|
||||
import "sync"
|
||||
|
||||
type minfo struct {
|
||||
check bool
|
||||
count int
|
||||
}
|
||||
|
||||
// RefCounter is a generic counter for use by graphdriver Get/Put calls
|
||||
type RefCounter struct {
|
||||
counts map[string]*minfo
|
||||
mu sync.Mutex
|
||||
checker Checker
|
||||
}
|
||||
|
||||
// NewRefCounter returns a new RefCounter
|
||||
func NewRefCounter(c Checker) *RefCounter {
|
||||
return &RefCounter{
|
||||
checker: c,
|
||||
counts: make(map[string]*minfo),
|
||||
}
|
||||
}
|
||||
|
||||
// Increment increaes the ref count for the given id and returns the current count
|
||||
func (c *RefCounter) Increment(path string) int {
|
||||
c.mu.Lock()
|
||||
m := c.counts[path]
|
||||
if m == nil {
|
||||
m = &minfo{}
|
||||
c.counts[path] = m
|
||||
}
|
||||
// if we are checking this path for the first time check to make sure
|
||||
// if it was already mounted on the system and make sure we have a correct ref
|
||||
// count if it is mounted as it is in use.
|
||||
if !m.check {
|
||||
m.check = true
|
||||
if c.checker.IsMounted(path) {
|
||||
m.count++
|
||||
}
|
||||
}
|
||||
m.count++
|
||||
c.mu.Unlock()
|
||||
return m.count
|
||||
}
|
||||
|
||||
// Decrement decreases the ref count for the given id and returns the current count
|
||||
func (c *RefCounter) Decrement(path string) int {
|
||||
c.mu.Lock()
|
||||
m := c.counts[path]
|
||||
if m == nil {
|
||||
m = &minfo{}
|
||||
c.counts[path] = m
|
||||
}
|
||||
// if we are checking this path for the first time check to make sure
|
||||
// if it was already mounted on the system and make sure we have a correct ref
|
||||
// count if it is mounted as it is in use.
|
||||
if !m.check {
|
||||
m.check = true
|
||||
if c.checker.IsMounted(path) {
|
||||
m.count++
|
||||
}
|
||||
}
|
||||
m.count--
|
||||
c.mu.Unlock()
|
||||
return m.count
|
||||
}
|
243
vendor/github.com/docker/docker/daemon/graphdriver/driver.go
generated
vendored
Normal file
243
vendor/github.com/docker/docker/daemon/graphdriver/driver.go
generated
vendored
Normal file
|
@ -0,0 +1,243 @@
|
|||
package graphdriver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
)
|
||||
|
||||
// FsMagic unsigned id of the filesystem in use.
|
||||
type FsMagic uint32
|
||||
|
||||
const (
|
||||
// FsMagicUnsupported is a predefined constant value other than a valid filesystem id.
|
||||
FsMagicUnsupported = FsMagic(0x00000000)
|
||||
)
|
||||
|
||||
var (
|
||||
// All registered drivers
|
||||
drivers map[string]InitFunc
|
||||
|
||||
// ErrNotSupported returned when driver is not supported.
|
||||
ErrNotSupported = errors.New("driver not supported")
|
||||
// ErrPrerequisites retuned when driver does not meet prerequisites.
|
||||
ErrPrerequisites = errors.New("prerequisites for driver not satisfied (wrong filesystem?)")
|
||||
// ErrIncompatibleFS returned when file system is not supported.
|
||||
ErrIncompatibleFS = fmt.Errorf("backing file system is unsupported for this graph driver")
|
||||
)
|
||||
|
||||
// InitFunc initializes the storage driver.
|
||||
type InitFunc func(root string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error)
|
||||
|
||||
// ProtoDriver defines the basic capabilities of a driver.
|
||||
// This interface exists solely to be a minimum set of methods
|
||||
// for client code which choose not to implement the entire Driver
|
||||
// interface and use the NaiveDiffDriver wrapper constructor.
|
||||
//
|
||||
// Use of ProtoDriver directly by client code is not recommended.
|
||||
type ProtoDriver interface {
|
||||
// String returns a string representation of this driver.
|
||||
String() string
|
||||
// CreateReadWrite creates a new, empty filesystem layer that is ready
|
||||
// to be used as the storage for a container.
|
||||
CreateReadWrite(id, parent, mountLabel string, storageOpt map[string]string) error
|
||||
// Create creates a new, empty, filesystem layer with the
|
||||
// specified id and parent and mountLabel. Parent and mountLabel may be "".
|
||||
Create(id, parent, mountLabel string, storageOpt map[string]string) error
|
||||
// Remove attempts to remove the filesystem layer with this id.
|
||||
Remove(id string) error
|
||||
// Get returns the mountpoint for the layered filesystem referred
|
||||
// to by this id. You can optionally specify a mountLabel or "".
|
||||
// Returns the absolute path to the mounted layered filesystem.
|
||||
Get(id, mountLabel string) (dir string, err error)
|
||||
// Put releases the system resources for the specified id,
|
||||
// e.g, unmounting layered filesystem.
|
||||
Put(id string) error
|
||||
// Exists returns whether a filesystem layer with the specified
|
||||
// ID exists on this driver.
|
||||
Exists(id string) bool
|
||||
// Status returns a set of key-value pairs which give low
|
||||
// level diagnostic status about this driver.
|
||||
Status() [][2]string
|
||||
// Returns a set of key-value pairs which give low level information
|
||||
// about the image/container driver is managing.
|
||||
GetMetadata(id string) (map[string]string, error)
|
||||
// Cleanup performs necessary tasks to release resources
|
||||
// held by the driver, e.g., unmounting all layered filesystems
|
||||
// known to this driver.
|
||||
Cleanup() error
|
||||
}
|
||||
|
||||
// Driver is the interface for layered/snapshot file system drivers.
|
||||
type Driver interface {
|
||||
ProtoDriver
|
||||
// Diff produces an archive of the changes between the specified
|
||||
// layer and its parent layer which may be "".
|
||||
Diff(id, parent string) (archive.Archive, error)
|
||||
// Changes produces a list of changes between the specified layer
|
||||
// and its parent layer. If parent is "", then all changes will be ADD changes.
|
||||
Changes(id, parent string) ([]archive.Change, error)
|
||||
// ApplyDiff extracts the changeset from the given diff into the
|
||||
// layer with the specified id and parent, returning the size of the
|
||||
// new layer in bytes.
|
||||
// The archive.Reader must be an uncompressed stream.
|
||||
ApplyDiff(id, parent string, diff archive.Reader) (size int64, err error)
|
||||
// DiffSize calculates the changes between the specified id
|
||||
// and its parent and returns the size in bytes of the changes
|
||||
// relative to its base filesystem directory.
|
||||
DiffSize(id, parent string) (size int64, err error)
|
||||
}
|
||||
|
||||
// DiffGetterDriver is the interface for layered file system drivers that
|
||||
// provide a specialized function for getting file contents for tar-split.
|
||||
type DiffGetterDriver interface {
|
||||
Driver
|
||||
// DiffGetter returns an interface to efficiently retrieve the contents
|
||||
// of files in a layer.
|
||||
DiffGetter(id string) (FileGetCloser, error)
|
||||
}
|
||||
|
||||
// FileGetCloser extends the storage.FileGetter interface with a Close method
|
||||
// for cleaning up.
|
||||
type FileGetCloser interface {
|
||||
storage.FileGetter
|
||||
// Close cleans up any resources associated with the FileGetCloser.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Checker makes checks on specified filesystems.
|
||||
type Checker interface {
|
||||
// IsMounted returns true if the provided path is mounted for the specific checker
|
||||
IsMounted(path string) bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
drivers = make(map[string]InitFunc)
|
||||
}
|
||||
|
||||
// Register registers an InitFunc for the driver.
|
||||
func Register(name string, initFunc InitFunc) error {
|
||||
if _, exists := drivers[name]; exists {
|
||||
return fmt.Errorf("Name already registered %s", name)
|
||||
}
|
||||
drivers[name] = initFunc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDriver initializes and returns the registered driver
|
||||
func GetDriver(name, home string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error) {
|
||||
if initFunc, exists := drivers[name]; exists {
|
||||
return initFunc(filepath.Join(home, name), options, uidMaps, gidMaps)
|
||||
}
|
||||
if pluginDriver, err := lookupPlugin(name, home, options); err == nil {
|
||||
return pluginDriver, nil
|
||||
}
|
||||
logrus.Errorf("Failed to GetDriver graph %s %s", name, home)
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
// getBuiltinDriver initializes and returns the registered driver, but does not try to load from plugins
|
||||
func getBuiltinDriver(name, home string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error) {
|
||||
if initFunc, exists := drivers[name]; exists {
|
||||
return initFunc(filepath.Join(home, name), options, uidMaps, gidMaps)
|
||||
}
|
||||
logrus.Errorf("Failed to built-in GetDriver graph %s %s", name, home)
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
// New creates the driver and initializes it at the specified root.
|
||||
func New(root string, name string, options []string, uidMaps, gidMaps []idtools.IDMap) (Driver, error) {
|
||||
if name != "" {
|
||||
logrus.Debugf("[graphdriver] trying provided driver %q", name) // so the logs show specified driver
|
||||
return GetDriver(name, root, options, uidMaps, gidMaps)
|
||||
}
|
||||
|
||||
// Guess for prior driver
|
||||
driversMap := scanPriorDrivers(root)
|
||||
for _, name := range priority {
|
||||
if name == "vfs" {
|
||||
// don't use vfs even if there is state present.
|
||||
continue
|
||||
}
|
||||
if _, prior := driversMap[name]; prior {
|
||||
// of the state found from prior drivers, check in order of our priority
|
||||
// which we would prefer
|
||||
driver, err := getBuiltinDriver(name, root, options, uidMaps, gidMaps)
|
||||
if err != nil {
|
||||
// unlike below, we will return error here, because there is prior
|
||||
// state, and now it is no longer supported/prereq/compatible, so
|
||||
// something changed and needs attention. Otherwise the daemon's
|
||||
// images would just "disappear".
|
||||
logrus.Errorf("[graphdriver] prior storage driver %q failed: %s", name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// abort starting when there are other prior configured drivers
|
||||
// to ensure the user explicitly selects the driver to load
|
||||
if len(driversMap)-1 > 0 {
|
||||
var driversSlice []string
|
||||
for name := range driversMap {
|
||||
driversSlice = append(driversSlice, name)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%q contains several valid graphdrivers: %s; Please cleanup or explicitly choose storage driver (-s <DRIVER>)", root, strings.Join(driversSlice, ", "))
|
||||
}
|
||||
|
||||
logrus.Infof("[graphdriver] using prior storage driver %q", name)
|
||||
return driver, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check for priority drivers first
|
||||
for _, name := range priority {
|
||||
driver, err := getBuiltinDriver(name, root, options, uidMaps, gidMaps)
|
||||
if err != nil {
|
||||
if isDriverNotSupported(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
// Check all registered drivers if no priority driver is found
|
||||
for name, initFunc := range drivers {
|
||||
driver, err := initFunc(filepath.Join(root, name), options, uidMaps, gidMaps)
|
||||
if err != nil {
|
||||
if isDriverNotSupported(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
return nil, fmt.Errorf("No supported storage backend found")
|
||||
}
|
||||
|
||||
// isDriverNotSupported returns true if the error initializing
|
||||
// the graph driver is a non-supported error.
|
||||
func isDriverNotSupported(err error) bool {
|
||||
return err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS
|
||||
}
|
||||
|
||||
// scanPriorDrivers returns an un-ordered scan of directories of prior storage drivers
|
||||
func scanPriorDrivers(root string) map[string]bool {
|
||||
driversMap := make(map[string]bool)
|
||||
|
||||
for driver := range drivers {
|
||||
p := filepath.Join(root, driver)
|
||||
if _, err := os.Stat(p); err == nil && driver != "vfs" {
|
||||
driversMap[driver] = true
|
||||
}
|
||||
}
|
||||
return driversMap
|
||||
}
|
19
vendor/github.com/docker/docker/daemon/graphdriver/driver_freebsd.go
generated
vendored
Normal file
19
vendor/github.com/docker/docker/daemon/graphdriver/driver_freebsd.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
package graphdriver
|
||||
|
||||
import "syscall"
|
||||
|
||||
var (
|
||||
// Slice of drivers that should be used in an order
|
||||
priority = []string{
|
||||
"zfs",
|
||||
}
|
||||
)
|
||||
|
||||
// Mounted checks if the given path is mounted as the fs type
|
||||
func Mounted(fsType FsMagic, mountPath string) (bool, error) {
|
||||
var buf syscall.Statfs_t
|
||||
if err := syscall.Statfs(mountPath, &buf); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return FsMagic(buf.Type) == fsType, nil
|
||||
}
|
133
vendor/github.com/docker/docker/daemon/graphdriver/driver_linux.go
generated
vendored
Normal file
133
vendor/github.com/docker/docker/daemon/graphdriver/driver_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,133 @@
|
|||
// +build linux
|
||||
|
||||
package graphdriver
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
)
|
||||
|
||||
const (
|
||||
// FsMagicAufs filesystem id for Aufs
|
||||
FsMagicAufs = FsMagic(0x61756673)
|
||||
// FsMagicBtrfs filesystem id for Btrfs
|
||||
FsMagicBtrfs = FsMagic(0x9123683E)
|
||||
// FsMagicCramfs filesystem id for Cramfs
|
||||
FsMagicCramfs = FsMagic(0x28cd3d45)
|
||||
// FsMagicEcryptfs filesystem id for eCryptfs
|
||||
FsMagicEcryptfs = FsMagic(0xf15f)
|
||||
// FsMagicExtfs filesystem id for Extfs
|
||||
FsMagicExtfs = FsMagic(0x0000EF53)
|
||||
// FsMagicF2fs filesystem id for F2fs
|
||||
FsMagicF2fs = FsMagic(0xF2F52010)
|
||||
// FsMagicGPFS filesystem id for GPFS
|
||||
FsMagicGPFS = FsMagic(0x47504653)
|
||||
// FsMagicJffs2Fs filesystem if for Jffs2Fs
|
||||
FsMagicJffs2Fs = FsMagic(0x000072b6)
|
||||
// FsMagicJfs filesystem id for Jfs
|
||||
FsMagicJfs = FsMagic(0x3153464a)
|
||||
// FsMagicNfsFs filesystem id for NfsFs
|
||||
FsMagicNfsFs = FsMagic(0x00006969)
|
||||
// FsMagicRAMFs filesystem id for RamFs
|
||||
FsMagicRAMFs = FsMagic(0x858458f6)
|
||||
// FsMagicReiserFs filesystem id for ReiserFs
|
||||
FsMagicReiserFs = FsMagic(0x52654973)
|
||||
// FsMagicSmbFs filesystem id for SmbFs
|
||||
FsMagicSmbFs = FsMagic(0x0000517B)
|
||||
// FsMagicSquashFs filesystem id for SquashFs
|
||||
FsMagicSquashFs = FsMagic(0x73717368)
|
||||
// FsMagicTmpFs filesystem id for TmpFs
|
||||
FsMagicTmpFs = FsMagic(0x01021994)
|
||||
// FsMagicVxFS filesystem id for VxFs
|
||||
FsMagicVxFS = FsMagic(0xa501fcf5)
|
||||
// FsMagicXfs filesystem id for Xfs
|
||||
FsMagicXfs = FsMagic(0x58465342)
|
||||
// FsMagicZfs filesystem id for Zfs
|
||||
FsMagicZfs = FsMagic(0x2fc12fc1)
|
||||
// FsMagicOverlay filesystem id for overlay
|
||||
FsMagicOverlay = FsMagic(0x794C7630)
|
||||
)
|
||||
|
||||
var (
|
||||
// Slice of drivers that should be used in an order
|
||||
priority = []string{
|
||||
"aufs",
|
||||
"btrfs",
|
||||
"zfs",
|
||||
"devicemapper",
|
||||
"overlay",
|
||||
"vfs",
|
||||
}
|
||||
|
||||
// FsNames maps filesystem id to name of the filesystem.
|
||||
FsNames = map[FsMagic]string{
|
||||
FsMagicAufs: "aufs",
|
||||
FsMagicBtrfs: "btrfs",
|
||||
FsMagicCramfs: "cramfs",
|
||||
FsMagicExtfs: "extfs",
|
||||
FsMagicF2fs: "f2fs",
|
||||
FsMagicGPFS: "gpfs",
|
||||
FsMagicJffs2Fs: "jffs2",
|
||||
FsMagicJfs: "jfs",
|
||||
FsMagicNfsFs: "nfs",
|
||||
FsMagicRAMFs: "ramfs",
|
||||
FsMagicReiserFs: "reiserfs",
|
||||
FsMagicSmbFs: "smb",
|
||||
FsMagicSquashFs: "squashfs",
|
||||
FsMagicTmpFs: "tmpfs",
|
||||
FsMagicUnsupported: "unsupported",
|
||||
FsMagicVxFS: "vxfs",
|
||||
FsMagicXfs: "xfs",
|
||||
FsMagicZfs: "zfs",
|
||||
}
|
||||
)
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
var buf syscall.Statfs_t
|
||||
if err := syscall.Statfs(filepath.Dir(rootpath), &buf); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return FsMagic(buf.Type), nil
|
||||
}
|
||||
|
||||
// NewFsChecker returns a checker configured for the provied FsMagic
|
||||
func NewFsChecker(t FsMagic) Checker {
|
||||
return &fsChecker{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
type fsChecker struct {
|
||||
t FsMagic
|
||||
}
|
||||
|
||||
func (c *fsChecker) IsMounted(path string) bool {
|
||||
m, _ := Mounted(c.t, path)
|
||||
return m
|
||||
}
|
||||
|
||||
// NewDefaultChecker returns a check that parses /proc/mountinfo to check
|
||||
// if the specified path is mounted.
|
||||
func NewDefaultChecker() Checker {
|
||||
return &defaultChecker{}
|
||||
}
|
||||
|
||||
type defaultChecker struct {
|
||||
}
|
||||
|
||||
func (c *defaultChecker) IsMounted(path string) bool {
|
||||
m, _ := mount.Mounted(path)
|
||||
return m
|
||||
}
|
||||
|
||||
// Mounted checks if the given path is mounted as the fs type
|
||||
func Mounted(fsType FsMagic, mountPath string) (bool, error) {
|
||||
var buf syscall.Statfs_t
|
||||
if err := syscall.Statfs(mountPath, &buf); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return FsMagic(buf.Type) == fsType, nil
|
||||
}
|
65
vendor/github.com/docker/docker/daemon/graphdriver/driver_solaris.go
generated
vendored
Normal file
65
vendor/github.com/docker/docker/daemon/graphdriver/driver_solaris.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
// +build solaris,cgo
|
||||
|
||||
package graphdriver
|
||||
|
||||
/*
|
||||
#include <sys/statvfs.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static inline struct statvfs *getstatfs(char *s) {
|
||||
struct statvfs *buf;
|
||||
int err;
|
||||
buf = (struct statvfs *)malloc(sizeof(struct statvfs));
|
||||
err = statvfs(s, buf);
|
||||
return buf;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"path/filepath"
|
||||
"unsafe"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// FsMagicZfs filesystem id for Zfs
|
||||
FsMagicZfs = FsMagic(0x2fc12fc1)
|
||||
)
|
||||
|
||||
var (
|
||||
// Slice of drivers that should be used in an order
|
||||
priority = []string{
|
||||
"zfs",
|
||||
}
|
||||
|
||||
// FsNames maps filesystem id to name of the filesystem.
|
||||
FsNames = map[FsMagic]string{
|
||||
FsMagicZfs: "zfs",
|
||||
}
|
||||
)
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Mounted checks if the given path is mounted as the fs type
|
||||
//Solaris supports only ZFS for now
|
||||
func Mounted(fsType FsMagic, mountPath string) (bool, error) {
|
||||
|
||||
cs := C.CString(filepath.Dir(mountPath))
|
||||
buf := C.getstatfs(cs)
|
||||
|
||||
// on Solaris buf.f_basetype contains ['z', 'f', 's', 0 ... ]
|
||||
if (buf.f_basetype[0] != 122) || (buf.f_basetype[1] != 102) || (buf.f_basetype[2] != 115) ||
|
||||
(buf.f_basetype[3] != 0) {
|
||||
log.Debugf("[zfs] no zfs dataset found for rootdir '%s'", mountPath)
|
||||
C.free(unsafe.Pointer(buf))
|
||||
return false, ErrPrerequisites
|
||||
}
|
||||
|
||||
C.free(unsafe.Pointer(buf))
|
||||
C.free(unsafe.Pointer(cs))
|
||||
return true, nil
|
||||
}
|
15
vendor/github.com/docker/docker/daemon/graphdriver/driver_unsupported.go
generated
vendored
Normal file
15
vendor/github.com/docker/docker/daemon/graphdriver/driver_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
// +build !linux,!windows,!freebsd,!solaris
|
||||
|
||||
package graphdriver
|
||||
|
||||
var (
|
||||
// Slice of drivers that should be used in an order
|
||||
priority = []string{
|
||||
"unsupported",
|
||||
}
|
||||
)
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
return FsMagicUnsupported, nil
|
||||
}
|
14
vendor/github.com/docker/docker/daemon/graphdriver/driver_windows.go
generated
vendored
Normal file
14
vendor/github.com/docker/docker/daemon/graphdriver/driver_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
package graphdriver
|
||||
|
||||
var (
|
||||
// Slice of drivers that should be used in order
|
||||
priority = []string{
|
||||
"windowsfilter",
|
||||
}
|
||||
)
|
||||
|
||||
// GetFSMagic returns the filesystem id given the path.
|
||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
||||
// Note it is OK to return FsMagicUnsupported on Windows.
|
||||
return FsMagicUnsupported, nil
|
||||
}
|
162
vendor/github.com/docker/docker/daemon/graphdriver/fsdiff.go
generated
vendored
Normal file
162
vendor/github.com/docker/docker/daemon/graphdriver/fsdiff.go
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
|||
package graphdriver
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/chrootarchive"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
)
|
||||
|
||||
var (
|
||||
// ApplyUncompressedLayer defines the unpack method used by the graph
|
||||
// driver.
|
||||
ApplyUncompressedLayer = chrootarchive.ApplyUncompressedLayer
|
||||
)
|
||||
|
||||
// NaiveDiffDriver takes a ProtoDriver and adds the
|
||||
// capability of the Diffing methods which it may or may not
|
||||
// support on its own. See the comment on the exported
|
||||
// NewNaiveDiffDriver function below.
|
||||
// Notably, the AUFS driver doesn't need to be wrapped like this.
|
||||
type NaiveDiffDriver struct {
|
||||
ProtoDriver
|
||||
uidMaps []idtools.IDMap
|
||||
gidMaps []idtools.IDMap
|
||||
}
|
||||
|
||||
// NewNaiveDiffDriver returns a fully functional driver that wraps the
|
||||
// given ProtoDriver and adds the capability of the following methods which
|
||||
// it may or may not support on its own:
|
||||
// Diff(id, parent string) (archive.Archive, error)
|
||||
// Changes(id, parent string) ([]archive.Change, error)
|
||||
// ApplyDiff(id, parent string, diff archive.Reader) (size int64, err error)
|
||||
// DiffSize(id, parent string) (size int64, err error)
|
||||
func NewNaiveDiffDriver(driver ProtoDriver, uidMaps, gidMaps []idtools.IDMap) Driver {
|
||||
return &NaiveDiffDriver{ProtoDriver: driver,
|
||||
uidMaps: uidMaps,
|
||||
gidMaps: gidMaps}
|
||||
}
|
||||
|
||||
// Diff produces an archive of the changes between the specified
|
||||
// layer and its parent layer which may be "".
|
||||
func (gdw *NaiveDiffDriver) Diff(id, parent string) (arch archive.Archive, err error) {
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
layerFs, err := driver.Get(id, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
driver.Put(id)
|
||||
}
|
||||
}()
|
||||
|
||||
if parent == "" {
|
||||
archive, err := archive.Tar(layerFs, archive.Uncompressed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ioutils.NewReadCloserWrapper(archive, func() error {
|
||||
err := archive.Close()
|
||||
driver.Put(id)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
parentFs, err := driver.Get(parent, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer driver.Put(parent)
|
||||
|
||||
changes, err := archive.ChangesDirs(layerFs, parentFs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
archive, err := archive.ExportChanges(layerFs, changes, gdw.uidMaps, gdw.gidMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ioutils.NewReadCloserWrapper(archive, func() error {
|
||||
err := archive.Close()
|
||||
driver.Put(id)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
// Changes produces a list of changes between the specified layer
|
||||
// and its parent layer. If parent is "", then all changes will be ADD changes.
|
||||
func (gdw *NaiveDiffDriver) Changes(id, parent string) ([]archive.Change, error) {
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
layerFs, err := driver.Get(id, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer driver.Put(id)
|
||||
|
||||
parentFs := ""
|
||||
|
||||
if parent != "" {
|
||||
parentFs, err = driver.Get(parent, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer driver.Put(parent)
|
||||
}
|
||||
|
||||
return archive.ChangesDirs(layerFs, parentFs)
|
||||
}
|
||||
|
||||
// ApplyDiff extracts the changeset from the given diff into the
|
||||
// layer with the specified id and parent, returning the size of the
|
||||
// new layer in bytes.
|
||||
func (gdw *NaiveDiffDriver) ApplyDiff(id, parent string, diff archive.Reader) (size int64, err error) {
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
// Mount the root filesystem so we can apply the diff/layer.
|
||||
layerFs, err := driver.Get(id, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer driver.Put(id)
|
||||
|
||||
options := &archive.TarOptions{UIDMaps: gdw.uidMaps,
|
||||
GIDMaps: gdw.gidMaps}
|
||||
start := time.Now().UTC()
|
||||
logrus.Debug("Start untar layer")
|
||||
if size, err = ApplyUncompressedLayer(layerFs, diff, options); err != nil {
|
||||
return
|
||||
}
|
||||
logrus.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DiffSize calculates the changes between the specified layer
|
||||
// and its parent and returns the size in bytes of the changes
|
||||
// relative to its base filesystem directory.
|
||||
func (gdw *NaiveDiffDriver) DiffSize(id, parent string) (size int64, err error) {
|
||||
driver := gdw.ProtoDriver
|
||||
|
||||
changes, err := gdw.Changes(id, parent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
layerFs, err := driver.Get(id, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer driver.Put(id)
|
||||
|
||||
return archive.ChangesSize(layerFs, changes), nil
|
||||
}
|
32
vendor/github.com/docker/docker/daemon/graphdriver/plugin.go
generated
vendored
Normal file
32
vendor/github.com/docker/docker/daemon/graphdriver/plugin.go
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
// +build experimental
|
||||
|
||||
package graphdriver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/pkg/plugins"
|
||||
)
|
||||
|
||||
type pluginClient interface {
|
||||
// Call calls the specified method with the specified arguments for the plugin.
|
||||
Call(string, interface{}, interface{}) error
|
||||
// Stream calls the specified method with the specified arguments for the plugin and returns the response IO stream
|
||||
Stream(string, interface{}) (io.ReadCloser, error)
|
||||
// SendFile calls the specified method, and passes through the IO stream
|
||||
SendFile(string, io.Reader, interface{}) error
|
||||
}
|
||||
|
||||
func lookupPlugin(name, home string, opts []string) (Driver, error) {
|
||||
pl, err := plugins.Get(name, "GraphDriver")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error looking up graphdriver plugin %s: %v", name, err)
|
||||
}
|
||||
return newPluginDriver(name, home, opts, pl.Client())
|
||||
}
|
||||
|
||||
func newPluginDriver(name, home string, opts []string, c pluginClient) (Driver, error) {
|
||||
proxy := &graphDriverProxy{name, c}
|
||||
return proxy, proxy.Init(home, opts)
|
||||
}
|
7
vendor/github.com/docker/docker/daemon/graphdriver/plugin_unsupported.go
generated
vendored
Normal file
7
vendor/github.com/docker/docker/daemon/graphdriver/plugin_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
// +build !experimental
|
||||
|
||||
package graphdriver
|
||||
|
||||
func lookupPlugin(name, home string, opts []string) (Driver, error) {
|
||||
return nil, ErrNotSupported
|
||||
}
|
225
vendor/github.com/docker/docker/daemon/graphdriver/proxy.go
generated
vendored
Normal file
225
vendor/github.com/docker/docker/daemon/graphdriver/proxy.go
generated
vendored
Normal file
|
@ -0,0 +1,225 @@
|
|||
// +build experimental
|
||||
|
||||
package graphdriver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
)
|
||||
|
||||
type graphDriverProxy struct {
|
||||
name string
|
||||
client pluginClient
|
||||
}
|
||||
|
||||
type graphDriverRequest struct {
|
||||
ID string `json:",omitempty"`
|
||||
Parent string `json:",omitempty"`
|
||||
MountLabel string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type graphDriverResponse struct {
|
||||
Err string `json:",omitempty"`
|
||||
Dir string `json:",omitempty"`
|
||||
Exists bool `json:",omitempty"`
|
||||
Status [][2]string `json:",omitempty"`
|
||||
Changes []archive.Change `json:",omitempty"`
|
||||
Size int64 `json:",omitempty"`
|
||||
Metadata map[string]string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type graphDriverInitRequest struct {
|
||||
Home string
|
||||
Opts []string
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Init(home string, opts []string) error {
|
||||
args := &graphDriverInitRequest{
|
||||
Home: home,
|
||||
Opts: opts,
|
||||
}
|
||||
var ret graphDriverResponse
|
||||
if err := d.client.Call("GraphDriver.Init", args, &ret); err != nil {
|
||||
return err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return errors.New(ret.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) String() string {
|
||||
return d.name
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) CreateReadWrite(id, parent, mountLabel string, storageOpt map[string]string) error {
|
||||
args := &graphDriverRequest{
|
||||
ID: id,
|
||||
Parent: parent,
|
||||
MountLabel: mountLabel,
|
||||
}
|
||||
var ret graphDriverResponse
|
||||
if err := d.client.Call("GraphDriver.CreateReadWrite", args, &ret); err != nil {
|
||||
return err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return errors.New(ret.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Create(id, parent, mountLabel string, storageOpt map[string]string) error {
|
||||
args := &graphDriverRequest{
|
||||
ID: id,
|
||||
Parent: parent,
|
||||
MountLabel: mountLabel,
|
||||
}
|
||||
var ret graphDriverResponse
|
||||
if err := d.client.Call("GraphDriver.Create", args, &ret); err != nil {
|
||||
return err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return errors.New(ret.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Remove(id string) error {
|
||||
args := &graphDriverRequest{ID: id}
|
||||
var ret graphDriverResponse
|
||||
if err := d.client.Call("GraphDriver.Remove", args, &ret); err != nil {
|
||||
return err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return errors.New(ret.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Get(id, mountLabel string) (string, error) {
|
||||
args := &graphDriverRequest{
|
||||
ID: id,
|
||||
MountLabel: mountLabel,
|
||||
}
|
||||
var ret graphDriverResponse
|
||||
if err := d.client.Call("GraphDriver.Get", args, &ret); err != nil {
|
||||
return "", err
|
||||
}
|
||||
var err error
|
||||
if ret.Err != "" {
|
||||
err = errors.New(ret.Err)
|
||||
}
|
||||
return ret.Dir, err
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Put(id string) error {
|
||||
args := &graphDriverRequest{ID: id}
|
||||
var ret graphDriverResponse
|
||||
if err := d.client.Call("GraphDriver.Put", args, &ret); err != nil {
|
||||
return err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return errors.New(ret.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Exists(id string) bool {
|
||||
args := &graphDriverRequest{ID: id}
|
||||
var ret graphDriverResponse
|
||||
if err := d.client.Call("GraphDriver.Exists", args, &ret); err != nil {
|
||||
return false
|
||||
}
|
||||
return ret.Exists
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Status() [][2]string {
|
||||
args := &graphDriverRequest{}
|
||||
var ret graphDriverResponse
|
||||
if err := d.client.Call("GraphDriver.Status", args, &ret); err != nil {
|
||||
return nil
|
||||
}
|
||||
return ret.Status
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) GetMetadata(id string) (map[string]string, error) {
|
||||
args := &graphDriverRequest{
|
||||
ID: id,
|
||||
}
|
||||
var ret graphDriverResponse
|
||||
if err := d.client.Call("GraphDriver.GetMetadata", args, &ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return nil, errors.New(ret.Err)
|
||||
}
|
||||
return ret.Metadata, nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Cleanup() error {
|
||||
args := &graphDriverRequest{}
|
||||
var ret graphDriverResponse
|
||||
if err := d.client.Call("GraphDriver.Cleanup", args, &ret); err != nil {
|
||||
return nil
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return errors.New(ret.Err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Diff(id, parent string) (archive.Archive, error) {
|
||||
args := &graphDriverRequest{
|
||||
ID: id,
|
||||
Parent: parent,
|
||||
}
|
||||
body, err := d.client.Stream("GraphDriver.Diff", args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return archive.Archive(body), nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) Changes(id, parent string) ([]archive.Change, error) {
|
||||
args := &graphDriverRequest{
|
||||
ID: id,
|
||||
Parent: parent,
|
||||
}
|
||||
var ret graphDriverResponse
|
||||
if err := d.client.Call("GraphDriver.Changes", args, &ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return nil, errors.New(ret.Err)
|
||||
}
|
||||
|
||||
return ret.Changes, nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) ApplyDiff(id, parent string, diff archive.Reader) (int64, error) {
|
||||
var ret graphDriverResponse
|
||||
if err := d.client.SendFile(fmt.Sprintf("GraphDriver.ApplyDiff?id=%s&parent=%s", id, parent), diff, &ret); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return -1, errors.New(ret.Err)
|
||||
}
|
||||
return ret.Size, nil
|
||||
}
|
||||
|
||||
func (d *graphDriverProxy) DiffSize(id, parent string) (int64, error) {
|
||||
args := &graphDriverRequest{
|
||||
ID: id,
|
||||
Parent: parent,
|
||||
}
|
||||
var ret graphDriverResponse
|
||||
if err := d.client.Call("GraphDriver.DiffSize", args, &ret); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if ret.Err != "" {
|
||||
return -1, errors.New(ret.Err)
|
||||
}
|
||||
return ret.Size, nil
|
||||
}
|
175
vendor/github.com/docker/docker/image/fs.go
generated
vendored
Normal file
175
vendor/github.com/docker/docker/image/fs.go
generated
vendored
Normal file
|
@ -0,0 +1,175 @@
|
|||
package image
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
)
|
||||
|
||||
// IDWalkFunc is function called by StoreBackend.Walk
|
||||
type IDWalkFunc func(id ID) error
|
||||
|
||||
// StoreBackend provides interface for image.Store persistence
|
||||
type StoreBackend interface {
|
||||
Walk(f IDWalkFunc) error
|
||||
Get(id ID) ([]byte, error)
|
||||
Set(data []byte) (ID, error)
|
||||
Delete(id ID) error
|
||||
SetMetadata(id ID, key string, data []byte) error
|
||||
GetMetadata(id ID, key string) ([]byte, error)
|
||||
DeleteMetadata(id ID, key string) error
|
||||
}
|
||||
|
||||
// fs implements StoreBackend using the filesystem.
|
||||
type fs struct {
|
||||
sync.RWMutex
|
||||
root string
|
||||
}
|
||||
|
||||
const (
|
||||
contentDirName = "content"
|
||||
metadataDirName = "metadata"
|
||||
)
|
||||
|
||||
// NewFSStoreBackend returns new filesystem based backend for image.Store
|
||||
func NewFSStoreBackend(root string) (StoreBackend, error) {
|
||||
return newFSStore(root)
|
||||
}
|
||||
|
||||
func newFSStore(root string) (*fs, error) {
|
||||
s := &fs{
|
||||
root: root,
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(root, contentDirName, string(digest.Canonical)), 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(root, metadataDirName, string(digest.Canonical)), 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *fs) contentFile(id ID) string {
|
||||
dgst := digest.Digest(id)
|
||||
return filepath.Join(s.root, contentDirName, string(dgst.Algorithm()), dgst.Hex())
|
||||
}
|
||||
|
||||
func (s *fs) metadataDir(id ID) string {
|
||||
dgst := digest.Digest(id)
|
||||
return filepath.Join(s.root, metadataDirName, string(dgst.Algorithm()), dgst.Hex())
|
||||
}
|
||||
|
||||
// Walk calls the supplied callback for each image ID in the storage backend.
|
||||
func (s *fs) Walk(f IDWalkFunc) error {
|
||||
// Only Canonical digest (sha256) is currently supported
|
||||
s.RLock()
|
||||
dir, err := ioutil.ReadDir(filepath.Join(s.root, contentDirName, string(digest.Canonical)))
|
||||
s.RUnlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range dir {
|
||||
dgst := digest.NewDigestFromHex(string(digest.Canonical), v.Name())
|
||||
if err := dgst.Validate(); err != nil {
|
||||
logrus.Debugf("Skipping invalid digest %s: %s", dgst, err)
|
||||
continue
|
||||
}
|
||||
if err := f(ID(dgst)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the content stored under a given ID.
|
||||
func (s *fs) Get(id ID) ([]byte, error) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.get(id)
|
||||
}
|
||||
|
||||
func (s *fs) get(id ID) ([]byte, error) {
|
||||
content, err := ioutil.ReadFile(s.contentFile(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// todo: maybe optional
|
||||
if ID(digest.FromBytes(content)) != id {
|
||||
return nil, fmt.Errorf("failed to verify image: %v", id)
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
// Set stores content under a given ID.
|
||||
func (s *fs) Set(data []byte) (ID, error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if len(data) == 0 {
|
||||
return "", fmt.Errorf("Invalid empty data")
|
||||
}
|
||||
|
||||
id := ID(digest.FromBytes(data))
|
||||
if err := ioutils.AtomicWriteFile(s.contentFile(id), data, 0600); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// Delete removes content and metadata files associated with the ID.
|
||||
func (s *fs) Delete(id ID) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if err := os.RemoveAll(s.metadataDir(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Remove(s.contentFile(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMetadata sets metadata for a given ID. It fails if there's no base file.
|
||||
func (s *fs) SetMetadata(id ID, key string, data []byte) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
if _, err := s.get(id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseDir := filepath.Join(s.metadataDir(id))
|
||||
if err := os.MkdirAll(baseDir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutils.AtomicWriteFile(filepath.Join(s.metadataDir(id), key), data, 0600)
|
||||
}
|
||||
|
||||
// GetMetadata returns metadata for a given ID.
|
||||
func (s *fs) GetMetadata(id ID, key string) ([]byte, error) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
if _, err := s.get(id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ioutil.ReadFile(filepath.Join(s.metadataDir(id), key))
|
||||
}
|
||||
|
||||
// DeleteMetadata removes the metadata associated with an ID.
|
||||
func (s *fs) DeleteMetadata(id ID, key string) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
return os.RemoveAll(filepath.Join(s.metadataDir(id), key))
|
||||
}
|
140
vendor/github.com/docker/docker/image/image.go
generated
vendored
Normal file
140
vendor/github.com/docker/docker/image/image.go
generated
vendored
Normal file
|
@ -0,0 +1,140 @@
|
|||
package image
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/engine-api/types/container"
|
||||
)
|
||||
|
||||
// ID is the content-addressable ID of an image.
|
||||
type ID digest.Digest
|
||||
|
||||
func (id ID) String() string {
|
||||
return digest.Digest(id).String()
|
||||
}
|
||||
|
||||
// V1Image stores the V1 image configuration.
|
||||
type V1Image struct {
|
||||
// ID a unique 64 character identifier of the image
|
||||
ID string `json:"id,omitempty"`
|
||||
// Parent id of the image
|
||||
Parent string `json:"parent,omitempty"`
|
||||
// Comment user added comment
|
||||
Comment string `json:"comment,omitempty"`
|
||||
// Created timestamp when image was created
|
||||
Created time.Time `json:"created"`
|
||||
// Container is the id of the container used to commit
|
||||
Container string `json:"container,omitempty"`
|
||||
// ContainerConfig is the configuration of the container that is committed into the image
|
||||
ContainerConfig container.Config `json:"container_config,omitempty"`
|
||||
// DockerVersion specifies version on which image is built
|
||||
DockerVersion string `json:"docker_version,omitempty"`
|
||||
// Author of the image
|
||||
Author string `json:"author,omitempty"`
|
||||
// Config is the configuration of the container received from the client
|
||||
Config *container.Config `json:"config,omitempty"`
|
||||
// Architecture is the hardware that the image is build and runs on
|
||||
Architecture string `json:"architecture,omitempty"`
|
||||
// OS is the operating system used to build and run the image
|
||||
OS string `json:"os,omitempty"`
|
||||
// Size is the total size of the image including all layers it is composed of
|
||||
Size int64 `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Image stores the image configuration
|
||||
type Image struct {
|
||||
V1Image
|
||||
Parent ID `json:"parent,omitempty"`
|
||||
RootFS *RootFS `json:"rootfs,omitempty"`
|
||||
History []History `json:"history,omitempty"`
|
||||
OSVersion string `json:"os.version,omitempty"`
|
||||
OSFeatures []string `json:"os.features,omitempty"`
|
||||
|
||||
// rawJSON caches the immutable JSON associated with this image.
|
||||
rawJSON []byte
|
||||
|
||||
// computedID is the ID computed from the hash of the image config.
|
||||
// Not to be confused with the legacy V1 ID in V1Image.
|
||||
computedID ID
|
||||
}
|
||||
|
||||
// RawJSON returns the immutable JSON associated with the image.
|
||||
func (img *Image) RawJSON() []byte {
|
||||
return img.rawJSON
|
||||
}
|
||||
|
||||
// ID returns the image's content-addressable ID.
|
||||
func (img *Image) ID() ID {
|
||||
return img.computedID
|
||||
}
|
||||
|
||||
// ImageID stringizes ID.
|
||||
func (img *Image) ImageID() string {
|
||||
return string(img.ID())
|
||||
}
|
||||
|
||||
// RunConfig returns the image's container config.
|
||||
func (img *Image) RunConfig() *container.Config {
|
||||
return img.Config
|
||||
}
|
||||
|
||||
// MarshalJSON serializes the image to JSON. It sorts the top-level keys so
|
||||
// that JSON that's been manipulated by a push/pull cycle with a legacy
|
||||
// registry won't end up with a different key order.
|
||||
func (img *Image) MarshalJSON() ([]byte, error) {
|
||||
type MarshalImage Image
|
||||
|
||||
pass1, err := json.Marshal(MarshalImage(*img))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var c map[string]*json.RawMessage
|
||||
if err := json.Unmarshal(pass1, &c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(c)
|
||||
}
|
||||
|
||||
// History stores build commands that were used to create an image
|
||||
type History struct {
|
||||
// Created timestamp for build point
|
||||
Created time.Time `json:"created"`
|
||||
// Author of the build point
|
||||
Author string `json:"author,omitempty"`
|
||||
// CreatedBy keeps the Dockerfile command used while building image.
|
||||
CreatedBy string `json:"created_by,omitempty"`
|
||||
// Comment is custom message set by the user when creating the image.
|
||||
Comment string `json:"comment,omitempty"`
|
||||
// EmptyLayer is set to true if this history item did not generate a
|
||||
// layer. Otherwise, the history item is associated with the next
|
||||
// layer in the RootFS section.
|
||||
EmptyLayer bool `json:"empty_layer,omitempty"`
|
||||
}
|
||||
|
||||
// Exporter provides interface for exporting and importing images
|
||||
type Exporter interface {
|
||||
Load(io.ReadCloser, io.Writer, bool) error
|
||||
// TODO: Load(net.Context, io.ReadCloser, <- chan StatusMessage) error
|
||||
Save([]string, io.Writer) error
|
||||
}
|
||||
|
||||
// NewFromJSON creates an Image configuration from json.
|
||||
func NewFromJSON(src []byte) (*Image, error) {
|
||||
img := &Image{}
|
||||
|
||||
if err := json.Unmarshal(src, img); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if img.RootFS == nil {
|
||||
return nil, errors.New("Invalid image JSON, no RootFS key.")
|
||||
}
|
||||
|
||||
img.rawJSON = src
|
||||
|
||||
return img, nil
|
||||
}
|
16
vendor/github.com/docker/docker/image/rootfs.go
generated
vendored
Normal file
16
vendor/github.com/docker/docker/image/rootfs.go
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
package image
|
||||
|
||||
import "github.com/docker/docker/layer"
|
||||
|
||||
// TypeLayers is used for RootFS.Type for filesystems organized into layers.
|
||||
const TypeLayers = "layers"
|
||||
|
||||
// NewRootFS returns empty RootFS struct
|
||||
func NewRootFS() *RootFS {
|
||||
return &RootFS{Type: TypeLayers}
|
||||
}
|
||||
|
||||
// Append appends a new diffID to rootfs
|
||||
func (r *RootFS) Append(id layer.DiffID) {
|
||||
r.DiffIDs = append(r.DiffIDs, id)
|
||||
}
|
18
vendor/github.com/docker/docker/image/rootfs_unix.go
generated
vendored
Normal file
18
vendor/github.com/docker/docker/image/rootfs_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
// +build !windows
|
||||
|
||||
package image
|
||||
|
||||
import "github.com/docker/docker/layer"
|
||||
|
||||
// RootFS describes images root filesystem
|
||||
// This is currently a placeholder that only supports layers. In the future
|
||||
// this can be made into an interface that supports different implementations.
|
||||
type RootFS struct {
|
||||
Type string `json:"type"`
|
||||
DiffIDs []layer.DiffID `json:"diff_ids,omitempty"`
|
||||
}
|
||||
|
||||
// ChainID returns the ChainID for the top layer in RootFS.
|
||||
func (r *RootFS) ChainID() layer.ChainID {
|
||||
return layer.CreateChainID(r.DiffIDs)
|
||||
}
|
48
vendor/github.com/docker/docker/image/rootfs_windows.go
generated
vendored
Normal file
48
vendor/github.com/docker/docker/image/rootfs_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
// +build windows
|
||||
|
||||
package image
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/layer"
|
||||
)
|
||||
|
||||
// TypeLayersWithBase is used for RootFS.Type for Windows filesystems that have layers and a centrally-stored base layer.
|
||||
const TypeLayersWithBase = "layers+base"
|
||||
|
||||
// RootFS describes images root filesystem
|
||||
// This is currently a placeholder that only supports layers. In the future
|
||||
// this can be made into an interface that supports different implementations.
|
||||
type RootFS struct {
|
||||
Type string `json:"type"`
|
||||
DiffIDs []layer.DiffID `json:"diff_ids,omitempty"`
|
||||
BaseLayer string `json:"base_layer,omitempty"`
|
||||
}
|
||||
|
||||
// BaseLayerID returns the 64 byte hex ID for the baselayer name.
|
||||
func (r *RootFS) BaseLayerID() string {
|
||||
if r.Type != TypeLayersWithBase {
|
||||
panic("tried to get base layer ID without a base layer")
|
||||
}
|
||||
baseID := sha512.Sum384([]byte(r.BaseLayer))
|
||||
return fmt.Sprintf("%x", baseID[:32])
|
||||
}
|
||||
|
||||
// ChainID returns the ChainID for the top layer in RootFS.
|
||||
func (r *RootFS) ChainID() layer.ChainID {
|
||||
ids := r.DiffIDs
|
||||
if r.Type == TypeLayersWithBase {
|
||||
// Add an extra ID for the base.
|
||||
baseDiffID := layer.DiffID(digest.FromBytes([]byte(r.BaseLayerID())))
|
||||
ids = append([]layer.DiffID{baseDiffID}, ids...)
|
||||
}
|
||||
return layer.CreateChainID(ids)
|
||||
}
|
||||
|
||||
// NewRootFSWithBaseLayer returns a RootFS struct with a base layer
|
||||
func NewRootFSWithBaseLayer(baseLayer string) *RootFS {
|
||||
return &RootFS{Type: TypeLayersWithBase, BaseLayer: baseLayer}
|
||||
}
|
295
vendor/github.com/docker/docker/image/store.go
generated
vendored
Normal file
295
vendor/github.com/docker/docker/image/store.go
generated
vendored
Normal file
|
@ -0,0 +1,295 @@
|
|||
package image
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/layer"
|
||||
)
|
||||
|
||||
// Store is an interface for creating and accessing images
|
||||
type Store interface {
|
||||
Create(config []byte) (ID, error)
|
||||
Get(id ID) (*Image, error)
|
||||
Delete(id ID) ([]layer.Metadata, error)
|
||||
Search(partialID string) (ID, error)
|
||||
SetParent(id ID, parent ID) error
|
||||
GetParent(id ID) (ID, error)
|
||||
Children(id ID) []ID
|
||||
Map() map[ID]*Image
|
||||
Heads() map[ID]*Image
|
||||
}
|
||||
|
||||
// LayerGetReleaser is a minimal interface for getting and releasing images.
|
||||
type LayerGetReleaser interface {
|
||||
Get(layer.ChainID) (layer.Layer, error)
|
||||
Release(layer.Layer) ([]layer.Metadata, error)
|
||||
}
|
||||
|
||||
type imageMeta struct {
|
||||
layer layer.Layer
|
||||
children map[ID]struct{}
|
||||
}
|
||||
|
||||
type store struct {
|
||||
sync.Mutex
|
||||
ls LayerGetReleaser
|
||||
images map[ID]*imageMeta
|
||||
fs StoreBackend
|
||||
digestSet *digest.Set
|
||||
}
|
||||
|
||||
// NewImageStore returns new store object for given layer store
|
||||
func NewImageStore(fs StoreBackend, ls LayerGetReleaser) (Store, error) {
|
||||
is := &store{
|
||||
ls: ls,
|
||||
images: make(map[ID]*imageMeta),
|
||||
fs: fs,
|
||||
digestSet: digest.NewSet(),
|
||||
}
|
||||
|
||||
// load all current images and retain layers
|
||||
if err := is.restore(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return is, nil
|
||||
}
|
||||
|
||||
func (is *store) restore() error {
|
||||
err := is.fs.Walk(func(id ID) error {
|
||||
img, err := is.Get(id)
|
||||
if err != nil {
|
||||
logrus.Errorf("invalid image %v, %v", id, err)
|
||||
return nil
|
||||
}
|
||||
var l layer.Layer
|
||||
if chainID := img.RootFS.ChainID(); chainID != "" {
|
||||
l, err = is.ls.Get(chainID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := is.digestSet.Add(digest.Digest(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imageMeta := &imageMeta{
|
||||
layer: l,
|
||||
children: make(map[ID]struct{}),
|
||||
}
|
||||
|
||||
is.images[ID(id)] = imageMeta
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Second pass to fill in children maps
|
||||
for id := range is.images {
|
||||
if parent, err := is.GetParent(id); err == nil {
|
||||
if parentMeta := is.images[parent]; parentMeta != nil {
|
||||
parentMeta.children[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (is *store) Create(config []byte) (ID, error) {
|
||||
var img Image
|
||||
err := json.Unmarshal(config, &img)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Must reject any config that references diffIDs from the history
|
||||
// which aren't among the rootfs layers.
|
||||
rootFSLayers := make(map[layer.DiffID]struct{})
|
||||
for _, diffID := range img.RootFS.DiffIDs {
|
||||
rootFSLayers[diffID] = struct{}{}
|
||||
}
|
||||
|
||||
layerCounter := 0
|
||||
for _, h := range img.History {
|
||||
if !h.EmptyLayer {
|
||||
layerCounter++
|
||||
}
|
||||
}
|
||||
if layerCounter > len(img.RootFS.DiffIDs) {
|
||||
return "", errors.New("too many non-empty layers in History section")
|
||||
}
|
||||
|
||||
dgst, err := is.fs.Set(config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
imageID := ID(dgst)
|
||||
|
||||
is.Lock()
|
||||
defer is.Unlock()
|
||||
|
||||
if _, exists := is.images[imageID]; exists {
|
||||
return imageID, nil
|
||||
}
|
||||
|
||||
layerID := img.RootFS.ChainID()
|
||||
|
||||
var l layer.Layer
|
||||
if layerID != "" {
|
||||
l, err = is.ls.Get(layerID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
imageMeta := &imageMeta{
|
||||
layer: l,
|
||||
children: make(map[ID]struct{}),
|
||||
}
|
||||
|
||||
is.images[imageID] = imageMeta
|
||||
if err := is.digestSet.Add(digest.Digest(imageID)); err != nil {
|
||||
delete(is.images, imageID)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return imageID, nil
|
||||
}
|
||||
|
||||
func (is *store) Search(term string) (ID, error) {
|
||||
is.Lock()
|
||||
defer is.Unlock()
|
||||
|
||||
dgst, err := is.digestSet.Lookup(term)
|
||||
if err != nil {
|
||||
if err == digest.ErrDigestNotFound {
|
||||
err = fmt.Errorf("No such image: %s", term)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return ID(dgst), nil
|
||||
}
|
||||
|
||||
func (is *store) Get(id ID) (*Image, error) {
|
||||
// todo: Check if image is in images
|
||||
// todo: Detect manual insertions and start using them
|
||||
config, err := is.fs.Get(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
img, err := NewFromJSON(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
img.computedID = id
|
||||
|
||||
img.Parent, err = is.GetParent(id)
|
||||
if err != nil {
|
||||
img.Parent = ""
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func (is *store) Delete(id ID) ([]layer.Metadata, error) {
|
||||
is.Lock()
|
||||
defer is.Unlock()
|
||||
|
||||
imageMeta := is.images[id]
|
||||
if imageMeta == nil {
|
||||
return nil, fmt.Errorf("unrecognized image ID %s", id.String())
|
||||
}
|
||||
for id := range imageMeta.children {
|
||||
is.fs.DeleteMetadata(id, "parent")
|
||||
}
|
||||
if parent, err := is.GetParent(id); err == nil && is.images[parent] != nil {
|
||||
delete(is.images[parent].children, id)
|
||||
}
|
||||
|
||||
if err := is.digestSet.Remove(digest.Digest(id)); err != nil {
|
||||
logrus.Errorf("error removing %s from digest set: %q", id, err)
|
||||
}
|
||||
delete(is.images, id)
|
||||
is.fs.Delete(id)
|
||||
|
||||
if imageMeta.layer != nil {
|
||||
return is.ls.Release(imageMeta.layer)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (is *store) SetParent(id, parent ID) error {
|
||||
is.Lock()
|
||||
defer is.Unlock()
|
||||
parentMeta := is.images[parent]
|
||||
if parentMeta == nil {
|
||||
return fmt.Errorf("unknown parent image ID %s", parent.String())
|
||||
}
|
||||
if parent, err := is.GetParent(id); err == nil && is.images[parent] != nil {
|
||||
delete(is.images[parent].children, id)
|
||||
}
|
||||
parentMeta.children[id] = struct{}{}
|
||||
return is.fs.SetMetadata(id, "parent", []byte(parent))
|
||||
}
|
||||
|
||||
func (is *store) GetParent(id ID) (ID, error) {
|
||||
d, err := is.fs.GetMetadata(id, "parent")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ID(d), nil // todo: validate?
|
||||
}
|
||||
|
||||
func (is *store) Children(id ID) []ID {
|
||||
is.Lock()
|
||||
defer is.Unlock()
|
||||
|
||||
return is.children(id)
|
||||
}
|
||||
|
||||
func (is *store) children(id ID) []ID {
|
||||
var ids []ID
|
||||
if is.images[id] != nil {
|
||||
for id := range is.images[id].children {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func (is *store) Heads() map[ID]*Image {
|
||||
return is.imagesMap(false)
|
||||
}
|
||||
|
||||
func (is *store) Map() map[ID]*Image {
|
||||
return is.imagesMap(true)
|
||||
}
|
||||
|
||||
func (is *store) imagesMap(all bool) map[ID]*Image {
|
||||
is.Lock()
|
||||
defer is.Unlock()
|
||||
|
||||
images := make(map[ID]*Image)
|
||||
|
||||
for id := range is.images {
|
||||
if !all && len(is.children(id)) > 0 {
|
||||
continue
|
||||
}
|
||||
img, err := is.Get(id)
|
||||
if err != nil {
|
||||
logrus.Errorf("invalid image access: %q, error: %q", id, err)
|
||||
continue
|
||||
}
|
||||
images[id] = img
|
||||
}
|
||||
return images
|
||||
}
|
156
vendor/github.com/docker/docker/image/v1/imagev1.go
generated
vendored
Normal file
156
vendor/github.com/docker/docker/image/v1/imagev1.go
generated
vendored
Normal file
|
@ -0,0 +1,156 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/layer"
|
||||
"github.com/docker/engine-api/types/versions"
|
||||
)
|
||||
|
||||
var validHex = regexp.MustCompile(`^([a-f0-9]{64})$`)
|
||||
|
||||
// noFallbackMinVersion is the minimum version for which v1compatibility
|
||||
// information will not be marshaled through the Image struct to remove
|
||||
// blank fields.
|
||||
var noFallbackMinVersion = "1.8.3"
|
||||
|
||||
// HistoryFromConfig creates a History struct from v1 configuration JSON
|
||||
func HistoryFromConfig(imageJSON []byte, emptyLayer bool) (image.History, error) {
|
||||
h := image.History{}
|
||||
var v1Image image.V1Image
|
||||
if err := json.Unmarshal(imageJSON, &v1Image); err != nil {
|
||||
return h, err
|
||||
}
|
||||
|
||||
return image.History{
|
||||
Author: v1Image.Author,
|
||||
Created: v1Image.Created,
|
||||
CreatedBy: strings.Join(v1Image.ContainerConfig.Cmd, " "),
|
||||
Comment: v1Image.Comment,
|
||||
EmptyLayer: emptyLayer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateID creates an ID from v1 image, layerID and parent ID.
|
||||
// Used for backwards compatibility with old clients.
|
||||
func CreateID(v1Image image.V1Image, layerID layer.ChainID, parent digest.Digest) (digest.Digest, error) {
|
||||
v1Image.ID = ""
|
||||
v1JSON, err := json.Marshal(v1Image)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var config map[string]*json.RawMessage
|
||||
if err := json.Unmarshal(v1JSON, &config); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// FIXME: note that this is slightly incompatible with RootFS logic
|
||||
config["layer_id"] = rawJSON(layerID)
|
||||
if parent != "" {
|
||||
config["parent"] = rawJSON(parent)
|
||||
}
|
||||
|
||||
configJSON, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
logrus.Debugf("CreateV1ID %s", configJSON)
|
||||
|
||||
return digest.FromBytes(configJSON), nil
|
||||
}
|
||||
|
||||
// MakeConfigFromV1Config creates an image config from the legacy V1 config format.
|
||||
func MakeConfigFromV1Config(imageJSON []byte, rootfs *image.RootFS, history []image.History) ([]byte, error) {
|
||||
var dver struct {
|
||||
DockerVersion string `json:"docker_version"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(imageJSON, &dver); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
useFallback := versions.LessThan(dver.DockerVersion, noFallbackMinVersion)
|
||||
|
||||
if useFallback {
|
||||
var v1Image image.V1Image
|
||||
err := json.Unmarshal(imageJSON, &v1Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageJSON, err = json.Marshal(v1Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var c map[string]*json.RawMessage
|
||||
if err := json.Unmarshal(imageJSON, &c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delete(c, "id")
|
||||
delete(c, "parent")
|
||||
delete(c, "Size") // Size is calculated from data on disk and is inconsistent
|
||||
delete(c, "parent_id")
|
||||
delete(c, "layer_id")
|
||||
delete(c, "throwaway")
|
||||
|
||||
c["rootfs"] = rawJSON(rootfs)
|
||||
c["history"] = rawJSON(history)
|
||||
|
||||
return json.Marshal(c)
|
||||
}
|
||||
|
||||
// MakeV1ConfigFromConfig creates an legacy V1 image config from an Image struct
|
||||
func MakeV1ConfigFromConfig(img *image.Image, v1ID, parentV1ID string, throwaway bool) ([]byte, error) {
|
||||
// Top-level v1compatibility string should be a modified version of the
|
||||
// image config.
|
||||
var configAsMap map[string]*json.RawMessage
|
||||
if err := json.Unmarshal(img.RawJSON(), &configAsMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Delete fields that didn't exist in old manifest
|
||||
imageType := reflect.TypeOf(img).Elem()
|
||||
for i := 0; i < imageType.NumField(); i++ {
|
||||
f := imageType.Field(i)
|
||||
jsonName := strings.Split(f.Tag.Get("json"), ",")[0]
|
||||
// Parent is handled specially below.
|
||||
if jsonName != "" && jsonName != "parent" {
|
||||
delete(configAsMap, jsonName)
|
||||
}
|
||||
}
|
||||
configAsMap["id"] = rawJSON(v1ID)
|
||||
if parentV1ID != "" {
|
||||
configAsMap["parent"] = rawJSON(parentV1ID)
|
||||
}
|
||||
if throwaway {
|
||||
configAsMap["throwaway"] = rawJSON(true)
|
||||
}
|
||||
|
||||
return json.Marshal(configAsMap)
|
||||
}
|
||||
|
||||
func rawJSON(value interface{}) *json.RawMessage {
|
||||
jsonval, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return (*json.RawMessage)(&jsonval)
|
||||
}
|
||||
|
||||
// ValidateID checks whether an ID string is a valid image ID.
|
||||
func ValidateID(id string) error {
|
||||
if ok := validHex.MatchString(id); !ok {
|
||||
return fmt.Errorf("image ID %q is invalid", id)
|
||||
}
|
||||
return nil
|
||||
}
|
48
vendor/github.com/docker/docker/layer/empty.go
generated
vendored
Normal file
48
vendor/github.com/docker/docker/layer/empty.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
package layer
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// DigestSHA256EmptyTar is the canonical sha256 digest of empty tar file -
|
||||
// (1024 NULL bytes)
|
||||
const DigestSHA256EmptyTar = DiffID("sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef")
|
||||
|
||||
type emptyLayer struct{}
|
||||
|
||||
// EmptyLayer is a layer that corresponds to empty tar.
|
||||
var EmptyLayer = &emptyLayer{}
|
||||
|
||||
func (el *emptyLayer) TarStream() (io.ReadCloser, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
tarWriter := tar.NewWriter(buf)
|
||||
tarWriter.Close()
|
||||
return ioutil.NopCloser(buf), nil
|
||||
}
|
||||
|
||||
func (el *emptyLayer) ChainID() ChainID {
|
||||
return ChainID(DigestSHA256EmptyTar)
|
||||
}
|
||||
|
||||
func (el *emptyLayer) DiffID() DiffID {
|
||||
return DigestSHA256EmptyTar
|
||||
}
|
||||
|
||||
func (el *emptyLayer) Parent() Layer {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (el *emptyLayer) Size() (size int64, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (el *emptyLayer) DiffSize() (size int64, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (el *emptyLayer) Metadata() (map[string]string, error) {
|
||||
return make(map[string]string), nil
|
||||
}
|
354
vendor/github.com/docker/docker/layer/filestore.go
generated
vendored
Normal file
354
vendor/github.com/docker/docker/layer/filestore.go
generated
vendored
Normal file
|
@ -0,0 +1,354 @@
|
|||
package layer
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
)
|
||||
|
||||
var (
|
||||
stringIDRegexp = regexp.MustCompile(`^[a-f0-9]{64}(-init)?$`)
|
||||
supportedAlgorithms = []digest.Algorithm{
|
||||
digest.SHA256,
|
||||
// digest.SHA384, // Currently not used
|
||||
// digest.SHA512, // Currently not used
|
||||
}
|
||||
)
|
||||
|
||||
type fileMetadataStore struct {
|
||||
root string
|
||||
}
|
||||
|
||||
type fileMetadataTransaction struct {
|
||||
store *fileMetadataStore
|
||||
root string
|
||||
}
|
||||
|
||||
// NewFSMetadataStore returns an instance of a metadata store
|
||||
// which is backed by files on disk using the provided root
|
||||
// as the root of metadata files.
|
||||
func NewFSMetadataStore(root string) (MetadataStore, error) {
|
||||
if err := os.MkdirAll(root, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fileMetadataStore{
|
||||
root: root,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) getLayerDirectory(layer ChainID) string {
|
||||
dgst := digest.Digest(layer)
|
||||
return filepath.Join(fms.root, string(dgst.Algorithm()), dgst.Hex())
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) getLayerFilename(layer ChainID, filename string) string {
|
||||
return filepath.Join(fms.getLayerDirectory(layer), filename)
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) getMountDirectory(mount string) string {
|
||||
return filepath.Join(fms.root, "mounts", mount)
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) getMountFilename(mount, filename string) string {
|
||||
return filepath.Join(fms.getMountDirectory(mount), filename)
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) StartTransaction() (MetadataTransaction, error) {
|
||||
tmpDir := filepath.Join(fms.root, "tmp")
|
||||
if err := os.MkdirAll(tmpDir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
td, err := ioutil.TempDir(tmpDir, "layer-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create a new tempdir
|
||||
return &fileMetadataTransaction{
|
||||
store: fms,
|
||||
root: td,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) SetSize(size int64) error {
|
||||
content := fmt.Sprintf("%d", size)
|
||||
return ioutil.WriteFile(filepath.Join(fm.root, "size"), []byte(content), 0644)
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) SetParent(parent ChainID) error {
|
||||
return ioutil.WriteFile(filepath.Join(fm.root, "parent"), []byte(digest.Digest(parent).String()), 0644)
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) SetDiffID(diff DiffID) error {
|
||||
return ioutil.WriteFile(filepath.Join(fm.root, "diff"), []byte(digest.Digest(diff).String()), 0644)
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) SetCacheID(cacheID string) error {
|
||||
return ioutil.WriteFile(filepath.Join(fm.root, "cache-id"), []byte(cacheID), 0644)
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) SetDescriptor(ref distribution.Descriptor) error {
|
||||
jsonRef, err := json.Marshal(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(filepath.Join(fm.root, "descriptor.json"), jsonRef, 0644)
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) TarSplitWriter(compressInput bool) (io.WriteCloser, error) {
|
||||
f, err := os.OpenFile(filepath.Join(fm.root, "tar-split.json.gz"), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var wc io.WriteCloser
|
||||
if compressInput {
|
||||
wc = gzip.NewWriter(f)
|
||||
} else {
|
||||
wc = f
|
||||
}
|
||||
|
||||
return ioutils.NewWriteCloserWrapper(wc, func() error {
|
||||
wc.Close()
|
||||
return f.Close()
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) Commit(layer ChainID) error {
|
||||
finalDir := fm.store.getLayerDirectory(layer)
|
||||
if err := os.MkdirAll(filepath.Dir(finalDir), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(fm.root, finalDir)
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) Cancel() error {
|
||||
return os.RemoveAll(fm.root)
|
||||
}
|
||||
|
||||
func (fm *fileMetadataTransaction) String() string {
|
||||
return fm.root
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) GetSize(layer ChainID) (int64, error) {
|
||||
content, err := ioutil.ReadFile(fms.getLayerFilename(layer, "size"))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
size, err := strconv.ParseInt(string(content), 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) GetParent(layer ChainID) (ChainID, error) {
|
||||
content, err := ioutil.ReadFile(fms.getLayerFilename(layer, "parent"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
dgst, err := digest.ParseDigest(strings.TrimSpace(string(content)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return ChainID(dgst), nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) GetDiffID(layer ChainID) (DiffID, error) {
|
||||
content, err := ioutil.ReadFile(fms.getLayerFilename(layer, "diff"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dgst, err := digest.ParseDigest(strings.TrimSpace(string(content)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return DiffID(dgst), nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) GetCacheID(layer ChainID) (string, error) {
|
||||
contentBytes, err := ioutil.ReadFile(fms.getLayerFilename(layer, "cache-id"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
content := strings.TrimSpace(string(contentBytes))
|
||||
|
||||
if !stringIDRegexp.MatchString(content) {
|
||||
return "", errors.New("invalid cache id value")
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) GetDescriptor(layer ChainID) (distribution.Descriptor, error) {
|
||||
content, err := ioutil.ReadFile(fms.getLayerFilename(layer, "descriptor.json"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// only return empty descriptor to represent what is stored
|
||||
return distribution.Descriptor{}, nil
|
||||
}
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
|
||||
var ref distribution.Descriptor
|
||||
err = json.Unmarshal(content, &ref)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
return ref, err
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) TarSplitReader(layer ChainID) (io.ReadCloser, error) {
|
||||
fz, err := os.Open(fms.getLayerFilename(layer, "tar-split.json.gz"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := gzip.NewReader(fz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ioutils.NewReadCloserWrapper(f, func() error {
|
||||
f.Close()
|
||||
return fz.Close()
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) SetMountID(mount string, mountID string) error {
|
||||
if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(fms.getMountFilename(mount, "mount-id"), []byte(mountID), 0644)
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) SetInitID(mount string, init string) error {
|
||||
if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(fms.getMountFilename(mount, "init-id"), []byte(init), 0644)
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) SetMountParent(mount string, parent ChainID) error {
|
||||
if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(fms.getMountFilename(mount, "parent"), []byte(digest.Digest(parent).String()), 0644)
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) GetMountID(mount string) (string, error) {
|
||||
contentBytes, err := ioutil.ReadFile(fms.getMountFilename(mount, "mount-id"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
content := strings.TrimSpace(string(contentBytes))
|
||||
|
||||
if !stringIDRegexp.MatchString(content) {
|
||||
return "", errors.New("invalid mount id value")
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) GetInitID(mount string) (string, error) {
|
||||
contentBytes, err := ioutil.ReadFile(fms.getMountFilename(mount, "init-id"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
content := strings.TrimSpace(string(contentBytes))
|
||||
|
||||
if !stringIDRegexp.MatchString(content) {
|
||||
return "", errors.New("invalid init id value")
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) GetMountParent(mount string) (ChainID, error) {
|
||||
content, err := ioutil.ReadFile(fms.getMountFilename(mount, "parent"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
dgst, err := digest.ParseDigest(strings.TrimSpace(string(content)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return ChainID(dgst), nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) List() ([]ChainID, []string, error) {
|
||||
var ids []ChainID
|
||||
for _, algorithm := range supportedAlgorithms {
|
||||
fileInfos, err := ioutil.ReadDir(filepath.Join(fms.root, string(algorithm)))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, fi := range fileInfos {
|
||||
if fi.IsDir() && fi.Name() != "mounts" {
|
||||
dgst := digest.NewDigestFromHex(string(algorithm), fi.Name())
|
||||
if err := dgst.Validate(); err != nil {
|
||||
logrus.Debugf("Ignoring invalid digest %s:%s", algorithm, fi.Name())
|
||||
} else {
|
||||
ids = append(ids, ChainID(dgst))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileInfos, err := ioutil.ReadDir(filepath.Join(fms.root, "mounts"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return ids, []string{}, nil
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var mounts []string
|
||||
for _, fi := range fileInfos {
|
||||
if fi.IsDir() {
|
||||
mounts = append(mounts, fi.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return ids, mounts, nil
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) Remove(layer ChainID) error {
|
||||
return os.RemoveAll(fms.getLayerDirectory(layer))
|
||||
}
|
||||
|
||||
func (fms *fileMetadataStore) RemoveMount(mount string) error {
|
||||
return os.RemoveAll(fms.getMountDirectory(mount))
|
||||
}
|
270
vendor/github.com/docker/docker/layer/layer.go
generated
vendored
Normal file
270
vendor/github.com/docker/docker/layer/layer.go
generated
vendored
Normal file
|
@ -0,0 +1,270 @@
|
|||
// Package layer is package for managing read-only
|
||||
// and read-write mounts on the union file system
|
||||
// driver. Read-only mounts are referenced using a
|
||||
// content hash and are protected from mutation in
|
||||
// the exposed interface. The tar format is used
|
||||
// to create read-only layers and export both
|
||||
// read-only and writable layers. The exported
|
||||
// tar data for a read-only layer should match
|
||||
// the tar used to create the layer.
|
||||
package layer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrLayerDoesNotExist is used when an operation is
|
||||
// attempted on a layer which does not exist.
|
||||
ErrLayerDoesNotExist = errors.New("layer does not exist")
|
||||
|
||||
// ErrLayerNotRetained is used when a release is
|
||||
// attempted on a layer which is not retained.
|
||||
ErrLayerNotRetained = errors.New("layer not retained")
|
||||
|
||||
// ErrMountDoesNotExist is used when an operation is
|
||||
// attempted on a mount layer which does not exist.
|
||||
ErrMountDoesNotExist = errors.New("mount does not exist")
|
||||
|
||||
// ErrMountNameConflict is used when a mount is attempted
|
||||
// to be created but there is already a mount with the name
|
||||
// used for creation.
|
||||
ErrMountNameConflict = errors.New("mount already exists with name")
|
||||
|
||||
// ErrActiveMount is used when an operation on a
|
||||
// mount is attempted but the layer is still
|
||||
// mounted and the operation cannot be performed.
|
||||
ErrActiveMount = errors.New("mount still active")
|
||||
|
||||
// ErrNotMounted is used when requesting an active
|
||||
// mount but the layer is not mounted.
|
||||
ErrNotMounted = errors.New("not mounted")
|
||||
|
||||
// ErrMaxDepthExceeded is used when a layer is attempted
|
||||
// to be created which would result in a layer depth
|
||||
// greater than the 125 max.
|
||||
ErrMaxDepthExceeded = errors.New("max depth exceeded")
|
||||
|
||||
// ErrNotSupported is used when the action is not supported
|
||||
// on the current platform
|
||||
ErrNotSupported = errors.New("not support on this platform")
|
||||
)
|
||||
|
||||
// ChainID is the content-addressable ID of a layer.
|
||||
type ChainID digest.Digest
|
||||
|
||||
// String returns a string rendition of a layer ID
|
||||
func (id ChainID) String() string {
|
||||
return string(id)
|
||||
}
|
||||
|
||||
// DiffID is the hash of an individual layer tar.
|
||||
type DiffID digest.Digest
|
||||
|
||||
// String returns a string rendition of a layer DiffID
|
||||
func (diffID DiffID) String() string {
|
||||
return string(diffID)
|
||||
}
|
||||
|
||||
// TarStreamer represents an object which may
|
||||
// have its contents exported as a tar stream.
|
||||
type TarStreamer interface {
|
||||
// TarStream returns a tar archive stream
|
||||
// for the contents of a layer.
|
||||
TarStream() (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// Layer represents a read-only layer
|
||||
type Layer interface {
|
||||
TarStreamer
|
||||
|
||||
// ChainID returns the content hash of the entire layer chain. The hash
|
||||
// chain is made up of DiffID of top layer and all of its parents.
|
||||
ChainID() ChainID
|
||||
|
||||
// DiffID returns the content hash of the layer
|
||||
// tar stream used to create this layer.
|
||||
DiffID() DiffID
|
||||
|
||||
// Parent returns the next layer in the layer chain.
|
||||
Parent() Layer
|
||||
|
||||
// Size returns the size of the entire layer chain. The size
|
||||
// is calculated from the total size of all files in the layers.
|
||||
Size() (int64, error)
|
||||
|
||||
// DiffSize returns the size difference of the top layer
|
||||
// from parent layer.
|
||||
DiffSize() (int64, error)
|
||||
|
||||
// Metadata returns the low level storage metadata associated
|
||||
// with layer.
|
||||
Metadata() (map[string]string, error)
|
||||
}
|
||||
|
||||
// RWLayer represents a layer which is
|
||||
// read and writable
|
||||
type RWLayer interface {
|
||||
TarStreamer
|
||||
|
||||
// Name of mounted layer
|
||||
Name() string
|
||||
|
||||
// Parent returns the layer which the writable
|
||||
// layer was created from.
|
||||
Parent() Layer
|
||||
|
||||
// Mount mounts the RWLayer and returns the filesystem path
|
||||
// the to the writable layer.
|
||||
Mount(mountLabel string) (string, error)
|
||||
|
||||
// Unmount unmounts the RWLayer. This should be called
|
||||
// for every mount. If there are multiple mount calls
|
||||
// this operation will only decrement the internal mount counter.
|
||||
Unmount() error
|
||||
|
||||
// Size represents the size of the writable layer
|
||||
// as calculated by the total size of the files
|
||||
// changed in the mutable layer.
|
||||
Size() (int64, error)
|
||||
|
||||
// Changes returns the set of changes for the mutable layer
|
||||
// from the base layer.
|
||||
Changes() ([]archive.Change, error)
|
||||
|
||||
// Metadata returns the low level metadata for the mutable layer
|
||||
Metadata() (map[string]string, error)
|
||||
}
|
||||
|
||||
// Metadata holds information about a
|
||||
// read-only layer
|
||||
type Metadata struct {
|
||||
// ChainID is the content hash of the layer
|
||||
ChainID ChainID
|
||||
|
||||
// DiffID is the hash of the tar data used to
|
||||
// create the layer
|
||||
DiffID DiffID
|
||||
|
||||
// Size is the size of the layer and all parents
|
||||
Size int64
|
||||
|
||||
// DiffSize is the size of the top layer
|
||||
DiffSize int64
|
||||
}
|
||||
|
||||
// MountInit is a function to initialize a
|
||||
// writable mount. Changes made here will
|
||||
// not be included in the Tar stream of the
|
||||
// RWLayer.
|
||||
type MountInit func(root string) error
|
||||
|
||||
// Store represents a backend for managing both
|
||||
// read-only and read-write layers.
|
||||
type Store interface {
|
||||
Register(io.Reader, ChainID) (Layer, error)
|
||||
Get(ChainID) (Layer, error)
|
||||
Release(Layer) ([]Metadata, error)
|
||||
|
||||
CreateRWLayer(id string, parent ChainID, mountLabel string, initFunc MountInit, storageOpt map[string]string) (RWLayer, error)
|
||||
GetRWLayer(id string) (RWLayer, error)
|
||||
GetMountID(id string) (string, error)
|
||||
ReleaseRWLayer(RWLayer) ([]Metadata, error)
|
||||
|
||||
Cleanup() error
|
||||
DriverStatus() [][2]string
|
||||
DriverName() string
|
||||
}
|
||||
|
||||
// DescribableStore represents a layer store capable of storing
|
||||
// descriptors for layers.
|
||||
type DescribableStore interface {
|
||||
RegisterWithDescriptor(io.Reader, ChainID, distribution.Descriptor) (Layer, error)
|
||||
}
|
||||
|
||||
// MetadataTransaction represents functions for setting layer metadata
|
||||
// with a single transaction.
|
||||
type MetadataTransaction interface {
|
||||
SetSize(int64) error
|
||||
SetParent(parent ChainID) error
|
||||
SetDiffID(DiffID) error
|
||||
SetCacheID(string) error
|
||||
SetDescriptor(distribution.Descriptor) error
|
||||
TarSplitWriter(compressInput bool) (io.WriteCloser, error)
|
||||
|
||||
Commit(ChainID) error
|
||||
Cancel() error
|
||||
String() string
|
||||
}
|
||||
|
||||
// MetadataStore represents a backend for persisting
|
||||
// metadata about layers and providing the metadata
|
||||
// for restoring a Store.
|
||||
type MetadataStore interface {
|
||||
// StartTransaction starts an update for new metadata
|
||||
// which will be used to represent an ID on commit.
|
||||
StartTransaction() (MetadataTransaction, error)
|
||||
|
||||
GetSize(ChainID) (int64, error)
|
||||
GetParent(ChainID) (ChainID, error)
|
||||
GetDiffID(ChainID) (DiffID, error)
|
||||
GetCacheID(ChainID) (string, error)
|
||||
GetDescriptor(ChainID) (distribution.Descriptor, error)
|
||||
TarSplitReader(ChainID) (io.ReadCloser, error)
|
||||
|
||||
SetMountID(string, string) error
|
||||
SetInitID(string, string) error
|
||||
SetMountParent(string, ChainID) error
|
||||
|
||||
GetMountID(string) (string, error)
|
||||
GetInitID(string) (string, error)
|
||||
GetMountParent(string) (ChainID, error)
|
||||
|
||||
// List returns the full list of referenced
|
||||
// read-only and read-write layers
|
||||
List() ([]ChainID, []string, error)
|
||||
|
||||
Remove(ChainID) error
|
||||
RemoveMount(string) error
|
||||
}
|
||||
|
||||
// CreateChainID returns ID for a layerDigest slice
|
||||
func CreateChainID(dgsts []DiffID) ChainID {
|
||||
return createChainIDFromParent("", dgsts...)
|
||||
}
|
||||
|
||||
func createChainIDFromParent(parent ChainID, dgsts ...DiffID) ChainID {
|
||||
if len(dgsts) == 0 {
|
||||
return parent
|
||||
}
|
||||
if parent == "" {
|
||||
return createChainIDFromParent(ChainID(dgsts[0]), dgsts[1:]...)
|
||||
}
|
||||
// H = "H(n-1) SHA256(n)"
|
||||
dgst := digest.FromBytes([]byte(string(parent) + " " + string(dgsts[0])))
|
||||
return createChainIDFromParent(ChainID(dgst), dgsts[1:]...)
|
||||
}
|
||||
|
||||
// ReleaseAndLog releases the provided layer from the given layer
|
||||
// store, logging any error and release metadata
|
||||
func ReleaseAndLog(ls Store, l Layer) {
|
||||
metadata, err := ls.Release(l)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error releasing layer %s: %v", l.ChainID(), err)
|
||||
}
|
||||
LogReleaseMetadata(metadata)
|
||||
}
|
||||
|
||||
// LogReleaseMetadata logs a metadata array, uses this to
|
||||
// ensure consistent logging for release metadata
|
||||
func LogReleaseMetadata(metadatas []Metadata) {
|
||||
for _, metadata := range metadatas {
|
||||
logrus.Infof("Layer %s cleaned up", metadata.ChainID)
|
||||
}
|
||||
}
|
659
vendor/github.com/docker/docker/layer/layer_store.go
generated
vendored
Normal file
659
vendor/github.com/docker/docker/layer/layer_store.go
generated
vendored
Normal file
|
@ -0,0 +1,659 @@
|
|||
package layer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/vbatts/tar-split/tar/asm"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
)
|
||||
|
||||
// maxLayerDepth represents the maximum number of
|
||||
// layers which can be chained together. 125 was
|
||||
// chosen to account for the 127 max in some
|
||||
// graphdrivers plus the 2 additional layers
|
||||
// used to create a rwlayer.
|
||||
const maxLayerDepth = 125
|
||||
|
||||
type layerStore struct {
|
||||
store MetadataStore
|
||||
driver graphdriver.Driver
|
||||
|
||||
layerMap map[ChainID]*roLayer
|
||||
layerL sync.Mutex
|
||||
|
||||
mounts map[string]*mountedLayer
|
||||
mountL sync.Mutex
|
||||
}
|
||||
|
||||
// StoreOptions are the options used to create a new Store instance
|
||||
type StoreOptions struct {
|
||||
StorePath string
|
||||
MetadataStorePathTemplate string
|
||||
GraphDriver string
|
||||
GraphDriverOptions []string
|
||||
UIDMaps []idtools.IDMap
|
||||
GIDMaps []idtools.IDMap
|
||||
}
|
||||
|
||||
// NewStoreFromOptions creates a new Store instance
|
||||
func NewStoreFromOptions(options StoreOptions) (Store, error) {
|
||||
driver, err := graphdriver.New(
|
||||
options.StorePath,
|
||||
options.GraphDriver,
|
||||
options.GraphDriverOptions,
|
||||
options.UIDMaps,
|
||||
options.GIDMaps)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error initializing graphdriver: %v", err)
|
||||
}
|
||||
logrus.Debugf("Using graph driver %s", driver)
|
||||
|
||||
fms, err := NewFSMetadataStore(fmt.Sprintf(options.MetadataStorePathTemplate, driver))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewStoreFromGraphDriver(fms, driver)
|
||||
}
|
||||
|
||||
// NewStoreFromGraphDriver creates a new Store instance using the provided
|
||||
// metadata store and graph driver. The metadata store will be used to restore
|
||||
// the Store.
|
||||
func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver) (Store, error) {
|
||||
ls := &layerStore{
|
||||
store: store,
|
||||
driver: driver,
|
||||
layerMap: map[ChainID]*roLayer{},
|
||||
mounts: map[string]*mountedLayer{},
|
||||
}
|
||||
|
||||
ids, mounts, err := store.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
l, err := ls.loadLayer(id)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to load layer %s: %s", id, err)
|
||||
continue
|
||||
}
|
||||
if l.parent != nil {
|
||||
l.parent.referenceCount++
|
||||
}
|
||||
}
|
||||
|
||||
for _, mount := range mounts {
|
||||
if err := ls.loadMount(mount); err != nil {
|
||||
logrus.Debugf("Failed to load mount %s: %s", mount, err)
|
||||
}
|
||||
}
|
||||
|
||||
return ls, nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) loadLayer(layer ChainID) (*roLayer, error) {
|
||||
cl, ok := ls.layerMap[layer]
|
||||
if ok {
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
diff, err := ls.store.GetDiffID(layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get diff id for %s: %s", layer, err)
|
||||
}
|
||||
|
||||
size, err := ls.store.GetSize(layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get size for %s: %s", layer, err)
|
||||
}
|
||||
|
||||
cacheID, err := ls.store.GetCacheID(layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get cache id for %s: %s", layer, err)
|
||||
}
|
||||
|
||||
parent, err := ls.store.GetParent(layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get parent for %s: %s", layer, err)
|
||||
}
|
||||
|
||||
descriptor, err := ls.store.GetDescriptor(layer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get descriptor for %s: %s", layer, err)
|
||||
}
|
||||
|
||||
cl = &roLayer{
|
||||
chainID: layer,
|
||||
diffID: diff,
|
||||
size: size,
|
||||
cacheID: cacheID,
|
||||
layerStore: ls,
|
||||
references: map[Layer]struct{}{},
|
||||
descriptor: descriptor,
|
||||
}
|
||||
|
||||
if parent != "" {
|
||||
p, err := ls.loadLayer(parent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cl.parent = p
|
||||
}
|
||||
|
||||
ls.layerMap[cl.chainID] = cl
|
||||
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) loadMount(mount string) error {
|
||||
if _, ok := ls.mounts[mount]; ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
mountID, err := ls.store.GetMountID(mount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
initID, err := ls.store.GetInitID(mount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parent, err := ls.store.GetMountParent(mount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ml := &mountedLayer{
|
||||
name: mount,
|
||||
mountID: mountID,
|
||||
initID: initID,
|
||||
layerStore: ls,
|
||||
references: map[RWLayer]*referencedRWLayer{},
|
||||
}
|
||||
|
||||
if parent != "" {
|
||||
p, err := ls.loadLayer(parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ml.parent = p
|
||||
|
||||
p.referenceCount++
|
||||
}
|
||||
|
||||
ls.mounts[ml.name] = ml
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) applyTar(tx MetadataTransaction, ts io.Reader, parent string, layer *roLayer) error {
|
||||
digester := digest.Canonical.New()
|
||||
tr := io.TeeReader(ts, digester.Hash())
|
||||
|
||||
tsw, err := tx.TarSplitWriter(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
metaPacker := storage.NewJSONPacker(tsw)
|
||||
defer tsw.Close()
|
||||
|
||||
// we're passing nil here for the file putter, because the ApplyDiff will
|
||||
// handle the extraction of the archive
|
||||
rdr, err := asm.NewInputTarStream(tr, metaPacker, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
applySize, err := ls.driver.ApplyDiff(layer.cacheID, parent, archive.Reader(rdr))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Discard trailing data but ensure metadata is picked up to reconstruct stream
|
||||
io.Copy(ioutil.Discard, rdr) // ignore error as reader may be closed
|
||||
|
||||
layer.size = applySize
|
||||
layer.diffID = DiffID(digester.Digest())
|
||||
|
||||
logrus.Debugf("Applied tar %s to %s, size: %d", layer.diffID, layer.cacheID, applySize)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) Register(ts io.Reader, parent ChainID) (Layer, error) {
|
||||
return ls.registerWithDescriptor(ts, parent, distribution.Descriptor{})
|
||||
}
|
||||
|
||||
func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, descriptor distribution.Descriptor) (Layer, error) {
|
||||
// err is used to hold the error which will always trigger
|
||||
// cleanup of creates sources but may not be an error returned
|
||||
// to the caller (already exists).
|
||||
var err error
|
||||
var pid string
|
||||
var p *roLayer
|
||||
if string(parent) != "" {
|
||||
p = ls.get(parent)
|
||||
if p == nil {
|
||||
return nil, ErrLayerDoesNotExist
|
||||
}
|
||||
pid = p.cacheID
|
||||
// Release parent chain if error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
ls.layerL.Lock()
|
||||
ls.releaseLayer(p)
|
||||
ls.layerL.Unlock()
|
||||
}
|
||||
}()
|
||||
if p.depth() >= maxLayerDepth {
|
||||
err = ErrMaxDepthExceeded
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Create new roLayer
|
||||
layer := &roLayer{
|
||||
parent: p,
|
||||
cacheID: stringid.GenerateRandomID(),
|
||||
referenceCount: 1,
|
||||
layerStore: ls,
|
||||
references: map[Layer]struct{}{},
|
||||
descriptor: descriptor,
|
||||
}
|
||||
|
||||
if err = ls.driver.Create(layer.cacheID, pid, "", nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx, err := ls.store.StartTransaction()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
logrus.Debugf("Cleaning up layer %s: %v", layer.cacheID, err)
|
||||
if err := ls.driver.Remove(layer.cacheID); err != nil {
|
||||
logrus.Errorf("Error cleaning up cache layer %s: %v", layer.cacheID, err)
|
||||
}
|
||||
if err := tx.Cancel(); err != nil {
|
||||
logrus.Errorf("Error canceling metadata transaction %q: %s", tx.String(), err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err = ls.applyTar(tx, ts, pid, layer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if layer.parent == nil {
|
||||
layer.chainID = ChainID(layer.diffID)
|
||||
} else {
|
||||
layer.chainID = createChainIDFromParent(layer.parent.chainID, layer.diffID)
|
||||
}
|
||||
|
||||
if err = storeLayer(tx, layer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ls.layerL.Lock()
|
||||
defer ls.layerL.Unlock()
|
||||
|
||||
if existingLayer := ls.getWithoutLock(layer.chainID); existingLayer != nil {
|
||||
// Set error for cleanup, but do not return the error
|
||||
err = errors.New("layer already exists")
|
||||
return existingLayer.getReference(), nil
|
||||
}
|
||||
|
||||
if err = tx.Commit(layer.chainID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ls.layerMap[layer.chainID] = layer
|
||||
|
||||
return layer.getReference(), nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) getWithoutLock(layer ChainID) *roLayer {
|
||||
l, ok := ls.layerMap[layer]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.referenceCount++
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
func (ls *layerStore) get(l ChainID) *roLayer {
|
||||
ls.layerL.Lock()
|
||||
defer ls.layerL.Unlock()
|
||||
return ls.getWithoutLock(l)
|
||||
}
|
||||
|
||||
func (ls *layerStore) Get(l ChainID) (Layer, error) {
|
||||
ls.layerL.Lock()
|
||||
defer ls.layerL.Unlock()
|
||||
|
||||
layer := ls.getWithoutLock(l)
|
||||
if layer == nil {
|
||||
return nil, ErrLayerDoesNotExist
|
||||
}
|
||||
|
||||
return layer.getReference(), nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) deleteLayer(layer *roLayer, metadata *Metadata) error {
|
||||
err := ls.driver.Remove(layer.cacheID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ls.store.Remove(layer.chainID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
metadata.DiffID = layer.diffID
|
||||
metadata.ChainID = layer.chainID
|
||||
metadata.Size, err = layer.Size()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
metadata.DiffSize = layer.size
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) releaseLayer(l *roLayer) ([]Metadata, error) {
|
||||
depth := 0
|
||||
removed := []Metadata{}
|
||||
for {
|
||||
if l.referenceCount == 0 {
|
||||
panic("layer not retained")
|
||||
}
|
||||
l.referenceCount--
|
||||
if l.referenceCount != 0 {
|
||||
return removed, nil
|
||||
}
|
||||
|
||||
if len(removed) == 0 && depth > 0 {
|
||||
panic("cannot remove layer with child")
|
||||
}
|
||||
if l.hasReferences() {
|
||||
panic("cannot delete referenced layer")
|
||||
}
|
||||
var metadata Metadata
|
||||
if err := ls.deleteLayer(l, &metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delete(ls.layerMap, l.chainID)
|
||||
removed = append(removed, metadata)
|
||||
|
||||
if l.parent == nil {
|
||||
return removed, nil
|
||||
}
|
||||
|
||||
depth++
|
||||
l = l.parent
|
||||
}
|
||||
}
|
||||
|
||||
func (ls *layerStore) Release(l Layer) ([]Metadata, error) {
|
||||
ls.layerL.Lock()
|
||||
defer ls.layerL.Unlock()
|
||||
layer, ok := ls.layerMap[l.ChainID()]
|
||||
if !ok {
|
||||
return []Metadata{}, nil
|
||||
}
|
||||
if !layer.hasReference(l) {
|
||||
return nil, ErrLayerNotRetained
|
||||
}
|
||||
|
||||
layer.deleteReference(l)
|
||||
|
||||
return ls.releaseLayer(layer)
|
||||
}
|
||||
|
||||
func (ls *layerStore) CreateRWLayer(name string, parent ChainID, mountLabel string, initFunc MountInit, storageOpt map[string]string) (RWLayer, error) {
|
||||
ls.mountL.Lock()
|
||||
defer ls.mountL.Unlock()
|
||||
m, ok := ls.mounts[name]
|
||||
if ok {
|
||||
return nil, ErrMountNameConflict
|
||||
}
|
||||
|
||||
var err error
|
||||
var pid string
|
||||
var p *roLayer
|
||||
if string(parent) != "" {
|
||||
p = ls.get(parent)
|
||||
if p == nil {
|
||||
return nil, ErrLayerDoesNotExist
|
||||
}
|
||||
pid = p.cacheID
|
||||
|
||||
// Release parent chain if error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
ls.layerL.Lock()
|
||||
ls.releaseLayer(p)
|
||||
ls.layerL.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
m = &mountedLayer{
|
||||
name: name,
|
||||
parent: p,
|
||||
mountID: ls.mountID(name),
|
||||
layerStore: ls,
|
||||
references: map[RWLayer]*referencedRWLayer{},
|
||||
}
|
||||
|
||||
if initFunc != nil {
|
||||
pid, err = ls.initMount(m.mountID, pid, mountLabel, initFunc, storageOpt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.initID = pid
|
||||
}
|
||||
|
||||
if err = ls.driver.CreateReadWrite(m.mountID, pid, "", storageOpt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = ls.saveMount(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m.getReference(), nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) GetRWLayer(id string) (RWLayer, error) {
|
||||
ls.mountL.Lock()
|
||||
defer ls.mountL.Unlock()
|
||||
mount, ok := ls.mounts[id]
|
||||
if !ok {
|
||||
return nil, ErrMountDoesNotExist
|
||||
}
|
||||
|
||||
return mount.getReference(), nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) GetMountID(id string) (string, error) {
|
||||
ls.mountL.Lock()
|
||||
defer ls.mountL.Unlock()
|
||||
mount, ok := ls.mounts[id]
|
||||
if !ok {
|
||||
return "", ErrMountDoesNotExist
|
||||
}
|
||||
logrus.Debugf("GetMountID id: %s -> mountID: %s", id, mount.mountID)
|
||||
|
||||
return mount.mountID, nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) ReleaseRWLayer(l RWLayer) ([]Metadata, error) {
|
||||
ls.mountL.Lock()
|
||||
defer ls.mountL.Unlock()
|
||||
m, ok := ls.mounts[l.Name()]
|
||||
if !ok {
|
||||
return []Metadata{}, nil
|
||||
}
|
||||
|
||||
if err := m.deleteReference(l); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if m.hasReferences() {
|
||||
return []Metadata{}, nil
|
||||
}
|
||||
|
||||
if err := ls.driver.Remove(m.mountID); err != nil {
|
||||
logrus.Errorf("Error removing mounted layer %s: %s", m.name, err)
|
||||
m.retakeReference(l)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if m.initID != "" {
|
||||
if err := ls.driver.Remove(m.initID); err != nil {
|
||||
logrus.Errorf("Error removing init layer %s: %s", m.name, err)
|
||||
m.retakeReference(l)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := ls.store.RemoveMount(m.name); err != nil {
|
||||
logrus.Errorf("Error removing mount metadata: %s: %s", m.name, err)
|
||||
m.retakeReference(l)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delete(ls.mounts, m.Name())
|
||||
|
||||
ls.layerL.Lock()
|
||||
defer ls.layerL.Unlock()
|
||||
if m.parent != nil {
|
||||
return ls.releaseLayer(m.parent)
|
||||
}
|
||||
|
||||
return []Metadata{}, nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) saveMount(mount *mountedLayer) error {
|
||||
if err := ls.store.SetMountID(mount.name, mount.mountID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if mount.initID != "" {
|
||||
if err := ls.store.SetInitID(mount.name, mount.initID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if mount.parent != nil {
|
||||
if err := ls.store.SetMountParent(mount.name, mount.parent.chainID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ls.mounts[mount.name] = mount
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) initMount(graphID, parent, mountLabel string, initFunc MountInit, storageOpt map[string]string) (string, error) {
|
||||
// Use "<graph-id>-init" to maintain compatibility with graph drivers
|
||||
// which are expecting this layer with this special name. If all
|
||||
// graph drivers can be updated to not rely on knowing about this layer
|
||||
// then the initID should be randomly generated.
|
||||
initID := fmt.Sprintf("%s-init", graphID)
|
||||
|
||||
if err := ls.driver.Create(initID, parent, mountLabel, storageOpt); err != nil {
|
||||
return "", err
|
||||
}
|
||||
p, err := ls.driver.Get(initID, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := initFunc(p); err != nil {
|
||||
ls.driver.Put(initID)
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := ls.driver.Put(initID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return initID, nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) assembleTarTo(graphID string, metadata io.ReadCloser, size *int64, w io.Writer) error {
|
||||
diffDriver, ok := ls.driver.(graphdriver.DiffGetterDriver)
|
||||
if !ok {
|
||||
diffDriver = &naiveDiffPathDriver{ls.driver}
|
||||
}
|
||||
|
||||
defer metadata.Close()
|
||||
|
||||
// get our relative path to the container
|
||||
fileGetCloser, err := diffDriver.DiffGetter(graphID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileGetCloser.Close()
|
||||
|
||||
metaUnpacker := storage.NewJSONUnpacker(metadata)
|
||||
upackerCounter := &unpackSizeCounter{metaUnpacker, size}
|
||||
logrus.Debugf("Assembling tar data for %s", graphID)
|
||||
return asm.WriteOutputTarStream(fileGetCloser, upackerCounter, w)
|
||||
}
|
||||
|
||||
func (ls *layerStore) Cleanup() error {
|
||||
return ls.driver.Cleanup()
|
||||
}
|
||||
|
||||
func (ls *layerStore) DriverStatus() [][2]string {
|
||||
return ls.driver.Status()
|
||||
}
|
||||
|
||||
func (ls *layerStore) DriverName() string {
|
||||
return ls.driver.String()
|
||||
}
|
||||
|
||||
type naiveDiffPathDriver struct {
|
||||
graphdriver.Driver
|
||||
}
|
||||
|
||||
type fileGetPutter struct {
|
||||
storage.FileGetter
|
||||
driver graphdriver.Driver
|
||||
id string
|
||||
}
|
||||
|
||||
func (w *fileGetPutter) Close() error {
|
||||
return w.driver.Put(w.id)
|
||||
}
|
||||
|
||||
func (n *naiveDiffPathDriver) DiffGetter(id string) (graphdriver.FileGetCloser, error) {
|
||||
p, err := n.Driver.Get(id, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fileGetPutter{storage.NewPathFileGetter(p), n.Driver, id}, nil
|
||||
}
|
11
vendor/github.com/docker/docker/layer/layer_store_windows.go
generated
vendored
Normal file
11
vendor/github.com/docker/docker/layer/layer_store_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
package layer
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/docker/distribution"
|
||||
)
|
||||
|
||||
func (ls *layerStore) RegisterWithDescriptor(ts io.Reader, parent ChainID, descriptor distribution.Descriptor) (Layer, error) {
|
||||
return ls.registerWithDescriptor(ts, parent, descriptor)
|
||||
}
|
9
vendor/github.com/docker/docker/layer/layer_unix.go
generated
vendored
Normal file
9
vendor/github.com/docker/docker/layer/layer_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
// +build linux freebsd darwin openbsd solaris
|
||||
|
||||
package layer
|
||||
|
||||
import "github.com/docker/docker/pkg/stringid"
|
||||
|
||||
func (ls *layerStore) mountID(name string) string {
|
||||
return stringid.GenerateRandomID()
|
||||
}
|
98
vendor/github.com/docker/docker/layer/layer_windows.go
generated
vendored
Normal file
98
vendor/github.com/docker/docker/layer/layer_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
package layer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
)
|
||||
|
||||
// GetLayerPath returns the path to a layer
|
||||
func GetLayerPath(s Store, layer ChainID) (string, error) {
|
||||
ls, ok := s.(*layerStore)
|
||||
if !ok {
|
||||
return "", errors.New("unsupported layer store")
|
||||
}
|
||||
ls.layerL.Lock()
|
||||
defer ls.layerL.Unlock()
|
||||
|
||||
rl, ok := ls.layerMap[layer]
|
||||
if !ok {
|
||||
return "", ErrLayerDoesNotExist
|
||||
}
|
||||
|
||||
path, err := ls.driver.Get(rl.cacheID, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := ls.driver.Put(rl.cacheID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) RegisterDiffID(graphID string, size int64) (Layer, error) {
|
||||
var err error // this is used for cleanup in existingLayer case
|
||||
diffID := digest.FromBytes([]byte(graphID))
|
||||
|
||||
// Create new roLayer
|
||||
layer := &roLayer{
|
||||
cacheID: graphID,
|
||||
diffID: DiffID(diffID),
|
||||
referenceCount: 1,
|
||||
layerStore: ls,
|
||||
references: map[Layer]struct{}{},
|
||||
size: size,
|
||||
}
|
||||
|
||||
tx, err := ls.store.StartTransaction()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if err := tx.Cancel(); err != nil {
|
||||
logrus.Errorf("Error canceling metadata transaction %q: %s", tx.String(), err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
layer.chainID = createChainIDFromParent("", layer.diffID)
|
||||
|
||||
if !ls.driver.Exists(layer.cacheID) {
|
||||
return nil, fmt.Errorf("layer %q is unknown to driver", layer.cacheID)
|
||||
}
|
||||
if err = storeLayer(tx, layer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ls.layerL.Lock()
|
||||
defer ls.layerL.Unlock()
|
||||
|
||||
if existingLayer := ls.getWithoutLock(layer.chainID); existingLayer != nil {
|
||||
// Set error for cleanup, but do not return
|
||||
err = errors.New("layer already exists")
|
||||
return existingLayer.getReference(), nil
|
||||
}
|
||||
|
||||
if err = tx.Commit(layer.chainID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ls.layerMap[layer.chainID] = layer
|
||||
|
||||
return layer.getReference(), nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) mountID(name string) string {
|
||||
// windows has issues if container ID doesn't match mount ID
|
||||
return name
|
||||
}
|
||||
|
||||
func (ls *layerStore) GraphDriver() graphdriver.Driver {
|
||||
return ls.driver
|
||||
}
|
256
vendor/github.com/docker/docker/layer/migration.go
generated
vendored
Normal file
256
vendor/github.com/docker/docker/layer/migration.go
generated
vendored
Normal file
|
@ -0,0 +1,256 @@
|
|||
package layer
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/vbatts/tar-split/tar/asm"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
)
|
||||
|
||||
// CreateRWLayerByGraphID creates a RWLayer in the layer store using
|
||||
// the provided name with the given graphID. To get the RWLayer
|
||||
// after migration the layer may be retrieved by the given name.
|
||||
func (ls *layerStore) CreateRWLayerByGraphID(name string, graphID string, parent ChainID) (err error) {
|
||||
ls.mountL.Lock()
|
||||
defer ls.mountL.Unlock()
|
||||
m, ok := ls.mounts[name]
|
||||
if ok {
|
||||
if m.parent.chainID != parent {
|
||||
return errors.New("name conflict, mismatched parent")
|
||||
}
|
||||
if m.mountID != graphID {
|
||||
return errors.New("mount already exists")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if !ls.driver.Exists(graphID) {
|
||||
return fmt.Errorf("graph ID does not exist: %q", graphID)
|
||||
}
|
||||
|
||||
var p *roLayer
|
||||
if string(parent) != "" {
|
||||
p = ls.get(parent)
|
||||
if p == nil {
|
||||
return ErrLayerDoesNotExist
|
||||
}
|
||||
|
||||
// Release parent chain if error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
ls.layerL.Lock()
|
||||
ls.releaseLayer(p)
|
||||
ls.layerL.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// TODO: Ensure graphID has correct parent
|
||||
|
||||
m = &mountedLayer{
|
||||
name: name,
|
||||
parent: p,
|
||||
mountID: graphID,
|
||||
layerStore: ls,
|
||||
references: map[RWLayer]*referencedRWLayer{},
|
||||
}
|
||||
|
||||
// Check for existing init layer
|
||||
initID := fmt.Sprintf("%s-init", graphID)
|
||||
if ls.driver.Exists(initID) {
|
||||
m.initID = initID
|
||||
}
|
||||
|
||||
if err = ls.saveMount(m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *layerStore) ChecksumForGraphID(id, parent, oldTarDataPath, newTarDataPath string) (diffID DiffID, size int64, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
logrus.Debugf("could not get checksum for %q with tar-split: %q", id, err)
|
||||
diffID, size, err = ls.checksumForGraphIDNoTarsplit(id, parent, newTarDataPath)
|
||||
}
|
||||
}()
|
||||
|
||||
if oldTarDataPath == "" {
|
||||
err = errors.New("no tar-split file")
|
||||
return
|
||||
}
|
||||
|
||||
tarDataFile, err := os.Open(oldTarDataPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer tarDataFile.Close()
|
||||
uncompressed, err := gzip.NewReader(tarDataFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dgst := digest.Canonical.New()
|
||||
err = ls.assembleTarTo(id, uncompressed, &size, dgst.Hash())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
diffID = DiffID(dgst.Digest())
|
||||
err = os.RemoveAll(newTarDataPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = os.Link(oldTarDataPath, newTarDataPath)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ls *layerStore) checksumForGraphIDNoTarsplit(id, parent, newTarDataPath string) (diffID DiffID, size int64, err error) {
|
||||
rawarchive, err := ls.driver.Diff(id, parent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rawarchive.Close()
|
||||
|
||||
f, err := os.Create(newTarDataPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
mfz := gzip.NewWriter(f)
|
||||
defer mfz.Close()
|
||||
metaPacker := storage.NewJSONPacker(mfz)
|
||||
|
||||
packerCounter := &packSizeCounter{metaPacker, &size}
|
||||
|
||||
archive, err := asm.NewInputTarStream(rawarchive, packerCounter, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dgst, err := digest.FromReader(archive)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
diffID = DiffID(dgst)
|
||||
return
|
||||
}
|
||||
|
||||
func (ls *layerStore) RegisterByGraphID(graphID string, parent ChainID, diffID DiffID, tarDataFile string, size int64) (Layer, error) {
|
||||
// err is used to hold the error which will always trigger
|
||||
// cleanup of creates sources but may not be an error returned
|
||||
// to the caller (already exists).
|
||||
var err error
|
||||
var p *roLayer
|
||||
if string(parent) != "" {
|
||||
p = ls.get(parent)
|
||||
if p == nil {
|
||||
return nil, ErrLayerDoesNotExist
|
||||
}
|
||||
|
||||
// Release parent chain if error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
ls.layerL.Lock()
|
||||
ls.releaseLayer(p)
|
||||
ls.layerL.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Create new roLayer
|
||||
layer := &roLayer{
|
||||
parent: p,
|
||||
cacheID: graphID,
|
||||
referenceCount: 1,
|
||||
layerStore: ls,
|
||||
references: map[Layer]struct{}{},
|
||||
diffID: diffID,
|
||||
size: size,
|
||||
chainID: createChainIDFromParent(parent, diffID),
|
||||
}
|
||||
|
||||
ls.layerL.Lock()
|
||||
defer ls.layerL.Unlock()
|
||||
|
||||
if existingLayer := ls.getWithoutLock(layer.chainID); existingLayer != nil {
|
||||
// Set error for cleanup, but do not return
|
||||
err = errors.New("layer already exists")
|
||||
return existingLayer.getReference(), nil
|
||||
}
|
||||
|
||||
tx, err := ls.store.StartTransaction()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
logrus.Debugf("Cleaning up transaction after failed migration for %s: %v", graphID, err)
|
||||
if err := tx.Cancel(); err != nil {
|
||||
logrus.Errorf("Error canceling metadata transaction %q: %s", tx.String(), err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
tsw, err := tx.TarSplitWriter(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tsw.Close()
|
||||
tdf, err := os.Open(tarDataFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tdf.Close()
|
||||
_, err = io.Copy(tsw, tdf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = storeLayer(tx, layer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = tx.Commit(layer.chainID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ls.layerMap[layer.chainID] = layer
|
||||
|
||||
return layer.getReference(), nil
|
||||
}
|
||||
|
||||
type unpackSizeCounter struct {
|
||||
unpacker storage.Unpacker
|
||||
size *int64
|
||||
}
|
||||
|
||||
func (u *unpackSizeCounter) Next() (*storage.Entry, error) {
|
||||
e, err := u.unpacker.Next()
|
||||
if err == nil && u.size != nil {
|
||||
*u.size += e.Size
|
||||
}
|
||||
return e, err
|
||||
}
|
||||
|
||||
type packSizeCounter struct {
|
||||
packer storage.Packer
|
||||
size *int64
|
||||
}
|
||||
|
||||
func (p *packSizeCounter) AddEntry(e storage.Entry) (int, error) {
|
||||
n, err := p.packer.AddEntry(e)
|
||||
if err == nil && p.size != nil {
|
||||
*p.size += e.Size
|
||||
}
|
||||
return n, err
|
||||
}
|
103
vendor/github.com/docker/docker/layer/mounted_layer.go
generated
vendored
Normal file
103
vendor/github.com/docker/docker/layer/mounted_layer.go
generated
vendored
Normal file
|
@ -0,0 +1,103 @@
|
|||
package layer
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
)
|
||||
|
||||
type mountedLayer struct {
|
||||
name string
|
||||
mountID string
|
||||
initID string
|
||||
parent *roLayer
|
||||
path string
|
||||
layerStore *layerStore
|
||||
|
||||
references map[RWLayer]*referencedRWLayer
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) cacheParent() string {
|
||||
if ml.initID != "" {
|
||||
return ml.initID
|
||||
}
|
||||
if ml.parent != nil {
|
||||
return ml.parent.cacheID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) TarStream() (io.ReadCloser, error) {
|
||||
archiver, err := ml.layerStore.driver.Diff(ml.mountID, ml.cacheParent())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return archiver, nil
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) Name() string {
|
||||
return ml.name
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) Parent() Layer {
|
||||
if ml.parent != nil {
|
||||
return ml.parent
|
||||
}
|
||||
|
||||
// Return a nil interface instead of an interface wrapping a nil
|
||||
// pointer.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) Size() (int64, error) {
|
||||
return ml.layerStore.driver.DiffSize(ml.mountID, ml.cacheParent())
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) Changes() ([]archive.Change, error) {
|
||||
return ml.layerStore.driver.Changes(ml.mountID, ml.cacheParent())
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) Metadata() (map[string]string, error) {
|
||||
return ml.layerStore.driver.GetMetadata(ml.mountID)
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) getReference() RWLayer {
|
||||
ref := &referencedRWLayer{
|
||||
mountedLayer: ml,
|
||||
}
|
||||
ml.references[ref] = ref
|
||||
|
||||
return ref
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) hasReferences() bool {
|
||||
return len(ml.references) > 0
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) deleteReference(ref RWLayer) error {
|
||||
if _, ok := ml.references[ref]; !ok {
|
||||
return ErrLayerNotRetained
|
||||
}
|
||||
delete(ml.references, ref)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ml *mountedLayer) retakeReference(r RWLayer) {
|
||||
if ref, ok := r.(*referencedRWLayer); ok {
|
||||
ml.references[ref] = ref
|
||||
}
|
||||
}
|
||||
|
||||
type referencedRWLayer struct {
|
||||
*mountedLayer
|
||||
}
|
||||
|
||||
func (rl *referencedRWLayer) Mount(mountLabel string) (string, error) {
|
||||
return rl.layerStore.driver.Get(rl.mountedLayer.mountID, mountLabel)
|
||||
}
|
||||
|
||||
// Unmount decrements the activity count and unmounts the underlying layer
|
||||
// Callers should only call `Unmount` once per call to `Mount`, even on error.
|
||||
func (rl *referencedRWLayer) Unmount() error {
|
||||
return rl.layerStore.driver.Put(rl.mountedLayer.mountID)
|
||||
}
|
172
vendor/github.com/docker/docker/layer/ro_layer.go
generated
vendored
Normal file
172
vendor/github.com/docker/docker/layer/ro_layer.go
generated
vendored
Normal file
|
@ -0,0 +1,172 @@
|
|||
package layer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/digest"
|
||||
)
|
||||
|
||||
type roLayer struct {
|
||||
chainID ChainID
|
||||
diffID DiffID
|
||||
parent *roLayer
|
||||
cacheID string
|
||||
size int64
|
||||
layerStore *layerStore
|
||||
descriptor distribution.Descriptor
|
||||
|
||||
referenceCount int
|
||||
references map[Layer]struct{}
|
||||
}
|
||||
|
||||
func (rl *roLayer) TarStream() (io.ReadCloser, error) {
|
||||
r, err := rl.layerStore.store.TarSplitReader(rl.chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pr, pw := io.Pipe()
|
||||
go func() {
|
||||
err := rl.layerStore.assembleTarTo(rl.cacheID, r, nil, pw)
|
||||
if err != nil {
|
||||
pw.CloseWithError(err)
|
||||
} else {
|
||||
pw.Close()
|
||||
}
|
||||
}()
|
||||
rc, err := newVerifiedReadCloser(pr, digest.Digest(rl.diffID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (rl *roLayer) ChainID() ChainID {
|
||||
return rl.chainID
|
||||
}
|
||||
|
||||
func (rl *roLayer) DiffID() DiffID {
|
||||
return rl.diffID
|
||||
}
|
||||
|
||||
func (rl *roLayer) Parent() Layer {
|
||||
if rl.parent == nil {
|
||||
return nil
|
||||
}
|
||||
return rl.parent
|
||||
}
|
||||
|
||||
func (rl *roLayer) Size() (size int64, err error) {
|
||||
if rl.parent != nil {
|
||||
size, err = rl.parent.Size()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return size + rl.size, nil
|
||||
}
|
||||
|
||||
func (rl *roLayer) DiffSize() (size int64, err error) {
|
||||
return rl.size, nil
|
||||
}
|
||||
|
||||
func (rl *roLayer) Metadata() (map[string]string, error) {
|
||||
return rl.layerStore.driver.GetMetadata(rl.cacheID)
|
||||
}
|
||||
|
||||
type referencedCacheLayer struct {
|
||||
*roLayer
|
||||
}
|
||||
|
||||
func (rl *roLayer) getReference() Layer {
|
||||
ref := &referencedCacheLayer{
|
||||
roLayer: rl,
|
||||
}
|
||||
rl.references[ref] = struct{}{}
|
||||
|
||||
return ref
|
||||
}
|
||||
|
||||
func (rl *roLayer) hasReference(ref Layer) bool {
|
||||
_, ok := rl.references[ref]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (rl *roLayer) hasReferences() bool {
|
||||
return len(rl.references) > 0
|
||||
}
|
||||
|
||||
func (rl *roLayer) deleteReference(ref Layer) {
|
||||
delete(rl.references, ref)
|
||||
}
|
||||
|
||||
func (rl *roLayer) depth() int {
|
||||
if rl.parent == nil {
|
||||
return 1
|
||||
}
|
||||
return rl.parent.depth() + 1
|
||||
}
|
||||
|
||||
func storeLayer(tx MetadataTransaction, layer *roLayer) error {
|
||||
if err := tx.SetDiffID(layer.diffID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.SetSize(layer.size); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.SetCacheID(layer.cacheID); err != nil {
|
||||
return err
|
||||
}
|
||||
// Do not store empty descriptors
|
||||
if layer.descriptor.Digest != "" {
|
||||
if err := tx.SetDescriptor(layer.descriptor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if layer.parent != nil {
|
||||
if err := tx.SetParent(layer.parent.chainID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newVerifiedReadCloser(rc io.ReadCloser, dgst digest.Digest) (io.ReadCloser, error) {
|
||||
verifier, err := digest.NewDigestVerifier(dgst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &verifiedReadCloser{
|
||||
rc: rc,
|
||||
dgst: dgst,
|
||||
verifier: verifier,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type verifiedReadCloser struct {
|
||||
rc io.ReadCloser
|
||||
dgst digest.Digest
|
||||
verifier digest.Verifier
|
||||
}
|
||||
|
||||
func (vrc *verifiedReadCloser) Read(p []byte) (n int, err error) {
|
||||
n, err = vrc.rc.Read(p)
|
||||
if n > 0 {
|
||||
if n, err := vrc.verifier.Write(p[:n]); err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
if !vrc.verifier.Verified() {
|
||||
err = fmt.Errorf("could not verify layer data for: %s. This may be because internal files in the layer store were modified. Re-pulling or rebuilding this image may resolve the issue", vrc.dgst)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (vrc *verifiedReadCloser) Close() error {
|
||||
return vrc.rc.Close()
|
||||
}
|
9
vendor/github.com/docker/docker/layer/ro_layer_windows.go
generated
vendored
Normal file
9
vendor/github.com/docker/docker/layer/ro_layer_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
package layer
|
||||
|
||||
import "github.com/docker/distribution"
|
||||
|
||||
var _ distribution.Describable = &roLayer{}
|
||||
|
||||
func (rl *roLayer) Descriptor() distribution.Descriptor {
|
||||
return rl.descriptor
|
||||
}
|
1
vendor/github.com/docker/docker/pkg/archive/README.md
generated
vendored
Normal file
1
vendor/github.com/docker/docker/pkg/archive/README.md
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
This code provides helper functions for dealing with archive files.
|
1147
vendor/github.com/docker/docker/pkg/archive/archive.go
generated
vendored
Normal file
1147
vendor/github.com/docker/docker/pkg/archive/archive.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
90
vendor/github.com/docker/docker/pkg/archive/archive_linux.go
generated
vendored
Normal file
90
vendor/github.com/docker/docker/pkg/archive/archive_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,90 @@
|
|||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
|
||||
if format == OverlayWhiteoutFormat {
|
||||
return overlayWhiteoutConverter{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type overlayWhiteoutConverter struct{}
|
||||
|
||||
func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) error {
|
||||
// convert whiteouts to AUFS format
|
||||
if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 {
|
||||
// we just rename the file and make it normal
|
||||
hdr.Name = WhiteoutPrefix + hdr.Name
|
||||
hdr.Mode = 0600
|
||||
hdr.Typeflag = tar.TypeReg
|
||||
hdr.Size = 0
|
||||
}
|
||||
|
||||
if fi.Mode()&os.ModeDir != 0 {
|
||||
// convert opaque dirs to AUFS format by writing an empty file with the prefix
|
||||
opaque, err := system.Lgetxattr(path, "trusted.overlay.opaque")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if opaque != nil && len(opaque) == 1 && opaque[0] == 'y' {
|
||||
// create a header for the whiteout file
|
||||
// it should inherit some properties from the parent, but be a regular file
|
||||
*hdr = tar.Header{
|
||||
Typeflag: tar.TypeReg,
|
||||
Mode: hdr.Mode & int64(os.ModePerm),
|
||||
Name: filepath.Join(hdr.Name, WhiteoutOpaqueDir),
|
||||
Size: 0,
|
||||
Uid: hdr.Uid,
|
||||
Uname: hdr.Uname,
|
||||
Gid: hdr.Gid,
|
||||
Gname: hdr.Gname,
|
||||
AccessTime: hdr.AccessTime,
|
||||
ChangeTime: hdr.ChangeTime,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) {
|
||||
base := filepath.Base(path)
|
||||
dir := filepath.Dir(path)
|
||||
|
||||
// if a directory is marked as opaque by the AUFS special file, we need to translate that to overlay
|
||||
if base == WhiteoutOpaqueDir {
|
||||
if err := syscall.Setxattr(dir, "trusted.overlay.opaque", []byte{'y'}, 0); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// don't write the file itself
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// if a file was deleted and we are using overlay, we need to create a character device
|
||||
if strings.HasPrefix(base, WhiteoutPrefix) {
|
||||
originalBase := base[len(WhiteoutPrefix):]
|
||||
originalPath := filepath.Join(dir, originalBase)
|
||||
|
||||
if err := syscall.Mknod(originalPath, syscall.S_IFCHR, 0); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := os.Chown(originalPath, hdr.Uid, hdr.Gid); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// don't write the file itself
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
7
vendor/github.com/docker/docker/pkg/archive/archive_other.go
generated
vendored
Normal file
7
vendor/github.com/docker/docker/pkg/archive/archive_other.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
// +build !linux
|
||||
|
||||
package archive
|
||||
|
||||
func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
|
||||
return nil
|
||||
}
|
112
vendor/github.com/docker/docker/pkg/archive/archive_unix.go
generated
vendored
Normal file
112
vendor/github.com/docker/docker/pkg/archive/archive_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,112 @@
|
|||
// +build !windows
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
// fixVolumePathPrefix does platform specific processing to ensure that if
|
||||
// the path being passed in is not in a volume path format, convert it to one.
|
||||
func fixVolumePathPrefix(srcPath string) string {
|
||||
return srcPath
|
||||
}
|
||||
|
||||
// getWalkRoot calculates the root path when performing a TarWithOptions.
|
||||
// We use a separate function as this is platform specific. On Linux, we
|
||||
// can't use filepath.Join(srcPath,include) because this will clean away
|
||||
// a trailing "." or "/" which may be important.
|
||||
func getWalkRoot(srcPath string, include string) string {
|
||||
return srcPath + string(filepath.Separator) + include
|
||||
}
|
||||
|
||||
// CanonicalTarNameForPath returns platform-specific filepath
|
||||
// to canonical posix-style path for tar archival. p is relative
|
||||
// path.
|
||||
func CanonicalTarNameForPath(p string) (string, error) {
|
||||
return p, nil // already unix-style
|
||||
}
|
||||
|
||||
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
||||
// on the platform the archival is done.
|
||||
|
||||
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
||||
return perm // noop for unix as golang APIs provide perm bits correctly
|
||||
}
|
||||
|
||||
func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (inode uint64, err error) {
|
||||
s, ok := stat.(*syscall.Stat_t)
|
||||
|
||||
if !ok {
|
||||
err = errors.New("cannot convert stat value to syscall.Stat_t")
|
||||
return
|
||||
}
|
||||
|
||||
inode = uint64(s.Ino)
|
||||
|
||||
// Currently go does not fill in the major/minors
|
||||
if s.Mode&syscall.S_IFBLK != 0 ||
|
||||
s.Mode&syscall.S_IFCHR != 0 {
|
||||
hdr.Devmajor = int64(major(uint64(s.Rdev)))
|
||||
hdr.Devminor = int64(minor(uint64(s.Rdev)))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getFileUIDGID(stat interface{}) (int, int, error) {
|
||||
s, ok := stat.(*syscall.Stat_t)
|
||||
|
||||
if !ok {
|
||||
return -1, -1, errors.New("cannot convert stat value to syscall.Stat_t")
|
||||
}
|
||||
return int(s.Uid), int(s.Gid), nil
|
||||
}
|
||||
|
||||
func major(device uint64) uint64 {
|
||||
return (device >> 8) & 0xfff
|
||||
}
|
||||
|
||||
func minor(device uint64) uint64 {
|
||||
return (device & 0xff) | ((device >> 12) & 0xfff00)
|
||||
}
|
||||
|
||||
// handleTarTypeBlockCharFifo is an OS-specific helper function used by
|
||||
// createTarFile to handle the following types of header: Block; Char; Fifo
|
||||
func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
|
||||
mode := uint32(hdr.Mode & 07777)
|
||||
switch hdr.Typeflag {
|
||||
case tar.TypeBlock:
|
||||
mode |= syscall.S_IFBLK
|
||||
case tar.TypeChar:
|
||||
mode |= syscall.S_IFCHR
|
||||
case tar.TypeFifo:
|
||||
mode |= syscall.S_IFIFO
|
||||
}
|
||||
|
||||
if err := system.Mknod(path, mode, int(system.Mkdev(hdr.Devmajor, hdr.Devminor))); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
|
||||
if hdr.Typeflag == tar.TypeLink {
|
||||
if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
|
||||
if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if hdr.Typeflag != tar.TypeSymlink {
|
||||
if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
70
vendor/github.com/docker/docker/pkg/archive/archive_windows.go
generated
vendored
Normal file
70
vendor/github.com/docker/docker/pkg/archive/archive_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
// +build windows
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/pkg/longpath"
|
||||
)
|
||||
|
||||
// fixVolumePathPrefix does platform specific processing to ensure that if
|
||||
// the path being passed in is not in a volume path format, convert it to one.
|
||||
func fixVolumePathPrefix(srcPath string) string {
|
||||
return longpath.AddPrefix(srcPath)
|
||||
}
|
||||
|
||||
// getWalkRoot calculates the root path when performing a TarWithOptions.
|
||||
// We use a separate function as this is platform specific.
|
||||
func getWalkRoot(srcPath string, include string) string {
|
||||
return filepath.Join(srcPath, include)
|
||||
}
|
||||
|
||||
// CanonicalTarNameForPath returns platform-specific filepath
|
||||
// to canonical posix-style path for tar archival. p is relative
|
||||
// path.
|
||||
func CanonicalTarNameForPath(p string) (string, error) {
|
||||
// windows: convert windows style relative path with backslashes
|
||||
// into forward slashes. Since windows does not allow '/' or '\'
|
||||
// in file names, it is mostly safe to replace however we must
|
||||
// check just in case
|
||||
if strings.Contains(p, "/") {
|
||||
return "", fmt.Errorf("Windows path contains forward slash: %s", p)
|
||||
}
|
||||
return strings.Replace(p, string(os.PathSeparator), "/", -1), nil
|
||||
|
||||
}
|
||||
|
||||
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
||||
// on the platform the archival is done.
|
||||
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
||||
perm &= 0755
|
||||
// Add the x bit: make everything +x from windows
|
||||
perm |= 0111
|
||||
|
||||
return perm
|
||||
}
|
||||
|
||||
func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (inode uint64, err error) {
|
||||
// do nothing. no notion of Rdev, Inode, Nlink in stat on Windows
|
||||
return
|
||||
}
|
||||
|
||||
// handleTarTypeBlockCharFifo is an OS-specific helper function used by
|
||||
// createTarFile to handle the following types of header: Block; Char; Fifo
|
||||
func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getFileUIDGID(stat interface{}) (int, int, error) {
|
||||
// no notion of file ownership mapping yet on Windows
|
||||
return 0, 0, nil
|
||||
}
|
446
vendor/github.com/docker/docker/pkg/archive/changes.go
generated
vendored
Normal file
446
vendor/github.com/docker/docker/pkg/archive/changes.go
generated
vendored
Normal file
|
@ -0,0 +1,446 @@
|
|||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/pools"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
// ChangeType represents the change type.
|
||||
type ChangeType int
|
||||
|
||||
const (
|
||||
// ChangeModify represents the modify operation.
|
||||
ChangeModify = iota
|
||||
// ChangeAdd represents the add operation.
|
||||
ChangeAdd
|
||||
// ChangeDelete represents the delete operation.
|
||||
ChangeDelete
|
||||
)
|
||||
|
||||
func (c ChangeType) String() string {
|
||||
switch c {
|
||||
case ChangeModify:
|
||||
return "C"
|
||||
case ChangeAdd:
|
||||
return "A"
|
||||
case ChangeDelete:
|
||||
return "D"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Change represents a change, it wraps the change type and path.
|
||||
// It describes changes of the files in the path respect to the
|
||||
// parent layers. The change could be modify, add, delete.
|
||||
// This is used for layer diff.
|
||||
type Change struct {
|
||||
Path string
|
||||
Kind ChangeType
|
||||
}
|
||||
|
||||
func (change *Change) String() string {
|
||||
return fmt.Sprintf("%s %s", change.Kind, change.Path)
|
||||
}
|
||||
|
||||
// for sort.Sort
|
||||
type changesByPath []Change
|
||||
|
||||
func (c changesByPath) Less(i, j int) bool { return c[i].Path < c[j].Path }
|
||||
func (c changesByPath) Len() int { return len(c) }
|
||||
func (c changesByPath) Swap(i, j int) { c[j], c[i] = c[i], c[j] }
|
||||
|
||||
// Gnu tar and the go tar writer don't have sub-second mtime
|
||||
// precision, which is problematic when we apply changes via tar
|
||||
// files, we handle this by comparing for exact times, *or* same
|
||||
// second count and either a or b having exactly 0 nanoseconds
|
||||
func sameFsTime(a, b time.Time) bool {
|
||||
return a == b ||
|
||||
(a.Unix() == b.Unix() &&
|
||||
(a.Nanosecond() == 0 || b.Nanosecond() == 0))
|
||||
}
|
||||
|
||||
func sameFsTimeSpec(a, b syscall.Timespec) bool {
|
||||
return a.Sec == b.Sec &&
|
||||
(a.Nsec == b.Nsec || a.Nsec == 0 || b.Nsec == 0)
|
||||
}
|
||||
|
||||
// Changes walks the path rw and determines changes for the files in the path,
|
||||
// with respect to the parent layers
|
||||
func Changes(layers []string, rw string) ([]Change, error) {
|
||||
return changes(layers, rw, aufsDeletedFile, aufsMetadataSkip)
|
||||
}
|
||||
|
||||
func aufsMetadataSkip(path string) (skip bool, err error) {
|
||||
skip, err = filepath.Match(string(os.PathSeparator)+WhiteoutMetaPrefix+"*", path)
|
||||
if err != nil {
|
||||
skip = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func aufsDeletedFile(root, path string, fi os.FileInfo) (string, error) {
|
||||
f := filepath.Base(path)
|
||||
|
||||
// If there is a whiteout, then the file was removed
|
||||
if strings.HasPrefix(f, WhiteoutPrefix) {
|
||||
originalFile := f[len(WhiteoutPrefix):]
|
||||
return filepath.Join(filepath.Dir(path), originalFile), nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
type skipChange func(string) (bool, error)
|
||||
type deleteChange func(string, string, os.FileInfo) (string, error)
|
||||
|
||||
func changes(layers []string, rw string, dc deleteChange, sc skipChange) ([]Change, error) {
|
||||
var (
|
||||
changes []Change
|
||||
changedDirs = make(map[string]struct{})
|
||||
)
|
||||
|
||||
err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
path, err = filepath.Rel(rw, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// As this runs on the daemon side, file paths are OS specific.
|
||||
path = filepath.Join(string(os.PathSeparator), path)
|
||||
|
||||
// Skip root
|
||||
if path == string(os.PathSeparator) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if sc != nil {
|
||||
if skip, err := sc(path); skip {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
change := Change{
|
||||
Path: path,
|
||||
}
|
||||
|
||||
deletedFile, err := dc(rw, path, f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find out what kind of modification happened
|
||||
if deletedFile != "" {
|
||||
change.Path = deletedFile
|
||||
change.Kind = ChangeDelete
|
||||
} else {
|
||||
// Otherwise, the file was added
|
||||
change.Kind = ChangeAdd
|
||||
|
||||
// ...Unless it already existed in a top layer, in which case, it's a modification
|
||||
for _, layer := range layers {
|
||||
stat, err := os.Stat(filepath.Join(layer, path))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
// The file existed in the top layer, so that's a modification
|
||||
|
||||
// However, if it's a directory, maybe it wasn't actually modified.
|
||||
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
|
||||
if stat.IsDir() && f.IsDir() {
|
||||
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && sameFsTime(f.ModTime(), stat.ModTime()) {
|
||||
// Both directories are the same, don't record the change
|
||||
return nil
|
||||
}
|
||||
}
|
||||
change.Kind = ChangeModify
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If /foo/bar/file.txt is modified, then /foo/bar must be part of the changed files.
|
||||
// This block is here to ensure the change is recorded even if the
|
||||
// modify time, mode and size of the parent directory in the rw and ro layers are all equal.
|
||||
// Check https://github.com/docker/docker/pull/13590 for details.
|
||||
if f.IsDir() {
|
||||
changedDirs[path] = struct{}{}
|
||||
}
|
||||
if change.Kind == ChangeAdd || change.Kind == ChangeDelete {
|
||||
parent := filepath.Dir(path)
|
||||
if _, ok := changedDirs[parent]; !ok && parent != "/" {
|
||||
changes = append(changes, Change{Path: parent, Kind: ChangeModify})
|
||||
changedDirs[parent] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Record change
|
||||
changes = append(changes, change)
|
||||
return nil
|
||||
})
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
// FileInfo describes the information of a file.
|
||||
type FileInfo struct {
|
||||
parent *FileInfo
|
||||
name string
|
||||
stat *system.StatT
|
||||
children map[string]*FileInfo
|
||||
capability []byte
|
||||
added bool
|
||||
}
|
||||
|
||||
// LookUp looks up the file information of a file.
|
||||
func (info *FileInfo) LookUp(path string) *FileInfo {
|
||||
// As this runs on the daemon side, file paths are OS specific.
|
||||
parent := info
|
||||
if path == string(os.PathSeparator) {
|
||||
return info
|
||||
}
|
||||
|
||||
pathElements := strings.Split(path, string(os.PathSeparator))
|
||||
for _, elem := range pathElements {
|
||||
if elem != "" {
|
||||
child := parent.children[elem]
|
||||
if child == nil {
|
||||
return nil
|
||||
}
|
||||
parent = child
|
||||
}
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
||||
func (info *FileInfo) path() string {
|
||||
if info.parent == nil {
|
||||
// As this runs on the daemon side, file paths are OS specific.
|
||||
return string(os.PathSeparator)
|
||||
}
|
||||
return filepath.Join(info.parent.path(), info.name)
|
||||
}
|
||||
|
||||
func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
|
||||
|
||||
sizeAtEntry := len(*changes)
|
||||
|
||||
if oldInfo == nil {
|
||||
// add
|
||||
change := Change{
|
||||
Path: info.path(),
|
||||
Kind: ChangeAdd,
|
||||
}
|
||||
*changes = append(*changes, change)
|
||||
info.added = true
|
||||
}
|
||||
|
||||
// We make a copy so we can modify it to detect additions
|
||||
// also, we only recurse on the old dir if the new info is a directory
|
||||
// otherwise any previous delete/change is considered recursive
|
||||
oldChildren := make(map[string]*FileInfo)
|
||||
if oldInfo != nil && info.isDir() {
|
||||
for k, v := range oldInfo.children {
|
||||
oldChildren[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
for name, newChild := range info.children {
|
||||
oldChild, _ := oldChildren[name]
|
||||
if oldChild != nil {
|
||||
// change?
|
||||
oldStat := oldChild.stat
|
||||
newStat := newChild.stat
|
||||
// Note: We can't compare inode or ctime or blocksize here, because these change
|
||||
// when copying a file into a container. However, that is not generally a problem
|
||||
// because any content change will change mtime, and any status change should
|
||||
// be visible when actually comparing the stat fields. The only time this
|
||||
// breaks down is if some code intentionally hides a change by setting
|
||||
// back mtime
|
||||
if statDifferent(oldStat, newStat) ||
|
||||
bytes.Compare(oldChild.capability, newChild.capability) != 0 {
|
||||
change := Change{
|
||||
Path: newChild.path(),
|
||||
Kind: ChangeModify,
|
||||
}
|
||||
*changes = append(*changes, change)
|
||||
newChild.added = true
|
||||
}
|
||||
|
||||
// Remove from copy so we can detect deletions
|
||||
delete(oldChildren, name)
|
||||
}
|
||||
|
||||
newChild.addChanges(oldChild, changes)
|
||||
}
|
||||
for _, oldChild := range oldChildren {
|
||||
// delete
|
||||
change := Change{
|
||||
Path: oldChild.path(),
|
||||
Kind: ChangeDelete,
|
||||
}
|
||||
*changes = append(*changes, change)
|
||||
}
|
||||
|
||||
// If there were changes inside this directory, we need to add it, even if the directory
|
||||
// itself wasn't changed. This is needed to properly save and restore filesystem permissions.
|
||||
// As this runs on the daemon side, file paths are OS specific.
|
||||
if len(*changes) > sizeAtEntry && info.isDir() && !info.added && info.path() != string(os.PathSeparator) {
|
||||
change := Change{
|
||||
Path: info.path(),
|
||||
Kind: ChangeModify,
|
||||
}
|
||||
// Let's insert the directory entry before the recently added entries located inside this dir
|
||||
*changes = append(*changes, change) // just to resize the slice, will be overwritten
|
||||
copy((*changes)[sizeAtEntry+1:], (*changes)[sizeAtEntry:])
|
||||
(*changes)[sizeAtEntry] = change
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Changes add changes to file information.
|
||||
func (info *FileInfo) Changes(oldInfo *FileInfo) []Change {
|
||||
var changes []Change
|
||||
|
||||
info.addChanges(oldInfo, &changes)
|
||||
|
||||
return changes
|
||||
}
|
||||
|
||||
func newRootFileInfo() *FileInfo {
|
||||
// As this runs on the daemon side, file paths are OS specific.
|
||||
root := &FileInfo{
|
||||
name: string(os.PathSeparator),
|
||||
children: make(map[string]*FileInfo),
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
// ChangesDirs compares two directories and generates an array of Change objects describing the changes.
|
||||
// If oldDir is "", then all files in newDir will be Add-Changes.
|
||||
func ChangesDirs(newDir, oldDir string) ([]Change, error) {
|
||||
var (
|
||||
oldRoot, newRoot *FileInfo
|
||||
)
|
||||
if oldDir == "" {
|
||||
emptyDir, err := ioutil.TempDir("", "empty")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.Remove(emptyDir)
|
||||
oldDir = emptyDir
|
||||
}
|
||||
oldRoot, newRoot, err := collectFileInfoForChanges(oldDir, newDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newRoot.Changes(oldRoot), nil
|
||||
}
|
||||
|
||||
// ChangesSize calculates the size in bytes of the provided changes, based on newDir.
|
||||
func ChangesSize(newDir string, changes []Change) int64 {
|
||||
var (
|
||||
size int64
|
||||
sf = make(map[uint64]struct{})
|
||||
)
|
||||
for _, change := range changes {
|
||||
if change.Kind == ChangeModify || change.Kind == ChangeAdd {
|
||||
file := filepath.Join(newDir, change.Path)
|
||||
fileInfo, err := os.Lstat(file)
|
||||
if err != nil {
|
||||
logrus.Errorf("Can not stat %q: %s", file, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if fileInfo != nil && !fileInfo.IsDir() {
|
||||
if hasHardlinks(fileInfo) {
|
||||
inode := getIno(fileInfo)
|
||||
if _, ok := sf[inode]; !ok {
|
||||
size += fileInfo.Size()
|
||||
sf[inode] = struct{}{}
|
||||
}
|
||||
} else {
|
||||
size += fileInfo.Size()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// ExportChanges produces an Archive from the provided changes, relative to dir.
|
||||
func ExportChanges(dir string, changes []Change, uidMaps, gidMaps []idtools.IDMap) (Archive, error) {
|
||||
reader, writer := io.Pipe()
|
||||
go func() {
|
||||
ta := &tarAppender{
|
||||
TarWriter: tar.NewWriter(writer),
|
||||
Buffer: pools.BufioWriter32KPool.Get(nil),
|
||||
SeenFiles: make(map[uint64]string),
|
||||
UIDMaps: uidMaps,
|
||||
GIDMaps: gidMaps,
|
||||
}
|
||||
// this buffer is needed for the duration of this piped stream
|
||||
defer pools.BufioWriter32KPool.Put(ta.Buffer)
|
||||
|
||||
sort.Sort(changesByPath(changes))
|
||||
|
||||
// In general we log errors here but ignore them because
|
||||
// during e.g. a diff operation the container can continue
|
||||
// mutating the filesystem and we can see transient errors
|
||||
// from this
|
||||
for _, change := range changes {
|
||||
if change.Kind == ChangeDelete {
|
||||
whiteOutDir := filepath.Dir(change.Path)
|
||||
whiteOutBase := filepath.Base(change.Path)
|
||||
whiteOut := filepath.Join(whiteOutDir, WhiteoutPrefix+whiteOutBase)
|
||||
timestamp := time.Now()
|
||||
hdr := &tar.Header{
|
||||
Name: whiteOut[1:],
|
||||
Size: 0,
|
||||
ModTime: timestamp,
|
||||
AccessTime: timestamp,
|
||||
ChangeTime: timestamp,
|
||||
}
|
||||
if err := ta.TarWriter.WriteHeader(hdr); err != nil {
|
||||
logrus.Debugf("Can't write whiteout header: %s", err)
|
||||
}
|
||||
} else {
|
||||
path := filepath.Join(dir, change.Path)
|
||||
if err := ta.addTarFile(path, change.Path[1:]); err != nil {
|
||||
logrus.Debugf("Can't add file %s to tar: %s", path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to check the error on Close.
|
||||
if err := ta.TarWriter.Close(); err != nil {
|
||||
logrus.Debugf("Can't close layer: %s", err)
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
logrus.Debugf("failed close Changes writer: %s", err)
|
||||
}
|
||||
}()
|
||||
return reader, nil
|
||||
}
|
312
vendor/github.com/docker/docker/pkg/archive/changes_linux.go
generated
vendored
Normal file
312
vendor/github.com/docker/docker/pkg/archive/changes_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,312 @@
|
|||
package archive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
// walker is used to implement collectFileInfoForChanges on linux. Where this
|
||||
// method in general returns the entire contents of two directory trees, we
|
||||
// optimize some FS calls out on linux. In particular, we take advantage of the
|
||||
// fact that getdents(2) returns the inode of each file in the directory being
|
||||
// walked, which, when walking two trees in parallel to generate a list of
|
||||
// changes, can be used to prune subtrees without ever having to lstat(2) them
|
||||
// directly. Eliminating stat calls in this way can save up to seconds on large
|
||||
// images.
|
||||
type walker struct {
|
||||
dir1 string
|
||||
dir2 string
|
||||
root1 *FileInfo
|
||||
root2 *FileInfo
|
||||
}
|
||||
|
||||
// collectFileInfoForChanges returns a complete representation of the trees
|
||||
// rooted at dir1 and dir2, with one important exception: any subtree or
|
||||
// leaf where the inode and device numbers are an exact match between dir1
|
||||
// and dir2 will be pruned from the results. This method is *only* to be used
|
||||
// to generating a list of changes between the two directories, as it does not
|
||||
// reflect the full contents.
|
||||
func collectFileInfoForChanges(dir1, dir2 string) (*FileInfo, *FileInfo, error) {
|
||||
w := &walker{
|
||||
dir1: dir1,
|
||||
dir2: dir2,
|
||||
root1: newRootFileInfo(),
|
||||
root2: newRootFileInfo(),
|
||||
}
|
||||
|
||||
i1, err := os.Lstat(w.dir1)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
i2, err := os.Lstat(w.dir2)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := w.walk("/", i1, i2); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return w.root1, w.root2, nil
|
||||
}
|
||||
|
||||
// Given a FileInfo, its path info, and a reference to the root of the tree
|
||||
// being constructed, register this file with the tree.
|
||||
func walkchunk(path string, fi os.FileInfo, dir string, root *FileInfo) error {
|
||||
if fi == nil {
|
||||
return nil
|
||||
}
|
||||
parent := root.LookUp(filepath.Dir(path))
|
||||
if parent == nil {
|
||||
return fmt.Errorf("collectFileInfoForChanges: Unexpectedly no parent for %s", path)
|
||||
}
|
||||
info := &FileInfo{
|
||||
name: filepath.Base(path),
|
||||
children: make(map[string]*FileInfo),
|
||||
parent: parent,
|
||||
}
|
||||
cpath := filepath.Join(dir, path)
|
||||
stat, err := system.FromStatT(fi.Sys().(*syscall.Stat_t))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info.stat = stat
|
||||
info.capability, _ = system.Lgetxattr(cpath, "security.capability") // lgetxattr(2): fs access
|
||||
parent.children[info.name] = info
|
||||
return nil
|
||||
}
|
||||
|
||||
// Walk a subtree rooted at the same path in both trees being iterated. For
|
||||
// example, /docker/overlay/1234/a/b/c/d and /docker/overlay/8888/a/b/c/d
|
||||
func (w *walker) walk(path string, i1, i2 os.FileInfo) (err error) {
|
||||
// Register these nodes with the return trees, unless we're still at the
|
||||
// (already-created) roots:
|
||||
if path != "/" {
|
||||
if err := walkchunk(path, i1, w.dir1, w.root1); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := walkchunk(path, i2, w.dir2, w.root2); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
is1Dir := i1 != nil && i1.IsDir()
|
||||
is2Dir := i2 != nil && i2.IsDir()
|
||||
|
||||
sameDevice := false
|
||||
if i1 != nil && i2 != nil {
|
||||
si1 := i1.Sys().(*syscall.Stat_t)
|
||||
si2 := i2.Sys().(*syscall.Stat_t)
|
||||
if si1.Dev == si2.Dev {
|
||||
sameDevice = true
|
||||
}
|
||||
}
|
||||
|
||||
// If these files are both non-existent, or leaves (non-dirs), we are done.
|
||||
if !is1Dir && !is2Dir {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fetch the names of all the files contained in both directories being walked:
|
||||
var names1, names2 []nameIno
|
||||
if is1Dir {
|
||||
names1, err = readdirnames(filepath.Join(w.dir1, path)) // getdents(2): fs access
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if is2Dir {
|
||||
names2, err = readdirnames(filepath.Join(w.dir2, path)) // getdents(2): fs access
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// We have lists of the files contained in both parallel directories, sorted
|
||||
// in the same order. Walk them in parallel, generating a unique merged list
|
||||
// of all items present in either or both directories.
|
||||
var names []string
|
||||
ix1 := 0
|
||||
ix2 := 0
|
||||
|
||||
for {
|
||||
if ix1 >= len(names1) {
|
||||
break
|
||||
}
|
||||
if ix2 >= len(names2) {
|
||||
break
|
||||
}
|
||||
|
||||
ni1 := names1[ix1]
|
||||
ni2 := names2[ix2]
|
||||
|
||||
switch bytes.Compare([]byte(ni1.name), []byte(ni2.name)) {
|
||||
case -1: // ni1 < ni2 -- advance ni1
|
||||
// we will not encounter ni1 in names2
|
||||
names = append(names, ni1.name)
|
||||
ix1++
|
||||
case 0: // ni1 == ni2
|
||||
if ni1.ino != ni2.ino || !sameDevice {
|
||||
names = append(names, ni1.name)
|
||||
}
|
||||
ix1++
|
||||
ix2++
|
||||
case 1: // ni1 > ni2 -- advance ni2
|
||||
// we will not encounter ni2 in names1
|
||||
names = append(names, ni2.name)
|
||||
ix2++
|
||||
}
|
||||
}
|
||||
for ix1 < len(names1) {
|
||||
names = append(names, names1[ix1].name)
|
||||
ix1++
|
||||
}
|
||||
for ix2 < len(names2) {
|
||||
names = append(names, names2[ix2].name)
|
||||
ix2++
|
||||
}
|
||||
|
||||
// For each of the names present in either or both of the directories being
|
||||
// iterated, stat the name under each root, and recurse the pair of them:
|
||||
for _, name := range names {
|
||||
fname := filepath.Join(path, name)
|
||||
var cInfo1, cInfo2 os.FileInfo
|
||||
if is1Dir {
|
||||
cInfo1, err = os.Lstat(filepath.Join(w.dir1, fname)) // lstat(2): fs access
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if is2Dir {
|
||||
cInfo2, err = os.Lstat(filepath.Join(w.dir2, fname)) // lstat(2): fs access
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = w.walk(fname, cInfo1, cInfo2); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// {name,inode} pairs used to support the early-pruning logic of the walker type
|
||||
type nameIno struct {
|
||||
name string
|
||||
ino uint64
|
||||
}
|
||||
|
||||
type nameInoSlice []nameIno
|
||||
|
||||
func (s nameInoSlice) Len() int { return len(s) }
|
||||
func (s nameInoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s nameInoSlice) Less(i, j int) bool { return s[i].name < s[j].name }
|
||||
|
||||
// readdirnames is a hacked-apart version of the Go stdlib code, exposing inode
|
||||
// numbers further up the stack when reading directory contents. Unlike
|
||||
// os.Readdirnames, which returns a list of filenames, this function returns a
|
||||
// list of {filename,inode} pairs.
|
||||
func readdirnames(dirname string) (names []nameIno, err error) {
|
||||
var (
|
||||
size = 100
|
||||
buf = make([]byte, 4096)
|
||||
nbuf int
|
||||
bufp int
|
||||
nb int
|
||||
)
|
||||
|
||||
f, err := os.Open(dirname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
names = make([]nameIno, 0, size) // Empty with room to grow.
|
||||
for {
|
||||
// Refill the buffer if necessary
|
||||
if bufp >= nbuf {
|
||||
bufp = 0
|
||||
nbuf, err = syscall.ReadDirent(int(f.Fd()), buf) // getdents on linux
|
||||
if nbuf < 0 {
|
||||
nbuf = 0
|
||||
}
|
||||
if err != nil {
|
||||
return nil, os.NewSyscallError("readdirent", err)
|
||||
}
|
||||
if nbuf <= 0 {
|
||||
break // EOF
|
||||
}
|
||||
}
|
||||
|
||||
// Drain the buffer
|
||||
nb, names = parseDirent(buf[bufp:nbuf], names)
|
||||
bufp += nb
|
||||
}
|
||||
|
||||
sl := nameInoSlice(names)
|
||||
sort.Sort(sl)
|
||||
return sl, nil
|
||||
}
|
||||
|
||||
// parseDirent is a minor modification of syscall.ParseDirent (linux version)
|
||||
// which returns {name,inode} pairs instead of just names.
|
||||
func parseDirent(buf []byte, names []nameIno) (consumed int, newnames []nameIno) {
|
||||
origlen := len(buf)
|
||||
for len(buf) > 0 {
|
||||
dirent := (*syscall.Dirent)(unsafe.Pointer(&buf[0]))
|
||||
buf = buf[dirent.Reclen:]
|
||||
if dirent.Ino == 0 { // File absent in directory.
|
||||
continue
|
||||
}
|
||||
bytes := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0]))
|
||||
var name = string(bytes[0:clen(bytes[:])])
|
||||
if name == "." || name == ".." { // Useless names
|
||||
continue
|
||||
}
|
||||
names = append(names, nameIno{name, dirent.Ino})
|
||||
}
|
||||
return origlen - len(buf), names
|
||||
}
|
||||
|
||||
func clen(n []byte) int {
|
||||
for i := 0; i < len(n); i++ {
|
||||
if n[i] == 0 {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return len(n)
|
||||
}
|
||||
|
||||
// OverlayChanges walks the path rw and determines changes for the files in the path,
|
||||
// with respect to the parent layers
|
||||
func OverlayChanges(layers []string, rw string) ([]Change, error) {
|
||||
return changes(layers, rw, overlayDeletedFile, nil)
|
||||
}
|
||||
|
||||
func overlayDeletedFile(root, path string, fi os.FileInfo) (string, error) {
|
||||
if fi.Mode()&os.ModeCharDevice != 0 {
|
||||
s := fi.Sys().(*syscall.Stat_t)
|
||||
if major(uint64(s.Rdev)) == 0 && minor(uint64(s.Rdev)) == 0 {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
if fi.Mode()&os.ModeDir != 0 {
|
||||
opaque, err := system.Lgetxattr(filepath.Join(root, path), "trusted.overlay.opaque")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if opaque != nil && len(opaque) == 1 && opaque[0] == 'y' {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
|
||||
}
|
97
vendor/github.com/docker/docker/pkg/archive/changes_other.go
generated
vendored
Normal file
97
vendor/github.com/docker/docker/pkg/archive/changes_other.go
generated
vendored
Normal file
|
@ -0,0 +1,97 @@
|
|||
// +build !linux
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
func collectFileInfoForChanges(oldDir, newDir string) (*FileInfo, *FileInfo, error) {
|
||||
var (
|
||||
oldRoot, newRoot *FileInfo
|
||||
err1, err2 error
|
||||
errs = make(chan error, 2)
|
||||
)
|
||||
go func() {
|
||||
oldRoot, err1 = collectFileInfo(oldDir)
|
||||
errs <- err1
|
||||
}()
|
||||
go func() {
|
||||
newRoot, err2 = collectFileInfo(newDir)
|
||||
errs <- err2
|
||||
}()
|
||||
|
||||
// block until both routines have returned
|
||||
for i := 0; i < 2; i++ {
|
||||
if err := <-errs; err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return oldRoot, newRoot, nil
|
||||
}
|
||||
|
||||
func collectFileInfo(sourceDir string) (*FileInfo, error) {
|
||||
root := newRootFileInfo()
|
||||
|
||||
err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase path
|
||||
relPath, err := filepath.Rel(sourceDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// As this runs on the daemon side, file paths are OS specific.
|
||||
relPath = filepath.Join(string(os.PathSeparator), relPath)
|
||||
|
||||
// See https://github.com/golang/go/issues/9168 - bug in filepath.Join.
|
||||
// Temporary workaround. If the returned path starts with two backslashes,
|
||||
// trim it down to a single backslash. Only relevant on Windows.
|
||||
if runtime.GOOS == "windows" {
|
||||
if strings.HasPrefix(relPath, `\\`) {
|
||||
relPath = relPath[1:]
|
||||
}
|
||||
}
|
||||
|
||||
if relPath == string(os.PathSeparator) {
|
||||
return nil
|
||||
}
|
||||
|
||||
parent := root.LookUp(filepath.Dir(relPath))
|
||||
if parent == nil {
|
||||
return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
|
||||
}
|
||||
|
||||
info := &FileInfo{
|
||||
name: filepath.Base(relPath),
|
||||
children: make(map[string]*FileInfo),
|
||||
parent: parent,
|
||||
}
|
||||
|
||||
s, err := system.Lstat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info.stat = s
|
||||
|
||||
info.capability, _ = system.Lgetxattr(path, "security.capability")
|
||||
|
||||
parent.children[info.name] = info
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return root, nil
|
||||
}
|
36
vendor/github.com/docker/docker/pkg/archive/changes_unix.go
generated
vendored
Normal file
36
vendor/github.com/docker/docker/pkg/archive/changes_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
// +build !windows
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
func statDifferent(oldStat *system.StatT, newStat *system.StatT) bool {
|
||||
// Don't look at size for dirs, its not a good measure of change
|
||||
if oldStat.Mode() != newStat.Mode() ||
|
||||
oldStat.UID() != newStat.UID() ||
|
||||
oldStat.GID() != newStat.GID() ||
|
||||
oldStat.Rdev() != newStat.Rdev() ||
|
||||
// Don't look at size for dirs, its not a good measure of change
|
||||
(oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR &&
|
||||
(!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || (oldStat.Size() != newStat.Size()))) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (info *FileInfo) isDir() bool {
|
||||
return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR != 0
|
||||
}
|
||||
|
||||
func getIno(fi os.FileInfo) uint64 {
|
||||
return uint64(fi.Sys().(*syscall.Stat_t).Ino)
|
||||
}
|
||||
|
||||
func hasHardlinks(fi os.FileInfo) bool {
|
||||
return fi.Sys().(*syscall.Stat_t).Nlink > 1
|
||||
}
|
30
vendor/github.com/docker/docker/pkg/archive/changes_windows.go
generated
vendored
Normal file
30
vendor/github.com/docker/docker/pkg/archive/changes_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
package archive
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
func statDifferent(oldStat *system.StatT, newStat *system.StatT) bool {
|
||||
|
||||
// Don't look at size for dirs, its not a good measure of change
|
||||
if oldStat.ModTime() != newStat.ModTime() ||
|
||||
oldStat.Mode() != newStat.Mode() ||
|
||||
oldStat.Size() != newStat.Size() && !oldStat.IsDir() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (info *FileInfo) isDir() bool {
|
||||
return info.parent == nil || info.stat.IsDir()
|
||||
}
|
||||
|
||||
func getIno(fi os.FileInfo) (inode uint64) {
|
||||
return
|
||||
}
|
||||
|
||||
func hasHardlinks(fi os.FileInfo) bool {
|
||||
return false
|
||||
}
|
458
vendor/github.com/docker/docker/pkg/archive/copy.go
generated
vendored
Normal file
458
vendor/github.com/docker/docker/pkg/archive/copy.go
generated
vendored
Normal file
|
@ -0,0 +1,458 @@
|
|||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
// Errors used or returned by this file.
|
||||
var (
|
||||
ErrNotDirectory = errors.New("not a directory")
|
||||
ErrDirNotExists = errors.New("no such directory")
|
||||
ErrCannotCopyDir = errors.New("cannot copy directory")
|
||||
ErrInvalidCopySource = errors.New("invalid copy source content")
|
||||
)
|
||||
|
||||
// PreserveTrailingDotOrSeparator returns the given cleaned path (after
|
||||
// processing using any utility functions from the path or filepath stdlib
|
||||
// packages) and appends a trailing `/.` or `/` if its corresponding original
|
||||
// path (from before being processed by utility functions from the path or
|
||||
// filepath stdlib packages) ends with a trailing `/.` or `/`. If the cleaned
|
||||
// path already ends in a `.` path segment, then another is not added. If the
|
||||
// clean path already ends in a path separator, then another is not added.
|
||||
func PreserveTrailingDotOrSeparator(cleanedPath, originalPath string) string {
|
||||
// Ensure paths are in platform semantics
|
||||
cleanedPath = normalizePath(cleanedPath)
|
||||
originalPath = normalizePath(originalPath)
|
||||
|
||||
if !specifiesCurrentDir(cleanedPath) && specifiesCurrentDir(originalPath) {
|
||||
if !hasTrailingPathSeparator(cleanedPath) {
|
||||
// Add a separator if it doesn't already end with one (a cleaned
|
||||
// path would only end in a separator if it is the root).
|
||||
cleanedPath += string(filepath.Separator)
|
||||
}
|
||||
cleanedPath += "."
|
||||
}
|
||||
|
||||
if !hasTrailingPathSeparator(cleanedPath) && hasTrailingPathSeparator(originalPath) {
|
||||
cleanedPath += string(filepath.Separator)
|
||||
}
|
||||
|
||||
return cleanedPath
|
||||
}
|
||||
|
||||
// assertsDirectory returns whether the given path is
|
||||
// asserted to be a directory, i.e., the path ends with
|
||||
// a trailing '/' or `/.`, assuming a path separator of `/`.
|
||||
func assertsDirectory(path string) bool {
|
||||
return hasTrailingPathSeparator(path) || specifiesCurrentDir(path)
|
||||
}
|
||||
|
||||
// hasTrailingPathSeparator returns whether the given
|
||||
// path ends with the system's path separator character.
|
||||
func hasTrailingPathSeparator(path string) bool {
|
||||
return len(path) > 0 && os.IsPathSeparator(path[len(path)-1])
|
||||
}
|
||||
|
||||
// specifiesCurrentDir returns whether the given path specifies
|
||||
// a "current directory", i.e., the last path segment is `.`.
|
||||
func specifiesCurrentDir(path string) bool {
|
||||
return filepath.Base(path) == "."
|
||||
}
|
||||
|
||||
// SplitPathDirEntry splits the given path between its directory name and its
|
||||
// basename by first cleaning the path but preserves a trailing "." if the
|
||||
// original path specified the current directory.
|
||||
func SplitPathDirEntry(path string) (dir, base string) {
|
||||
cleanedPath := filepath.Clean(normalizePath(path))
|
||||
|
||||
if specifiesCurrentDir(path) {
|
||||
cleanedPath += string(filepath.Separator) + "."
|
||||
}
|
||||
|
||||
return filepath.Dir(cleanedPath), filepath.Base(cleanedPath)
|
||||
}
|
||||
|
||||
// TarResource archives the resource described by the given CopyInfo to a Tar
|
||||
// archive. A non-nil error is returned if sourcePath does not exist or is
|
||||
// asserted to be a directory but exists as another type of file.
|
||||
//
|
||||
// This function acts as a convenient wrapper around TarWithOptions, which
|
||||
// requires a directory as the source path. TarResource accepts either a
|
||||
// directory or a file path and correctly sets the Tar options.
|
||||
func TarResource(sourceInfo CopyInfo) (content Archive, err error) {
|
||||
return TarResourceRebase(sourceInfo.Path, sourceInfo.RebaseName)
|
||||
}
|
||||
|
||||
// TarResourceRebase is like TarResource but renames the first path element of
|
||||
// items in the resulting tar archive to match the given rebaseName if not "".
|
||||
func TarResourceRebase(sourcePath, rebaseName string) (content Archive, err error) {
|
||||
sourcePath = normalizePath(sourcePath)
|
||||
if _, err = os.Lstat(sourcePath); err != nil {
|
||||
// Catches the case where the source does not exist or is not a
|
||||
// directory if asserted to be a directory, as this also causes an
|
||||
// error.
|
||||
return
|
||||
}
|
||||
|
||||
// Separate the source path between it's directory and
|
||||
// the entry in that directory which we are archiving.
|
||||
sourceDir, sourceBase := SplitPathDirEntry(sourcePath)
|
||||
|
||||
filter := []string{sourceBase}
|
||||
|
||||
logrus.Debugf("copying %q from %q", sourceBase, sourceDir)
|
||||
|
||||
return TarWithOptions(sourceDir, &TarOptions{
|
||||
Compression: Uncompressed,
|
||||
IncludeFiles: filter,
|
||||
IncludeSourceDir: true,
|
||||
RebaseNames: map[string]string{
|
||||
sourceBase: rebaseName,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// CopyInfo holds basic info about the source
|
||||
// or destination path of a copy operation.
|
||||
type CopyInfo struct {
|
||||
Path string
|
||||
Exists bool
|
||||
IsDir bool
|
||||
RebaseName string
|
||||
}
|
||||
|
||||
// CopyInfoSourcePath stats the given path to create a CopyInfo
|
||||
// struct representing that resource for the source of an archive copy
|
||||
// operation. The given path should be an absolute local path. A source path
|
||||
// has all symlinks evaluated that appear before the last path separator ("/"
|
||||
// on Unix). As it is to be a copy source, the path must exist.
|
||||
func CopyInfoSourcePath(path string, followLink bool) (CopyInfo, error) {
|
||||
// normalize the file path and then evaluate the symbol link
|
||||
// we will use the target file instead of the symbol link if
|
||||
// followLink is set
|
||||
path = normalizePath(path)
|
||||
|
||||
resolvedPath, rebaseName, err := ResolveHostSourcePath(path, followLink)
|
||||
if err != nil {
|
||||
return CopyInfo{}, err
|
||||
}
|
||||
|
||||
stat, err := os.Lstat(resolvedPath)
|
||||
if err != nil {
|
||||
return CopyInfo{}, err
|
||||
}
|
||||
|
||||
return CopyInfo{
|
||||
Path: resolvedPath,
|
||||
Exists: true,
|
||||
IsDir: stat.IsDir(),
|
||||
RebaseName: rebaseName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CopyInfoDestinationPath stats the given path to create a CopyInfo
|
||||
// struct representing that resource for the destination of an archive copy
|
||||
// operation. The given path should be an absolute local path.
|
||||
func CopyInfoDestinationPath(path string) (info CopyInfo, err error) {
|
||||
maxSymlinkIter := 10 // filepath.EvalSymlinks uses 255, but 10 already seems like a lot.
|
||||
path = normalizePath(path)
|
||||
originalPath := path
|
||||
|
||||
stat, err := os.Lstat(path)
|
||||
|
||||
if err == nil && stat.Mode()&os.ModeSymlink == 0 {
|
||||
// The path exists and is not a symlink.
|
||||
return CopyInfo{
|
||||
Path: path,
|
||||
Exists: true,
|
||||
IsDir: stat.IsDir(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// While the path is a symlink.
|
||||
for n := 0; err == nil && stat.Mode()&os.ModeSymlink != 0; n++ {
|
||||
if n > maxSymlinkIter {
|
||||
// Don't follow symlinks more than this arbitrary number of times.
|
||||
return CopyInfo{}, errors.New("too many symlinks in " + originalPath)
|
||||
}
|
||||
|
||||
// The path is a symbolic link. We need to evaluate it so that the
|
||||
// destination of the copy operation is the link target and not the
|
||||
// link itself. This is notably different than CopyInfoSourcePath which
|
||||
// only evaluates symlinks before the last appearing path separator.
|
||||
// Also note that it is okay if the last path element is a broken
|
||||
// symlink as the copy operation should create the target.
|
||||
var linkTarget string
|
||||
|
||||
linkTarget, err = os.Readlink(path)
|
||||
if err != nil {
|
||||
return CopyInfo{}, err
|
||||
}
|
||||
|
||||
if !system.IsAbs(linkTarget) {
|
||||
// Join with the parent directory.
|
||||
dstParent, _ := SplitPathDirEntry(path)
|
||||
linkTarget = filepath.Join(dstParent, linkTarget)
|
||||
}
|
||||
|
||||
path = linkTarget
|
||||
stat, err = os.Lstat(path)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// It's okay if the destination path doesn't exist. We can still
|
||||
// continue the copy operation if the parent directory exists.
|
||||
if !os.IsNotExist(err) {
|
||||
return CopyInfo{}, err
|
||||
}
|
||||
|
||||
// Ensure destination parent dir exists.
|
||||
dstParent, _ := SplitPathDirEntry(path)
|
||||
|
||||
parentDirStat, err := os.Lstat(dstParent)
|
||||
if err != nil {
|
||||
return CopyInfo{}, err
|
||||
}
|
||||
if !parentDirStat.IsDir() {
|
||||
return CopyInfo{}, ErrNotDirectory
|
||||
}
|
||||
|
||||
return CopyInfo{Path: path}, nil
|
||||
}
|
||||
|
||||
// The path exists after resolving symlinks.
|
||||
return CopyInfo{
|
||||
Path: path,
|
||||
Exists: true,
|
||||
IsDir: stat.IsDir(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PrepareArchiveCopy prepares the given srcContent archive, which should
|
||||
// contain the archived resource described by srcInfo, to the destination
|
||||
// described by dstInfo. Returns the possibly modified content archive along
|
||||
// with the path to the destination directory which it should be extracted to.
|
||||
func PrepareArchiveCopy(srcContent Reader, srcInfo, dstInfo CopyInfo) (dstDir string, content Archive, err error) {
|
||||
// Ensure in platform semantics
|
||||
srcInfo.Path = normalizePath(srcInfo.Path)
|
||||
dstInfo.Path = normalizePath(dstInfo.Path)
|
||||
|
||||
// Separate the destination path between its directory and base
|
||||
// components in case the source archive contents need to be rebased.
|
||||
dstDir, dstBase := SplitPathDirEntry(dstInfo.Path)
|
||||
_, srcBase := SplitPathDirEntry(srcInfo.Path)
|
||||
|
||||
switch {
|
||||
case dstInfo.Exists && dstInfo.IsDir:
|
||||
// The destination exists as a directory. No alteration
|
||||
// to srcContent is needed as its contents can be
|
||||
// simply extracted to the destination directory.
|
||||
return dstInfo.Path, ioutil.NopCloser(srcContent), nil
|
||||
case dstInfo.Exists && srcInfo.IsDir:
|
||||
// The destination exists as some type of file and the source
|
||||
// content is a directory. This is an error condition since
|
||||
// you cannot copy a directory to an existing file location.
|
||||
return "", nil, ErrCannotCopyDir
|
||||
case dstInfo.Exists:
|
||||
// The destination exists as some type of file and the source content
|
||||
// is also a file. The source content entry will have to be renamed to
|
||||
// have a basename which matches the destination path's basename.
|
||||
if len(srcInfo.RebaseName) != 0 {
|
||||
srcBase = srcInfo.RebaseName
|
||||
}
|
||||
return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil
|
||||
case srcInfo.IsDir:
|
||||
// The destination does not exist and the source content is an archive
|
||||
// of a directory. The archive should be extracted to the parent of
|
||||
// the destination path instead, and when it is, the directory that is
|
||||
// created as a result should take the name of the destination path.
|
||||
// The source content entries will have to be renamed to have a
|
||||
// basename which matches the destination path's basename.
|
||||
if len(srcInfo.RebaseName) != 0 {
|
||||
srcBase = srcInfo.RebaseName
|
||||
}
|
||||
return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil
|
||||
case assertsDirectory(dstInfo.Path):
|
||||
// The destination does not exist and is asserted to be created as a
|
||||
// directory, but the source content is not a directory. This is an
|
||||
// error condition since you cannot create a directory from a file
|
||||
// source.
|
||||
return "", nil, ErrDirNotExists
|
||||
default:
|
||||
// The last remaining case is when the destination does not exist, is
|
||||
// not asserted to be a directory, and the source content is not an
|
||||
// archive of a directory. It this case, the destination file will need
|
||||
// to be created when the archive is extracted and the source content
|
||||
// entry will have to be renamed to have a basename which matches the
|
||||
// destination path's basename.
|
||||
if len(srcInfo.RebaseName) != 0 {
|
||||
srcBase = srcInfo.RebaseName
|
||||
}
|
||||
return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// RebaseArchiveEntries rewrites the given srcContent archive replacing
|
||||
// an occurrence of oldBase with newBase at the beginning of entry names.
|
||||
func RebaseArchiveEntries(srcContent Reader, oldBase, newBase string) Archive {
|
||||
if oldBase == string(os.PathSeparator) {
|
||||
// If oldBase specifies the root directory, use an empty string as
|
||||
// oldBase instead so that newBase doesn't replace the path separator
|
||||
// that all paths will start with.
|
||||
oldBase = ""
|
||||
}
|
||||
|
||||
rebased, w := io.Pipe()
|
||||
|
||||
go func() {
|
||||
srcTar := tar.NewReader(srcContent)
|
||||
rebasedTar := tar.NewWriter(w)
|
||||
|
||||
for {
|
||||
hdr, err := srcTar.Next()
|
||||
if err == io.EOF {
|
||||
// Signals end of archive.
|
||||
rebasedTar.Close()
|
||||
w.Close()
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
w.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
hdr.Name = strings.Replace(hdr.Name, oldBase, newBase, 1)
|
||||
|
||||
if err = rebasedTar.WriteHeader(hdr); err != nil {
|
||||
w.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = io.Copy(rebasedTar, srcTar); err != nil {
|
||||
w.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return rebased
|
||||
}
|
||||
|
||||
// CopyResource performs an archive copy from the given source path to the
|
||||
// given destination path. The source path MUST exist and the destination
|
||||
// path's parent directory must exist.
|
||||
func CopyResource(srcPath, dstPath string, followLink bool) error {
|
||||
var (
|
||||
srcInfo CopyInfo
|
||||
err error
|
||||
)
|
||||
|
||||
// Ensure in platform semantics
|
||||
srcPath = normalizePath(srcPath)
|
||||
dstPath = normalizePath(dstPath)
|
||||
|
||||
// Clean the source and destination paths.
|
||||
srcPath = PreserveTrailingDotOrSeparator(filepath.Clean(srcPath), srcPath)
|
||||
dstPath = PreserveTrailingDotOrSeparator(filepath.Clean(dstPath), dstPath)
|
||||
|
||||
if srcInfo, err = CopyInfoSourcePath(srcPath, followLink); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content, err := TarResource(srcInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer content.Close()
|
||||
|
||||
return CopyTo(content, srcInfo, dstPath)
|
||||
}
|
||||
|
||||
// CopyTo handles extracting the given content whose
|
||||
// entries should be sourced from srcInfo to dstPath.
|
||||
func CopyTo(content Reader, srcInfo CopyInfo, dstPath string) error {
|
||||
// The destination path need not exist, but CopyInfoDestinationPath will
|
||||
// ensure that at least the parent directory exists.
|
||||
dstInfo, err := CopyInfoDestinationPath(normalizePath(dstPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstDir, copyArchive, err := PrepareArchiveCopy(content, srcInfo, dstInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer copyArchive.Close()
|
||||
|
||||
options := &TarOptions{
|
||||
NoLchown: true,
|
||||
NoOverwriteDirNonDir: true,
|
||||
}
|
||||
|
||||
return Untar(copyArchive, dstDir, options)
|
||||
}
|
||||
|
||||
// ResolveHostSourcePath decides real path need to be copied with parameters such as
|
||||
// whether to follow symbol link or not, if followLink is true, resolvedPath will return
|
||||
// link target of any symbol link file, else it will only resolve symlink of directory
|
||||
// but return symbol link file itself without resolving.
|
||||
func ResolveHostSourcePath(path string, followLink bool) (resolvedPath, rebaseName string, err error) {
|
||||
if followLink {
|
||||
resolvedPath, err = filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resolvedPath, rebaseName = GetRebaseName(path, resolvedPath)
|
||||
} else {
|
||||
dirPath, basePath := filepath.Split(path)
|
||||
|
||||
// if not follow symbol link, then resolve symbol link of parent dir
|
||||
var resolvedDirPath string
|
||||
resolvedDirPath, err = filepath.EvalSymlinks(dirPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// resolvedDirPath will have been cleaned (no trailing path separators) so
|
||||
// we can manually join it with the base path element.
|
||||
resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath
|
||||
if hasTrailingPathSeparator(path) && filepath.Base(path) != filepath.Base(resolvedPath) {
|
||||
rebaseName = filepath.Base(path)
|
||||
}
|
||||
}
|
||||
return resolvedPath, rebaseName, nil
|
||||
}
|
||||
|
||||
// GetRebaseName normalizes and compares path and resolvedPath,
|
||||
// return completed resolved path and rebased file name
|
||||
func GetRebaseName(path, resolvedPath string) (string, string) {
|
||||
// linkTarget will have been cleaned (no trailing path separators and dot) so
|
||||
// we can manually join it with them
|
||||
var rebaseName string
|
||||
if specifiesCurrentDir(path) && !specifiesCurrentDir(resolvedPath) {
|
||||
resolvedPath += string(filepath.Separator) + "."
|
||||
}
|
||||
|
||||
if hasTrailingPathSeparator(path) && !hasTrailingPathSeparator(resolvedPath) {
|
||||
resolvedPath += string(filepath.Separator)
|
||||
}
|
||||
|
||||
if filepath.Base(path) != filepath.Base(resolvedPath) {
|
||||
// In the case where the path had a trailing separator and a symlink
|
||||
// evaluation has changed the last path component, we will need to
|
||||
// rebase the name in the archive that is being copied to match the
|
||||
// originally requested name.
|
||||
rebaseName = filepath.Base(path)
|
||||
}
|
||||
return resolvedPath, rebaseName
|
||||
}
|
11
vendor/github.com/docker/docker/pkg/archive/copy_unix.go
generated
vendored
Normal file
11
vendor/github.com/docker/docker/pkg/archive/copy_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
// +build !windows
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func normalizePath(path string) string {
|
||||
return filepath.ToSlash(path)
|
||||
}
|
9
vendor/github.com/docker/docker/pkg/archive/copy_windows.go
generated
vendored
Normal file
9
vendor/github.com/docker/docker/pkg/archive/copy_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
package archive
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func normalizePath(path string) string {
|
||||
return filepath.FromSlash(path)
|
||||
}
|
279
vendor/github.com/docker/docker/pkg/archive/diff.go
generated
vendored
Normal file
279
vendor/github.com/docker/docker/pkg/archive/diff.go
generated
vendored
Normal file
|
@ -0,0 +1,279 @@
|
|||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/pools"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
// UnpackLayer unpack `layer` to a `dest`. The stream `layer` can be
|
||||
// compressed or uncompressed.
|
||||
// Returns the size in bytes of the contents of the layer.
|
||||
func UnpackLayer(dest string, layer Reader, options *TarOptions) (size int64, err error) {
|
||||
tr := tar.NewReader(layer)
|
||||
trBuf := pools.BufioReader32KPool.Get(tr)
|
||||
defer pools.BufioReader32KPool.Put(trBuf)
|
||||
|
||||
var dirs []*tar.Header
|
||||
unpackedPaths := make(map[string]struct{})
|
||||
|
||||
if options == nil {
|
||||
options = &TarOptions{}
|
||||
}
|
||||
if options.ExcludePatterns == nil {
|
||||
options.ExcludePatterns = []string{}
|
||||
}
|
||||
remappedRootUID, remappedRootGID, err := idtools.GetRootUIDGID(options.UIDMaps, options.GIDMaps)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
aufsTempdir := ""
|
||||
aufsHardlinks := make(map[string]*tar.Header)
|
||||
|
||||
if options == nil {
|
||||
options = &TarOptions{}
|
||||
}
|
||||
// Iterate through the files in the archive.
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
// end of tar archive
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
size += hdr.Size
|
||||
|
||||
// Normalize name, for safety and for a simple is-root check
|
||||
hdr.Name = filepath.Clean(hdr.Name)
|
||||
|
||||
// Windows does not support filenames with colons in them. Ignore
|
||||
// these files. This is not a problem though (although it might
|
||||
// appear that it is). Let's suppose a client is running docker pull.
|
||||
// The daemon it points to is Windows. Would it make sense for the
|
||||
// client to be doing a docker pull Ubuntu for example (which has files
|
||||
// with colons in the name under /usr/share/man/man3)? No, absolutely
|
||||
// not as it would really only make sense that they were pulling a
|
||||
// Windows image. However, for development, it is necessary to be able
|
||||
// to pull Linux images which are in the repository.
|
||||
//
|
||||
// TODO Windows. Once the registry is aware of what images are Windows-
|
||||
// specific or Linux-specific, this warning should be changed to an error
|
||||
// to cater for the situation where someone does manage to upload a Linux
|
||||
// image but have it tagged as Windows inadvertently.
|
||||
if runtime.GOOS == "windows" {
|
||||
if strings.Contains(hdr.Name, ":") {
|
||||
logrus.Warnf("Windows: Ignoring %s (is this a Linux image?)", hdr.Name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Note as these operations are platform specific, so must the slash be.
|
||||
if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) {
|
||||
// Not the root directory, ensure that the parent directory exists.
|
||||
// This happened in some tests where an image had a tarfile without any
|
||||
// parent directories.
|
||||
parent := filepath.Dir(hdr.Name)
|
||||
parentPath := filepath.Join(dest, parent)
|
||||
|
||||
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
|
||||
err = system.MkdirAll(parentPath, 0600)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip AUFS metadata dirs
|
||||
if strings.HasPrefix(hdr.Name, WhiteoutMetaPrefix) {
|
||||
// Regular files inside /.wh..wh.plnk can be used as hardlink targets
|
||||
// We don't want this directory, but we need the files in them so that
|
||||
// such hardlinks can be resolved.
|
||||
if strings.HasPrefix(hdr.Name, WhiteoutLinkDir) && hdr.Typeflag == tar.TypeReg {
|
||||
basename := filepath.Base(hdr.Name)
|
||||
aufsHardlinks[basename] = hdr
|
||||
if aufsTempdir == "" {
|
||||
if aufsTempdir, err = ioutil.TempDir("", "dockerplnk"); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer os.RemoveAll(aufsTempdir)
|
||||
}
|
||||
if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, true, nil); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if hdr.Name != WhiteoutOpaqueDir {
|
||||
continue
|
||||
}
|
||||
}
|
||||
path := filepath.Join(dest, hdr.Name)
|
||||
rel, err := filepath.Rel(dest, path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Note as these operations are platform specific, so must the slash be.
|
||||
if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
||||
return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
|
||||
}
|
||||
base := filepath.Base(path)
|
||||
|
||||
if strings.HasPrefix(base, WhiteoutPrefix) {
|
||||
dir := filepath.Dir(path)
|
||||
if base == WhiteoutOpaqueDir {
|
||||
_, err := os.Lstat(dir)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = nil // parent was deleted
|
||||
}
|
||||
return err
|
||||
}
|
||||
if path == dir {
|
||||
return nil
|
||||
}
|
||||
if _, exists := unpackedPaths[path]; !exists {
|
||||
err := os.RemoveAll(path)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
} else {
|
||||
originalBase := base[len(WhiteoutPrefix):]
|
||||
originalPath := filepath.Join(dir, originalBase)
|
||||
if err := os.RemoveAll(originalPath); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If path exits we almost always just want to remove and replace it.
|
||||
// The only exception is when it is a directory *and* the file from
|
||||
// the layer is also a directory. Then we want to merge them (i.e.
|
||||
// just apply the metadata from the layer).
|
||||
if fi, err := os.Lstat(path); err == nil {
|
||||
if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trBuf.Reset(tr)
|
||||
srcData := io.Reader(trBuf)
|
||||
srcHdr := hdr
|
||||
|
||||
// Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
|
||||
// we manually retarget these into the temporary files we extracted them into
|
||||
if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), WhiteoutLinkDir) {
|
||||
linkBasename := filepath.Base(hdr.Linkname)
|
||||
srcHdr = aufsHardlinks[linkBasename]
|
||||
if srcHdr == nil {
|
||||
return 0, fmt.Errorf("Invalid aufs hardlink")
|
||||
}
|
||||
tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer tmpFile.Close()
|
||||
srcData = tmpFile
|
||||
}
|
||||
|
||||
// if the options contain a uid & gid maps, convert header uid/gid
|
||||
// entries using the maps such that lchown sets the proper mapped
|
||||
// uid/gid after writing the file. We only perform this mapping if
|
||||
// the file isn't already owned by the remapped root UID or GID, as
|
||||
// that specific uid/gid has no mapping from container -> host, and
|
||||
// those files already have the proper ownership for inside the
|
||||
// container.
|
||||
if srcHdr.Uid != remappedRootUID {
|
||||
xUID, err := idtools.ToHost(srcHdr.Uid, options.UIDMaps)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
srcHdr.Uid = xUID
|
||||
}
|
||||
if srcHdr.Gid != remappedRootGID {
|
||||
xGID, err := idtools.ToHost(srcHdr.Gid, options.GIDMaps)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
srcHdr.Gid = xGID
|
||||
}
|
||||
if err := createTarFile(path, dest, srcHdr, srcData, true, nil); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Directory mtimes must be handled at the end to avoid further
|
||||
// file creation in them to modify the directory mtime
|
||||
if hdr.Typeflag == tar.TypeDir {
|
||||
dirs = append(dirs, hdr)
|
||||
}
|
||||
unpackedPaths[path] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for _, hdr := range dirs {
|
||||
path := filepath.Join(dest, hdr.Name)
|
||||
if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// ApplyLayer parses a diff in the standard layer format from `layer`,
|
||||
// and applies it to the directory `dest`. The stream `layer` can be
|
||||
// compressed or uncompressed.
|
||||
// Returns the size in bytes of the contents of the layer.
|
||||
func ApplyLayer(dest string, layer Reader) (int64, error) {
|
||||
return applyLayerHandler(dest, layer, &TarOptions{}, true)
|
||||
}
|
||||
|
||||
// ApplyUncompressedLayer parses a diff in the standard layer format from
|
||||
// `layer`, and applies it to the directory `dest`. The stream `layer`
|
||||
// can only be uncompressed.
|
||||
// Returns the size in bytes of the contents of the layer.
|
||||
func ApplyUncompressedLayer(dest string, layer Reader, options *TarOptions) (int64, error) {
|
||||
return applyLayerHandler(dest, layer, options, false)
|
||||
}
|
||||
|
||||
// do the bulk load of ApplyLayer, but allow for not calling DecompressStream
|
||||
func applyLayerHandler(dest string, layer Reader, options *TarOptions, decompress bool) (int64, error) {
|
||||
dest = filepath.Clean(dest)
|
||||
|
||||
// We need to be able to set any perms
|
||||
oldmask, err := system.Umask(0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer system.Umask(oldmask) // ignore err, ErrNotSupportedPlatform
|
||||
|
||||
if decompress {
|
||||
layer, err = DecompressStream(layer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return UnpackLayer(dest, layer, options)
|
||||
}
|
97
vendor/github.com/docker/docker/pkg/archive/example_changes.go
generated
vendored
Normal file
97
vendor/github.com/docker/docker/pkg/archive/example_changes.go
generated
vendored
Normal file
|
@ -0,0 +1,97 @@
|
|||
// +build ignore
|
||||
|
||||
// Simple tool to create an archive stream from an old and new directory
|
||||
//
|
||||
// By default it will stream the comparison of two temporary directories with junk files
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
)
|
||||
|
||||
var (
|
||||
flDebug = flag.Bool("D", false, "debugging output")
|
||||
flNewDir = flag.String("newdir", "", "")
|
||||
flOldDir = flag.String("olddir", "", "")
|
||||
log = logrus.New()
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Println("Produce a tar from comparing two directory paths. By default a demo tar is created of around 200 files (including hardlinks)")
|
||||
fmt.Printf("%s [OPTIONS]\n", os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
log.Out = os.Stderr
|
||||
if (len(os.Getenv("DEBUG")) > 0) || *flDebug {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
var newDir, oldDir string
|
||||
|
||||
if len(*flNewDir) == 0 {
|
||||
var err error
|
||||
newDir, err = ioutil.TempDir("", "docker-test-newDir")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(newDir)
|
||||
if _, err := prepareUntarSourceDirectory(100, newDir, true); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
newDir = *flNewDir
|
||||
}
|
||||
|
||||
if len(*flOldDir) == 0 {
|
||||
oldDir, err := ioutil.TempDir("", "docker-test-oldDir")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(oldDir)
|
||||
} else {
|
||||
oldDir = *flOldDir
|
||||
}
|
||||
|
||||
changes, err := archive.ChangesDirs(newDir, oldDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
a, err := archive.ExportChanges(newDir, changes)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer a.Close()
|
||||
|
||||
i, err := io.Copy(os.Stdout, a)
|
||||
if err != nil && err != io.EOF {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "wrote archive of %d bytes", i)
|
||||
}
|
||||
|
||||
func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) {
|
||||
fileData := []byte("fooo")
|
||||
for n := 0; n < numberOfFiles; n++ {
|
||||
fileName := fmt.Sprintf("file-%d", n)
|
||||
if err := ioutil.WriteFile(path.Join(targetPath, fileName), fileData, 0700); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if makeLinks {
|
||||
if err := os.Link(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
totalSize := numberOfFiles * len(fileData)
|
||||
return totalSize, nil
|
||||
}
|
16
vendor/github.com/docker/docker/pkg/archive/time_linux.go
generated
vendored
Normal file
16
vendor/github.com/docker/docker/pkg/archive/time_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
package archive
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func timeToTimespec(time time.Time) (ts syscall.Timespec) {
|
||||
if time.IsZero() {
|
||||
// Return UTIME_OMIT special value
|
||||
ts.Sec = 0
|
||||
ts.Nsec = ((1 << 30) - 2)
|
||||
return
|
||||
}
|
||||
return syscall.NsecToTimespec(time.UnixNano())
|
||||
}
|
16
vendor/github.com/docker/docker/pkg/archive/time_unsupported.go
generated
vendored
Normal file
16
vendor/github.com/docker/docker/pkg/archive/time_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
// +build !linux
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func timeToTimespec(time time.Time) (ts syscall.Timespec) {
|
||||
nsec := int64(0)
|
||||
if !time.IsZero() {
|
||||
nsec = time.UnixNano()
|
||||
}
|
||||
return syscall.NsecToTimespec(nsec)
|
||||
}
|
23
vendor/github.com/docker/docker/pkg/archive/whiteouts.go
generated
vendored
Normal file
23
vendor/github.com/docker/docker/pkg/archive/whiteouts.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
package archive
|
||||
|
||||
// Whiteouts are files with a special meaning for the layered filesystem.
|
||||
// Docker uses AUFS whiteout files inside exported archives. In other
|
||||
// filesystems these files are generated/handled on tar creation/extraction.
|
||||
|
||||
// WhiteoutPrefix prefix means file is a whiteout. If this is followed by a
|
||||
// filename this means that file has been removed from the base layer.
|
||||
const WhiteoutPrefix = ".wh."
|
||||
|
||||
// WhiteoutMetaPrefix prefix means whiteout has a special meaning and is not
|
||||
// for removing an actual file. Normally these files are excluded from exported
|
||||
// archives.
|
||||
const WhiteoutMetaPrefix = WhiteoutPrefix + WhiteoutPrefix
|
||||
|
||||
// WhiteoutLinkDir is a directory AUFS uses for storing hardlink links to other
|
||||
// layers. Normally these should not go into exported archives and all changed
|
||||
// hardlinks should be copied to the top layer.
|
||||
const WhiteoutLinkDir = WhiteoutMetaPrefix + "plnk"
|
||||
|
||||
// WhiteoutOpaqueDir file means directory has been made opaque - meaning
|
||||
// readdir calls to this directory do not follow to lower layers.
|
||||
const WhiteoutOpaqueDir = WhiteoutMetaPrefix + ".opq"
|
59
vendor/github.com/docker/docker/pkg/archive/wrap.go
generated
vendored
Normal file
59
vendor/github.com/docker/docker/pkg/archive/wrap.go
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// Generate generates a new archive from the content provided
|
||||
// as input.
|
||||
//
|
||||
// `files` is a sequence of path/content pairs. A new file is
|
||||
// added to the archive for each pair.
|
||||
// If the last pair is incomplete, the file is created with an
|
||||
// empty content. For example:
|
||||
//
|
||||
// Generate("foo.txt", "hello world", "emptyfile")
|
||||
//
|
||||
// The above call will return an archive with 2 files:
|
||||
// * ./foo.txt with content "hello world"
|
||||
// * ./empty with empty content
|
||||
//
|
||||
// FIXME: stream content instead of buffering
|
||||
// FIXME: specify permissions and other archive metadata
|
||||
func Generate(input ...string) (Archive, error) {
|
||||
files := parseStringPairs(input...)
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(buf)
|
||||
for _, file := range files {
|
||||
name, content := file[0], file[1]
|
||||
hdr := &tar.Header{
|
||||
Name: name,
|
||||
Size: int64(len(content)),
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := tw.Write([]byte(content)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ioutil.NopCloser(buf), nil
|
||||
}
|
||||
|
||||
func parseStringPairs(input ...string) (output [][2]string) {
|
||||
output = make([][2]string, 0, len(input)/2+1)
|
||||
for i := 0; i < len(input); i += 2 {
|
||||
var pair [2]string
|
||||
pair[0] = input[i]
|
||||
if i+1 < len(input) {
|
||||
pair[1] = input[i+1]
|
||||
}
|
||||
output = append(output, pair)
|
||||
}
|
||||
return
|
||||
}
|
97
vendor/github.com/docker/docker/pkg/chrootarchive/archive.go
generated
vendored
Normal file
97
vendor/github.com/docker/docker/pkg/chrootarchive/archive.go
generated
vendored
Normal file
|
@ -0,0 +1,97 @@
|
|||
package chrootarchive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
)
|
||||
|
||||
var chrootArchiver = &archive.Archiver{Untar: Untar}
|
||||
|
||||
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
|
||||
// and unpacks it into the directory at `dest`.
|
||||
// The archive may be compressed with one of the following algorithms:
|
||||
// identity (uncompressed), gzip, bzip2, xz.
|
||||
func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
|
||||
return untarHandler(tarArchive, dest, options, true)
|
||||
}
|
||||
|
||||
// UntarUncompressed reads a stream of bytes from `archive`, parses it as a tar archive,
|
||||
// and unpacks it into the directory at `dest`.
|
||||
// The archive must be an uncompressed stream.
|
||||
func UntarUncompressed(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
|
||||
return untarHandler(tarArchive, dest, options, false)
|
||||
}
|
||||
|
||||
// Handler for teasing out the automatic decompression
|
||||
func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions, decompress bool) error {
|
||||
|
||||
if tarArchive == nil {
|
||||
return fmt.Errorf("Empty archive")
|
||||
}
|
||||
if options == nil {
|
||||
options = &archive.TarOptions{}
|
||||
}
|
||||
if options.ExcludePatterns == nil {
|
||||
options.ExcludePatterns = []string{}
|
||||
}
|
||||
|
||||
rootUID, rootGID, err := idtools.GetRootUIDGID(options.UIDMaps, options.GIDMaps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dest = filepath.Clean(dest)
|
||||
if _, err := os.Stat(dest); os.IsNotExist(err) {
|
||||
if err := idtools.MkdirAllNewAs(dest, 0755, rootUID, rootGID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
r := ioutil.NopCloser(tarArchive)
|
||||
if decompress {
|
||||
decompressedArchive, err := archive.DecompressStream(tarArchive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer decompressedArchive.Close()
|
||||
r = decompressedArchive
|
||||
}
|
||||
|
||||
return invokeUnpack(r, dest, options)
|
||||
}
|
||||
|
||||
// TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other.
|
||||
// If either Tar or Untar fails, TarUntar aborts and returns the error.
|
||||
func TarUntar(src, dst string) error {
|
||||
return chrootArchiver.TarUntar(src, dst)
|
||||
}
|
||||
|
||||
// CopyWithTar creates a tar archive of filesystem path `src`, and
|
||||
// unpacks it at filesystem path `dst`.
|
||||
// The archive is streamed directly with fixed buffering and no
|
||||
// intermediary disk IO.
|
||||
func CopyWithTar(src, dst string) error {
|
||||
return chrootArchiver.CopyWithTar(src, dst)
|
||||
}
|
||||
|
||||
// CopyFileWithTar emulates the behavior of the 'cp' command-line
|
||||
// for a single file. It copies a regular file from path `src` to
|
||||
// path `dst`, and preserves all its metadata.
|
||||
//
|
||||
// If `dst` ends with a trailing slash '/' ('\' on Windows), the final
|
||||
// destination path will be `dst/base(src)` or `dst\base(src)`
|
||||
func CopyFileWithTar(src, dst string) (err error) {
|
||||
return chrootArchiver.CopyFileWithTar(src, dst)
|
||||
}
|
||||
|
||||
// UntarPath is a convenience function which looks for an archive
|
||||
// at filesystem path `src`, and unpacks it at `dst`.
|
||||
func UntarPath(src, dst string) error {
|
||||
return chrootArchiver.UntarPath(src, dst)
|
||||
}
|
86
vendor/github.com/docker/docker/pkg/chrootarchive/archive_unix.go
generated
vendored
Normal file
86
vendor/github.com/docker/docker/pkg/chrootarchive/archive_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,86 @@
|
|||
// +build !windows
|
||||
|
||||
package chrootarchive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/reexec"
|
||||
)
|
||||
|
||||
// untar is the entry-point for docker-untar on re-exec. This is not used on
|
||||
// Windows as it does not support chroot, hence no point sandboxing through
|
||||
// chroot and rexec.
|
||||
func untar() {
|
||||
runtime.LockOSThread()
|
||||
flag.Parse()
|
||||
|
||||
var options *archive.TarOptions
|
||||
|
||||
//read the options from the pipe "ExtraFiles"
|
||||
if err := json.NewDecoder(os.NewFile(3, "options")).Decode(&options); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if err := chroot(flag.Arg(0)); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if err := archive.Unpack(os.Stdin, "/", options); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
// fully consume stdin in case it is zero padded
|
||||
if _, err := flush(os.Stdin); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.TarOptions) error {
|
||||
|
||||
// We can't pass a potentially large exclude list directly via cmd line
|
||||
// because we easily overrun the kernel's max argument/environment size
|
||||
// when the full image list is passed (e.g. when this is used by
|
||||
// `docker load`). We will marshall the options via a pipe to the
|
||||
// child
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Untar pipe failure: %v", err)
|
||||
}
|
||||
|
||||
cmd := reexec.Command("docker-untar", dest)
|
||||
cmd.Stdin = decompressedArchive
|
||||
|
||||
cmd.ExtraFiles = append(cmd.ExtraFiles, r)
|
||||
output := bytes.NewBuffer(nil)
|
||||
cmd.Stdout = output
|
||||
cmd.Stderr = output
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("Untar error on re-exec cmd: %v", err)
|
||||
}
|
||||
//write the options to the pipe for the untar exec to read
|
||||
if err := json.NewEncoder(w).Encode(options); err != nil {
|
||||
return fmt.Errorf("Untar json encode to pipe failed: %v", err)
|
||||
}
|
||||
w.Close()
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
// when `xz -d -c -q | docker-untar ...` failed on docker-untar side,
|
||||
// we need to exhaust `xz`'s output, otherwise the `xz` side will be
|
||||
// pending on write pipe forever
|
||||
io.Copy(ioutil.Discard, decompressedArchive)
|
||||
|
||||
return fmt.Errorf("Error processing tar file(%v): %s", err, output)
|
||||
}
|
||||
return nil
|
||||
}
|
22
vendor/github.com/docker/docker/pkg/chrootarchive/archive_windows.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker/pkg/chrootarchive/archive_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
package chrootarchive
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/longpath"
|
||||
)
|
||||
|
||||
// chroot is not supported by Windows
|
||||
func chroot(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func invokeUnpack(decompressedArchive io.ReadCloser,
|
||||
dest string,
|
||||
options *archive.TarOptions) error {
|
||||
// Windows is different to Linux here because Windows does not support
|
||||
// chroot. Hence there is no point sandboxing a chrooted process to
|
||||
// do the unpack. We call inline instead within the daemon process.
|
||||
return archive.Unpack(decompressedArchive, longpath.AddPrefix(dest), options)
|
||||
}
|
103
vendor/github.com/docker/docker/pkg/chrootarchive/chroot_linux.go
generated
vendored
Normal file
103
vendor/github.com/docker/docker/pkg/chrootarchive/chroot_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,103 @@
|
|||
package chrootarchive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
)
|
||||
|
||||
// chroot on linux uses pivot_root instead of chroot
|
||||
// pivot_root takes a new root and an old root.
|
||||
// Old root must be a sub-dir of new root, it is where the current rootfs will reside after the call to pivot_root.
|
||||
// New root is where the new rootfs is set to.
|
||||
// Old root is removed after the call to pivot_root so it is no longer available under the new root.
|
||||
// This is similar to how libcontainer sets up a container's rootfs
|
||||
func chroot(path string) (err error) {
|
||||
if err := syscall.Unshare(syscall.CLONE_NEWNS); err != nil {
|
||||
return fmt.Errorf("Error creating mount namespace before pivot: %v", err)
|
||||
}
|
||||
|
||||
if err := mount.MakeRPrivate(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// setup oldRoot for pivot_root
|
||||
pivotDir, err := ioutil.TempDir(path, ".pivot_root")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error setting up pivot dir: %v", err)
|
||||
}
|
||||
|
||||
var mounted bool
|
||||
defer func() {
|
||||
if mounted {
|
||||
// make sure pivotDir is not mounted before we try to remove it
|
||||
if errCleanup := syscall.Unmount(pivotDir, syscall.MNT_DETACH); errCleanup != nil {
|
||||
if err == nil {
|
||||
err = errCleanup
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
errCleanup := os.Remove(pivotDir)
|
||||
// pivotDir doesn't exist if pivot_root failed and chroot+chdir was successful
|
||||
// because we already cleaned it up on failed pivot_root
|
||||
if errCleanup != nil && !os.IsNotExist(errCleanup) {
|
||||
errCleanup = fmt.Errorf("Error cleaning up after pivot: %v", errCleanup)
|
||||
if err == nil {
|
||||
err = errCleanup
|
||||
}
|
||||
}
|
||||
|
||||
if errCleanup := syscall.Unmount("/", syscall.MNT_DETACH); errCleanup != nil {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("error unmounting root: %v", errCleanup)
|
||||
}
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
if err := syscall.PivotRoot(path, pivotDir); err != nil {
|
||||
// If pivot fails, fall back to the normal chroot after cleaning up temp dir
|
||||
if err := os.Remove(pivotDir); err != nil {
|
||||
return fmt.Errorf("Error cleaning up after failed pivot: %v", err)
|
||||
}
|
||||
return realChroot(path)
|
||||
}
|
||||
mounted = true
|
||||
|
||||
// This is the new path for where the old root (prior to the pivot) has been moved to
|
||||
// This dir contains the rootfs of the caller, which we need to remove so it is not visible during extraction
|
||||
pivotDir = filepath.Join("/", filepath.Base(pivotDir))
|
||||
|
||||
if err := syscall.Chdir("/"); err != nil {
|
||||
return fmt.Errorf("Error changing to new root: %v", err)
|
||||
}
|
||||
|
||||
// Make the pivotDir (where the old root lives) private so it can be unmounted without propagating to the host
|
||||
if err := syscall.Mount("", pivotDir, "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil {
|
||||
return fmt.Errorf("Error making old root private after pivot: %v", err)
|
||||
}
|
||||
|
||||
// Now unmount the old root so it's no longer visible from the new root
|
||||
if err := syscall.Unmount(pivotDir, syscall.MNT_DETACH); err != nil {
|
||||
return fmt.Errorf("Error while unmounting old root after pivot: %v", err)
|
||||
}
|
||||
mounted = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func realChroot(path string) error {
|
||||
if err := syscall.Chroot(path); err != nil {
|
||||
return fmt.Errorf("Error after fallback to chroot: %v", err)
|
||||
}
|
||||
if err := syscall.Chdir("/"); err != nil {
|
||||
return fmt.Errorf("Error changing to new root after chroot: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
12
vendor/github.com/docker/docker/pkg/chrootarchive/chroot_unix.go
generated
vendored
Normal file
12
vendor/github.com/docker/docker/pkg/chrootarchive/chroot_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
// +build !windows,!linux
|
||||
|
||||
package chrootarchive
|
||||
|
||||
import "syscall"
|
||||
|
||||
func chroot(path string) error {
|
||||
if err := syscall.Chroot(path); err != nil {
|
||||
return err
|
||||
}
|
||||
return syscall.Chdir("/")
|
||||
}
|
19
vendor/github.com/docker/docker/pkg/chrootarchive/diff.go
generated
vendored
Normal file
19
vendor/github.com/docker/docker/pkg/chrootarchive/diff.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
package chrootarchive
|
||||
|
||||
import "github.com/docker/docker/pkg/archive"
|
||||
|
||||
// ApplyLayer parses a diff in the standard layer format from `layer`,
|
||||
// and applies it to the directory `dest`. The stream `layer` can only be
|
||||
// uncompressed.
|
||||
// Returns the size in bytes of the contents of the layer.
|
||||
func ApplyLayer(dest string, layer archive.Reader) (size int64, err error) {
|
||||
return applyLayerHandler(dest, layer, &archive.TarOptions{}, true)
|
||||
}
|
||||
|
||||
// ApplyUncompressedLayer parses a diff in the standard layer format from
|
||||
// `layer`, and applies it to the directory `dest`. The stream `layer`
|
||||
// can only be uncompressed.
|
||||
// Returns the size in bytes of the contents of the layer.
|
||||
func ApplyUncompressedLayer(dest string, layer archive.Reader, options *archive.TarOptions) (int64, error) {
|
||||
return applyLayerHandler(dest, layer, options, false)
|
||||
}
|
120
vendor/github.com/docker/docker/pkg/chrootarchive/diff_unix.go
generated
vendored
Normal file
120
vendor/github.com/docker/docker/pkg/chrootarchive/diff_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,120 @@
|
|||
//+build !windows
|
||||
|
||||
package chrootarchive
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/reexec"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
type applyLayerResponse struct {
|
||||
LayerSize int64 `json:"layerSize"`
|
||||
}
|
||||
|
||||
// applyLayer is the entry-point for docker-applylayer on re-exec. This is not
|
||||
// used on Windows as it does not support chroot, hence no point sandboxing
|
||||
// through chroot and rexec.
|
||||
func applyLayer() {
|
||||
|
||||
var (
|
||||
tmpDir = ""
|
||||
err error
|
||||
options *archive.TarOptions
|
||||
)
|
||||
runtime.LockOSThread()
|
||||
flag.Parse()
|
||||
|
||||
if err := chroot(flag.Arg(0)); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
// We need to be able to set any perms
|
||||
oldmask, err := system.Umask(0)
|
||||
defer system.Umask(oldmask)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(os.Getenv("OPT")), &options); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if tmpDir, err = ioutil.TempDir("/", "temp-docker-extract"); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
os.Setenv("TMPDIR", tmpDir)
|
||||
size, err := archive.UnpackLayer("/", os.Stdin, options)
|
||||
os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(os.Stdout)
|
||||
if err := encoder.Encode(applyLayerResponse{size}); err != nil {
|
||||
fatal(fmt.Errorf("unable to encode layerSize JSON: %s", err))
|
||||
}
|
||||
|
||||
if _, err := flush(os.Stdin); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// applyLayerHandler parses a diff in the standard layer format from `layer`, and
|
||||
// applies it to the directory `dest`. Returns the size in bytes of the
|
||||
// contents of the layer.
|
||||
func applyLayerHandler(dest string, layer archive.Reader, options *archive.TarOptions, decompress bool) (size int64, err error) {
|
||||
dest = filepath.Clean(dest)
|
||||
if decompress {
|
||||
decompressed, err := archive.DecompressStream(layer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer decompressed.Close()
|
||||
|
||||
layer = decompressed
|
||||
}
|
||||
if options == nil {
|
||||
options = &archive.TarOptions{}
|
||||
}
|
||||
if options.ExcludePatterns == nil {
|
||||
options.ExcludePatterns = []string{}
|
||||
}
|
||||
|
||||
data, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ApplyLayer json encode: %v", err)
|
||||
}
|
||||
|
||||
cmd := reexec.Command("docker-applyLayer", dest)
|
||||
cmd.Stdin = layer
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("OPT=%s", data))
|
||||
|
||||
outBuf, errBuf := new(bytes.Buffer), new(bytes.Buffer)
|
||||
cmd.Stdout, cmd.Stderr = outBuf, errBuf
|
||||
|
||||
if err = cmd.Run(); err != nil {
|
||||
return 0, fmt.Errorf("ApplyLayer %s stdout: %s stderr: %s", err, outBuf, errBuf)
|
||||
}
|
||||
|
||||
// Stdout should be a valid JSON struct representing an applyLayerResponse.
|
||||
response := applyLayerResponse{}
|
||||
decoder := json.NewDecoder(outBuf)
|
||||
if err = decoder.Decode(&response); err != nil {
|
||||
return 0, fmt.Errorf("unable to decode ApplyLayer JSON response: %s", err)
|
||||
}
|
||||
|
||||
return response.LayerSize, nil
|
||||
}
|
44
vendor/github.com/docker/docker/pkg/chrootarchive/diff_windows.go
generated
vendored
Normal file
44
vendor/github.com/docker/docker/pkg/chrootarchive/diff_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
package chrootarchive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/longpath"
|
||||
)
|
||||
|
||||
// applyLayerHandler parses a diff in the standard layer format from `layer`, and
|
||||
// applies it to the directory `dest`. Returns the size in bytes of the
|
||||
// contents of the layer.
|
||||
func applyLayerHandler(dest string, layer archive.Reader, options *archive.TarOptions, decompress bool) (size int64, err error) {
|
||||
dest = filepath.Clean(dest)
|
||||
|
||||
// Ensure it is a Windows-style volume path
|
||||
dest = longpath.AddPrefix(dest)
|
||||
|
||||
if decompress {
|
||||
decompressed, err := archive.DecompressStream(layer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer decompressed.Close()
|
||||
|
||||
layer = decompressed
|
||||
}
|
||||
|
||||
tmpDir, err := ioutil.TempDir(os.Getenv("temp"), "temp-docker-extract")
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ApplyLayer failed to create temp-docker-extract under %s. %s", dest, err)
|
||||
}
|
||||
|
||||
s, err := archive.UnpackLayer(dest, layer, nil)
|
||||
os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ApplyLayer %s failed UnpackLayer to %s", err, dest)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
28
vendor/github.com/docker/docker/pkg/chrootarchive/init_unix.go
generated
vendored
Normal file
28
vendor/github.com/docker/docker/pkg/chrootarchive/init_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
// +build !windows
|
||||
|
||||
package chrootarchive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/docker/docker/pkg/reexec"
|
||||
)
|
||||
|
||||
func init() {
|
||||
reexec.Register("docker-applyLayer", applyLayer)
|
||||
reexec.Register("docker-untar", untar)
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// flush consumes all the bytes from the reader discarding
|
||||
// any errors
|
||||
func flush(r io.Reader) (bytes int64, err error) {
|
||||
return io.Copy(ioutil.Discard, r)
|
||||
}
|
4
vendor/github.com/docker/docker/pkg/chrootarchive/init_windows.go
generated
vendored
Normal file
4
vendor/github.com/docker/docker/pkg/chrootarchive/init_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
package chrootarchive
|
||||
|
||||
func init() {
|
||||
}
|
283
vendor/github.com/docker/docker/pkg/fileutils/fileutils.go
generated
vendored
Normal file
283
vendor/github.com/docker/docker/pkg/fileutils/fileutils.go
generated
vendored
Normal file
|
@ -0,0 +1,283 @@
|
|||
package fileutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// exclusion returns true if the specified pattern is an exclusion
|
||||
func exclusion(pattern string) bool {
|
||||
return pattern[0] == '!'
|
||||
}
|
||||
|
||||
// empty returns true if the specified pattern is empty
|
||||
func empty(pattern string) bool {
|
||||
return pattern == ""
|
||||
}
|
||||
|
||||
// CleanPatterns takes a slice of patterns returns a new
|
||||
// slice of patterns cleaned with filepath.Clean, stripped
|
||||
// of any empty patterns and lets the caller know whether the
|
||||
// slice contains any exception patterns (prefixed with !).
|
||||
func CleanPatterns(patterns []string) ([]string, [][]string, bool, error) {
|
||||
// Loop over exclusion patterns and:
|
||||
// 1. Clean them up.
|
||||
// 2. Indicate whether we are dealing with any exception rules.
|
||||
// 3. Error if we see a single exclusion marker on it's own (!).
|
||||
cleanedPatterns := []string{}
|
||||
patternDirs := [][]string{}
|
||||
exceptions := false
|
||||
for _, pattern := range patterns {
|
||||
// Eliminate leading and trailing whitespace.
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
if empty(pattern) {
|
||||
continue
|
||||
}
|
||||
if exclusion(pattern) {
|
||||
if len(pattern) == 1 {
|
||||
return nil, nil, false, errors.New("Illegal exclusion pattern: !")
|
||||
}
|
||||
exceptions = true
|
||||
}
|
||||
pattern = filepath.Clean(pattern)
|
||||
cleanedPatterns = append(cleanedPatterns, pattern)
|
||||
if exclusion(pattern) {
|
||||
pattern = pattern[1:]
|
||||
}
|
||||
patternDirs = append(patternDirs, strings.Split(pattern, string(os.PathSeparator)))
|
||||
}
|
||||
|
||||
return cleanedPatterns, patternDirs, exceptions, nil
|
||||
}
|
||||
|
||||
// Matches returns true if file matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns.
|
||||
func Matches(file string, patterns []string) (bool, error) {
|
||||
file = filepath.Clean(file)
|
||||
|
||||
if file == "." {
|
||||
// Don't let them exclude everything, kind of silly.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
patterns, patDirs, _, err := CleanPatterns(patterns)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return OptimizedMatches(file, patterns, patDirs)
|
||||
}
|
||||
|
||||
// OptimizedMatches is basically the same as fileutils.Matches() but optimized for archive.go.
|
||||
// It will assume that the inputs have been preprocessed and therefore the function
|
||||
// doesn't need to do as much error checking and clean-up. This was done to avoid
|
||||
// repeating these steps on each file being checked during the archive process.
|
||||
// The more generic fileutils.Matches() can't make these assumptions.
|
||||
func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, error) {
|
||||
matched := false
|
||||
file = filepath.FromSlash(file)
|
||||
parentPath := filepath.Dir(file)
|
||||
parentPathDirs := strings.Split(parentPath, string(os.PathSeparator))
|
||||
|
||||
for i, pattern := range patterns {
|
||||
negative := false
|
||||
|
||||
if exclusion(pattern) {
|
||||
negative = true
|
||||
pattern = pattern[1:]
|
||||
}
|
||||
|
||||
match, err := regexpMatch(pattern, file)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Error in pattern (%s): %s", pattern, err)
|
||||
}
|
||||
|
||||
if !match && parentPath != "." {
|
||||
// Check to see if the pattern matches one of our parent dirs.
|
||||
if len(patDirs[i]) <= len(parentPathDirs) {
|
||||
match, _ = regexpMatch(strings.Join(patDirs[i], string(os.PathSeparator)),
|
||||
strings.Join(parentPathDirs[:len(patDirs[i])], string(os.PathSeparator)))
|
||||
}
|
||||
}
|
||||
|
||||
if match {
|
||||
matched = !negative
|
||||
}
|
||||
}
|
||||
|
||||
if matched {
|
||||
logrus.Debugf("Skipping excluded path: %s", file)
|
||||
}
|
||||
|
||||
return matched, nil
|
||||
}
|
||||
|
||||
// regexpMatch tries to match the logic of filepath.Match but
|
||||
// does so using regexp logic. We do this so that we can expand the
|
||||
// wildcard set to include other things, like "**" to mean any number
|
||||
// of directories. This means that we should be backwards compatible
|
||||
// with filepath.Match(). We'll end up supporting more stuff, due to
|
||||
// the fact that we're using regexp, but that's ok - it does no harm.
|
||||
//
|
||||
// As per the comment in golangs filepath.Match, on Windows, escaping
|
||||
// is disabled. Instead, '\\' is treated as path separator.
|
||||
func regexpMatch(pattern, path string) (bool, error) {
|
||||
regStr := "^"
|
||||
|
||||
// Do some syntax checking on the pattern.
|
||||
// filepath's Match() has some really weird rules that are inconsistent
|
||||
// so instead of trying to dup their logic, just call Match() for its
|
||||
// error state and if there is an error in the pattern return it.
|
||||
// If this becomes an issue we can remove this since its really only
|
||||
// needed in the error (syntax) case - which isn't really critical.
|
||||
if _, err := filepath.Match(pattern, path); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Go through the pattern and convert it to a regexp.
|
||||
// We use a scanner so we can support utf-8 chars.
|
||||
var scan scanner.Scanner
|
||||
scan.Init(strings.NewReader(pattern))
|
||||
|
||||
sl := string(os.PathSeparator)
|
||||
escSL := sl
|
||||
if sl == `\` {
|
||||
escSL += `\`
|
||||
}
|
||||
|
||||
for scan.Peek() != scanner.EOF {
|
||||
ch := scan.Next()
|
||||
|
||||
if ch == '*' {
|
||||
if scan.Peek() == '*' {
|
||||
// is some flavor of "**"
|
||||
scan.Next()
|
||||
|
||||
if scan.Peek() == scanner.EOF {
|
||||
// is "**EOF" - to align with .gitignore just accept all
|
||||
regStr += ".*"
|
||||
} else {
|
||||
// is "**"
|
||||
regStr += "((.*" + escSL + ")|([^" + escSL + "]*))"
|
||||
}
|
||||
|
||||
// Treat **/ as ** so eat the "/"
|
||||
if string(scan.Peek()) == sl {
|
||||
scan.Next()
|
||||
}
|
||||
} else {
|
||||
// is "*" so map it to anything but "/"
|
||||
regStr += "[^" + escSL + "]*"
|
||||
}
|
||||
} else if ch == '?' {
|
||||
// "?" is any char except "/"
|
||||
regStr += "[^" + escSL + "]"
|
||||
} else if strings.Index(".$", string(ch)) != -1 {
|
||||
// Escape some regexp special chars that have no meaning
|
||||
// in golang's filepath.Match
|
||||
regStr += `\` + string(ch)
|
||||
} else if ch == '\\' {
|
||||
// escape next char. Note that a trailing \ in the pattern
|
||||
// will be left alone (but need to escape it)
|
||||
if sl == `\` {
|
||||
// On windows map "\" to "\\", meaning an escaped backslash,
|
||||
// and then just continue because filepath.Match on
|
||||
// Windows doesn't allow escaping at all
|
||||
regStr += escSL
|
||||
continue
|
||||
}
|
||||
if scan.Peek() != scanner.EOF {
|
||||
regStr += `\` + string(scan.Next())
|
||||
} else {
|
||||
regStr += `\`
|
||||
}
|
||||
} else {
|
||||
regStr += string(ch)
|
||||
}
|
||||
}
|
||||
|
||||
regStr += "$"
|
||||
|
||||
res, err := regexp.MatchString(regStr, path)
|
||||
|
||||
// Map regexp's error to filepath's so no one knows we're not using filepath
|
||||
if err != nil {
|
||||
err = filepath.ErrBadPattern
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// CopyFile copies from src to dst until either EOF is reached
|
||||
// on src or an error occurs. It verifies src exists and removes
|
||||
// the dst if it exists.
|
||||
func CopyFile(src, dst string) (int64, error) {
|
||||
cleanSrc := filepath.Clean(src)
|
||||
cleanDst := filepath.Clean(dst)
|
||||
if cleanSrc == cleanDst {
|
||||
return 0, nil
|
||||
}
|
||||
sf, err := os.Open(cleanSrc)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer sf.Close()
|
||||
if err := os.Remove(cleanDst); err != nil && !os.IsNotExist(err) {
|
||||
return 0, err
|
||||
}
|
||||
df, err := os.Create(cleanDst)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer df.Close()
|
||||
return io.Copy(df, sf)
|
||||
}
|
||||
|
||||
// ReadSymlinkedDirectory returns the target directory of a symlink.
|
||||
// The target of the symbolic link may not be a file.
|
||||
func ReadSymlinkedDirectory(path string) (string, error) {
|
||||
var realPath string
|
||||
var err error
|
||||
if realPath, err = filepath.Abs(path); err != nil {
|
||||
return "", fmt.Errorf("unable to get absolute path for %s: %s", path, err)
|
||||
}
|
||||
if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
|
||||
return "", fmt.Errorf("failed to canonicalise path for %s: %s", path, err)
|
||||
}
|
||||
realPathInfo, err := os.Stat(realPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to stat target '%s' of '%s': %s", realPath, path, err)
|
||||
}
|
||||
if !realPathInfo.Mode().IsDir() {
|
||||
return "", fmt.Errorf("canonical path points to a file '%s'", realPath)
|
||||
}
|
||||
return realPath, nil
|
||||
}
|
||||
|
||||
// CreateIfNotExists creates a file or a directory only if it does not already exist.
|
||||
func CreateIfNotExists(path string, isDir bool) error {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if isDir {
|
||||
return os.MkdirAll(path, 0755)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(path, os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
27
vendor/github.com/docker/docker/pkg/fileutils/fileutils_darwin.go
generated
vendored
Normal file
27
vendor/github.com/docker/docker/pkg/fileutils/fileutils_darwin.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
package fileutils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetTotalUsedFds returns the number of used File Descriptors by
|
||||
// executing `lsof -p PID`
|
||||
func GetTotalUsedFds() int {
|
||||
pid := os.Getpid()
|
||||
|
||||
cmd := exec.Command("lsof", "-p", strconv.Itoa(pid))
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
outputStr := strings.TrimSpace(string(output))
|
||||
|
||||
fds := strings.Split(outputStr, "\n")
|
||||
|
||||
return len(fds) - 1
|
||||
}
|
7
vendor/github.com/docker/docker/pkg/fileutils/fileutils_solaris.go
generated
vendored
Normal file
7
vendor/github.com/docker/docker/pkg/fileutils/fileutils_solaris.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package fileutils
|
||||
|
||||
// GetTotalUsedFds Returns the number of used File Descriptors.
|
||||
// On Solaris these limits are per process and not systemwide
|
||||
func GetTotalUsedFds() int {
|
||||
return -1
|
||||
}
|
22
vendor/github.com/docker/docker/pkg/fileutils/fileutils_unix.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker/pkg/fileutils/fileutils_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
// +build linux freebsd
|
||||
|
||||
package fileutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// GetTotalUsedFds Returns the number of used File Descriptors by
|
||||
// reading it via /proc filesystem.
|
||||
func GetTotalUsedFds() int {
|
||||
if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil {
|
||||
logrus.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err)
|
||||
} else {
|
||||
return len(fds)
|
||||
}
|
||||
return -1
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue