Update dependencies
This commit is contained in:
parent
bf5616c65b
commit
d6d374b28d
13962 changed files with 48226 additions and 3618880 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>
|
||||
182
vendor/github.com/docker/distribution/AUTHORS
generated
vendored
Normal file
182
vendor/github.com/docker/distribution/AUTHORS
generated
vendored
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
a-palchikov <deemok@gmail.com>
|
||||
Aaron Lehmann <aaron.lehmann@docker.com>
|
||||
Aaron Schlesinger <aschlesinger@deis.com>
|
||||
Aaron Vinson <avinson.public@gmail.com>
|
||||
Adam Duke <adam.v.duke@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>
|
||||
Anusha Ragunathan <anusha@docker.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 Bodenmiller <bbodenmiller@hotmail.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>
|
||||
Cezar Sa Espinola <cezarsa@gmail.com>
|
||||
Charles Smith <charles.smith@docker.com>
|
||||
Chris Dillon <squarism@gmail.com>
|
||||
cuiwei13 <cuiwei13@pku.edu.cn>
|
||||
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>
|
||||
Edgar Lee <edgar.lee@docker.com>
|
||||
Eric Yang <windfarer@gmail.com>
|
||||
Fabio Berchtold <jamesclonk@jamesclonk.ch>
|
||||
Fabio Huser <fabio@fh1.ch>
|
||||
farmerworking <farmerworking@gmail.com>
|
||||
Felix Yan <felixonmars@archlinux.org>
|
||||
Florentin Raud <florentin.raud@gmail.com>
|
||||
Frank Chen <frankchn@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>
|
||||
James Findley <jfindley@fastmail.com>
|
||||
Jason Freidman <jason.freidman@gmail.com>
|
||||
Jason Heiss <jheiss@aput.net>
|
||||
Jeff Nickoloff <jeff@allingeek.com>
|
||||
Jess Frazelle <acidburn@google.com>
|
||||
Jessie Frazelle <jessie@docker.com>
|
||||
jhaohai <jhaohai@foxmail.com>
|
||||
Jianqing Wang <tsing@jianqing.org>
|
||||
Jihoon Chung <jihoon@gmail.com>
|
||||
Joao Fernandes <joao.fernandes@docker.com>
|
||||
John Mulhausen <john@docker.com>
|
||||
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 Chorlton <josh.chorlton@docker.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>
|
||||
Lloyd Ramey <lnr0626@gmail.com>
|
||||
Louis Kottmann <louis.kottmann@gmail.com>
|
||||
Luke Carpenter <x@rubynerd.net>
|
||||
Marcus Martins <marcus@docker.com>
|
||||
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>
|
||||
Matthew Green <greenmr@live.co.uk>
|
||||
Michael Prokop <mika@grml.org>
|
||||
Michal Minar <miminar@redhat.com>
|
||||
Michal Minář <miminar@redhat.com>
|
||||
Mike Brown <brownwm@us.ibm.com>
|
||||
Miquel Sabaté <msabate@suse.com>
|
||||
Misty Stanley-Jones <misty@apache.org>
|
||||
Misty Stanley-Jones <misty@docker.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>
|
||||
Noah Treuhaft <noah.treuhaft@docker.com>
|
||||
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>
|
||||
Pierre-Yves Ritschard <pyr@spootnik.org>
|
||||
Qiao Anran <qiaoanran@gmail.com>
|
||||
Randy Barlow <randy@electronsweatshop.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>
|
||||
Sebastien Coavoux <s.coavoux@free.fr>
|
||||
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>
|
||||
spacexnice <yaoyao.xyy@alibaba-inc.com>
|
||||
Spencer Rinehart <anubis@overthemonkey.com>
|
||||
Stan Hu <stanhu@gmail.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>
|
||||
Victor Vieux <vieux@docker.com>
|
||||
Victoria Bialas <victoria.bialas@docker.com>
|
||||
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>
|
||||
yaoyao.xyy <yaoyao.xyy@alibaba-inc.com>
|
||||
yuexiao-wang <wang.yuexiao@zte.com.cn>
|
||||
yuzou <zouyu7@huawei.com>
|
||||
zhouhaibing089 <zhouhaibing089@gmail.com>
|
||||
姜继忠 <jizhong.jiangjz@alibaba-inc.com>
|
||||
117
vendor/github.com/docker/distribution/BUILDING.md
generated
vendored
Normal file
117
vendor/github.com/docker/distribution/BUILDING.md
generated
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
|
||||
# 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](https://github.com/docker/docker.github.io/blob/master/registry/recipes/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/golang/lint/golint
|
||||
|
||||
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 vendor
|
||||
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`.
|
||||
108
vendor/github.com/docker/distribution/CHANGELOG.md
generated
vendored
Normal file
108
vendor/github.com/docker/distribution/CHANGELOG.md
generated
vendored
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
# Changelog
|
||||
|
||||
## 2.6.0 (2017-01-18)
|
||||
|
||||
#### Storage
|
||||
- S3: fixed bug in delete due to read-after-write inconsistency
|
||||
- S3: allow EC2 IAM roles to be used when authorizing region endpoints
|
||||
- S3: add Object ACL Support
|
||||
- S3: fix delete method's notion of subpaths
|
||||
- S3: use multipart upload API in `Move` method for performance
|
||||
- S3: add v2 signature signing for legacy S3 clones
|
||||
- Swift: add simple heuristic to detect incomplete DLOs during read ops
|
||||
- Swift: support different user and tenant domains
|
||||
- Swift: bulk deletes in chunks
|
||||
- Aliyun OSS: fix delete method's notion of subpaths
|
||||
- Aliyun OSS: optimize data copy after upload finishes
|
||||
- Azure: close leaking response body
|
||||
- Fix storage drivers dropping non-EOF errors when listing repositories
|
||||
- Compare path properly when listing repositories in catalog
|
||||
- Add a foreign layer URL host whitelist
|
||||
- Improve catalog enumerate runtime
|
||||
|
||||
#### Registry
|
||||
- Export `storage.CreateOptions` in top-level package
|
||||
- Enable notifications to endpoints that use self-signed certificates
|
||||
- Properly validate multi-URL foreign layers
|
||||
- Add control over validation of URLs in pushed manifests
|
||||
- Proxy mode: fix socket leak when pull is cancelled
|
||||
- Tag service: properly handle error responses on HEAD request
|
||||
- Support for custom authentication URL in proxying registry
|
||||
- Add configuration option to disable access logging
|
||||
- Add notification filtering by target media type
|
||||
- Manifest: `References()` returns all children
|
||||
- Honor `X-Forwarded-Port` and Forwarded headers
|
||||
- Reference: Preserve tag and digest in With* functions
|
||||
- Add policy configuration for enforcing repository classes
|
||||
|
||||
#### Client
|
||||
- Changes the client Tags `All()` method to follow links
|
||||
- Allow registry clients to connect via HTTP2
|
||||
- Better handling of OAuth errors in client
|
||||
|
||||
#### Spec
|
||||
- Manifest: clarify relationship between urls and foreign layers
|
||||
- Authorization: add support for repository classes
|
||||
|
||||
#### Manifest
|
||||
- Override media type returned from `Stat()` for existing manifests
|
||||
- Add plugin mediatype to distribution manifest
|
||||
|
||||
#### Docs
|
||||
- Document `TOOMANYREQUESTS` error code
|
||||
- Document required Let's Encrypt port
|
||||
- Improve documentation around implementation of OAuth2
|
||||
- Improve documentation for configuration
|
||||
|
||||
#### Auth
|
||||
- Add support for registry type in scope
|
||||
- Add support for using v2 ping challenges for v1
|
||||
- Add leeway to JWT `nbf` and `exp` checking
|
||||
- htpasswd: dynamically parse htpasswd file
|
||||
- Fix missing auth headers with PATCH HTTP request when pushing to default port
|
||||
|
||||
#### Dockerfile
|
||||
- Update to go1.7
|
||||
- Reorder Dockerfile steps for better layer caching
|
||||
|
||||
#### Notes
|
||||
|
||||
Documentation has moved to the documentation repository at
|
||||
`github.com/docker/docker.github.io/tree/master/registry`
|
||||
|
||||
The registry is go 1.7 compliant, and passes newer, more restrictive `lint` and `vet` ing.
|
||||
|
||||
|
||||
## 2.5.0 (2016-06-14)
|
||||
|
||||
#### Storage
|
||||
- Ensure uploads directory is cleaned after upload is committed
|
||||
- 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
|
||||
|
||||
|
||||
148
vendor/github.com/docker/distribution/CONTRIBUTING.md
generated
vendored
Normal file
148
vendor/github.com/docker/distribution/CONTRIBUTING.md
generated
vendored
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
# 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 security issues
|
||||
|
||||
The Docker maintainers take security seriously. If you discover a security
|
||||
issue, please bring it to their attention right away!
|
||||
|
||||
Please **DO NOT** file a public issue, instead send your report privately to
|
||||
[security@docker.com](mailto:security@docker.com).
|
||||
|
||||
## 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.
|
||||
21
vendor/github.com/docker/distribution/Dockerfile
generated
vendored
Normal file
21
vendor/github.com/docker/distribution/Dockerfile
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
FROM golang:1.8-alpine
|
||||
|
||||
ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
|
||||
ENV DOCKER_BUILDTAGS include_oss include_gcs
|
||||
|
||||
ARG GOOS=linux
|
||||
ARG GOARCH=amd64
|
||||
|
||||
RUN set -ex \
|
||||
&& apk add --no-cache make git
|
||||
|
||||
WORKDIR $DISTRIBUTION_DIR
|
||||
COPY . $DISTRIBUTION_DIR
|
||||
COPY cmd/registry/config-dev.yml /etc/docker/registry/config.yml
|
||||
|
||||
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"
|
||||
99
vendor/github.com/docker/distribution/Makefile
generated
vendored
Normal file
99
vendor/github.com/docker/distribution/Makefile
generated
vendored
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
# 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: all build binaries clean dep-restore dep-save dep-validate fmt lint test test-full vet
|
||||
.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
|
||||
|
||||
# Go files
|
||||
GOFILES=$(shell find . -type f -name '*.go')
|
||||
|
||||
# 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 '')
|
||||
VNDR=$(shell which vndr || echo '')
|
||||
|
||||
${PREFIX}/bin/registry: $(GOFILES)
|
||||
@echo "+ $@"
|
||||
@go build -tags "${DOCKER_BUILDTAGS}" -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./cmd/registry
|
||||
|
||||
${PREFIX}/bin/digest: $(GOFILES)
|
||||
@echo "+ $@"
|
||||
@go build -tags "${DOCKER_BUILDTAGS}" -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./cmd/digest
|
||||
|
||||
${PREFIX}/bin/registry-api-descriptor-template: $(GOFILES)
|
||||
@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-validate:
|
||||
@echo "+ $@"
|
||||
$(if $(VNDR), , \
|
||||
$(error Please install vndr: go get github.com/lk4d4/vndr))
|
||||
@rm -Rf .vendor.bak
|
||||
@mv vendor .vendor.bak
|
||||
@$(VNDR)
|
||||
@test -z "$$(diff -r vendor .vendor.bak 2>&1 | tee /dev/stderr)" || \
|
||||
(echo >&2 "+ inconsistent dependencies! what you have in vendor.conf does not match with what you have in vendor" && false)
|
||||
@rm -Rf vendor
|
||||
@mv .vendor.bak vendor
|
||||
130
vendor/github.com/docker/distribution/README.md
generated
vendored
Normal file
130
vendor/github.com/docker/distribution/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# 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/) 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](https://github.com/docker/docker.github.io/blob/master/registry/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](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).
|
||||
44
vendor/github.com/docker/distribution/RELEASE-CHECKLIST.md
generated
vendored
Normal file
44
vendor/github.com/docker/distribution/RELEASE-CHECKLIST.md
generated
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
## Registry Release Checklist
|
||||
|
||||
10. Compile release notes detailing features and since the last release.
|
||||
|
||||
Update the `CHANGELOG.md` file and create a PR to master with the updates.
|
||||
Once that PR has been approved by maintainers the change may be cherry-picked
|
||||
to the release branch (new release branches may be forked from this commit).
|
||||
|
||||
20. Update the version file: `https://github.com/docker/distribution/blob/master/version/version.go`
|
||||
|
||||
30. Update the `MAINTAINERS` (if necessary), `AUTHORS` and `.mailmap` files.
|
||||
|
||||
```
|
||||
make AUTHORS
|
||||
```
|
||||
|
||||
40. Create a signed tag.
|
||||
|
||||
Distribution uses semantic versioning. Tags are of the format
|
||||
`vx.y.z[-rcn]`. You will need PGP installed and a PGP key which has been added
|
||||
to your Github account. The comment for the tag should include the release
|
||||
notes, use previous tags as a guide for formatting consistently. Run
|
||||
`git tag -s vx.y.z[-rcn]` to create tag and `git -v vx.y.z[-rcn]` to verify tag,
|
||||
check comment and correct commit hash.
|
||||
|
||||
50. Push the signed tag
|
||||
|
||||
60. Create a new [release](https://github.com/docker/distribution/releases). In the case of a release candidate, tick the `pre-release` checkbox.
|
||||
|
||||
70. Update the registry binary in [distribution library image repo](https://github.com/docker/distribution-library-image) by running the update script and opening a pull request.
|
||||
|
||||
80. Update the official image. Add the new version in the [official images repo](https://github.com/docker-library/official-images) by appending a new version to the `registry/registry` file with the git hash pointed to by the signed tag. Update the major version to point to the latest version and the minor version to point to new patch release if necessary.
|
||||
e.g. to release `2.3.1`
|
||||
|
||||
`2.3.1 (new)`
|
||||
|
||||
`2.3.0 -> 2.3.0` can be removed
|
||||
|
||||
`2 -> 2.3.1`
|
||||
|
||||
`2.3 -> 2.3.1`
|
||||
|
||||
90. Build a new distribution/registry image on [Docker hub](https://hub.docker.com/u/distribution/dashboard) by adding a new automated build with the new tag and re-building the images.
|
||||
|
||||
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.
|
||||
|
||||
257
vendor/github.com/docker/distribution/blobs.go
generated
vendored
Normal file
257
vendor/github.com/docker/distribution/blobs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
package distribution
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
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, identified 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
|
||||
}
|
||||
|
||||
// CreateOptions is a collection of blob creation modifiers relevant to general
|
||||
// blob storage intended to be configured by the BlobCreateOption.Apply method.
|
||||
type CreateOptions struct {
|
||||
Mount struct {
|
||||
ShouldMount bool
|
||||
From reference.Canonical
|
||||
// Stat allows to pass precalculated descriptor to link and return.
|
||||
// Blob access check will be skipped if set.
|
||||
Stat *Descriptor
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
94
vendor/github.com/docker/distribution/circle.yml
generated
vendored
Normal file
94
vendor/github.com/docker/distribution/circle.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# 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.8 --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/lk4d4/vndr:
|
||||
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
|
||||
- git fetch origin:
|
||||
pwd: $BASE_STABLE
|
||||
- gvm use stable && if test -n "`git diff --stat=1000 origin/master | grep -E \"^[[:space:]]*vendor\"`"; 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 && 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={}; go test -tags "$DOCKER_BUILDTAGS" -test.short -coverprofile=$GOPATH/src/$PACKAGE/coverage.out -coverpkg=$(./coverpkg.sh $PACKAGE $ROOT_PACKAGE) $PACKAGE':
|
||||
timeout: 1000
|
||||
pwd: $BASE_STABLE
|
||||
|
||||
# Test stable with race
|
||||
- gvm use stable; export ROOT_PACKAGE=$(go list .); go list -tags "$DOCKER_BUILDTAGS" ./... | grep -v "/vendor/" | grep -v "registry/handlers" | grep -v "registry/storage/driver" | xargs -L 1 -I{} bash -c 'export PACKAGE={}; go test -race -tags "$DOCKER_BUILDTAGS" -test.short $PACKAGE':
|
||||
timeout: 1000
|
||||
pwd: $BASE_STABLE
|
||||
post:
|
||||
# Report to codecov
|
||||
- bash <(curl -s https://codecov.io/bash):
|
||||
pwd: $BASE_STABLE
|
||||
|
||||
## Notes
|
||||
# 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
|
||||
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 ' ' ','
|
||||
247
vendor/github.com/docker/distribution/digestset/set.go
generated
vendored
Normal file
247
vendor/github.com/docker/distribution/digestset/set.go
generated
vendored
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
package digestset
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
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 digest.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.Digest, error) {
|
||||
dst.mutex.RLock()
|
||||
defer dst.mutex.RUnlock()
|
||||
if len(dst.entries) == 0 {
|
||||
return "", ErrDigestNotFound
|
||||
}
|
||||
var (
|
||||
searchFunc func(int) bool
|
||||
alg digest.Algorithm
|
||||
hex string
|
||||
)
|
||||
dgst, err := digest.Parse(d)
|
||||
if err == digest.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.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.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.Digest {
|
||||
dst.mutex.RLock()
|
||||
defer dst.mutex.RUnlock()
|
||||
retValues := make([]digest.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.Digest]string {
|
||||
dst.mutex.RLock()
|
||||
defer dst.mutex.RUnlock()
|
||||
m := make(map[digest.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 digest.Algorithm
|
||||
val string
|
||||
digest 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]
|
||||
}
|
||||
371
vendor/github.com/docker/distribution/digestset/set_test.go
generated
vendored
Normal file
371
vendor/github.com/docker/distribution/digestset/set_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
package digestset
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
_ "crypto/sha512"
|
||||
"encoding/binary"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
func assertEqualDigests(t *testing.T, d1, d2 digest.Digest) {
|
||||
if d1 != d2 {
|
||||
t.Fatalf("Digests do not match:\n\tActual: %s\n\tExpected: %s", d1, d2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLookup(t *testing.T) {
|
||||
digests := []digest.Digest{
|
||||
"sha256:1234511111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:1234111111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:1234611111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:5432111111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:6543111111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:6432111111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:6542111111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:6532111111111111111111111111111111111111111111111111111111111111",
|
||||
}
|
||||
|
||||
dset := NewSet()
|
||||
for i := range digests {
|
||||
if err := dset.Add(digests[i]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
dgst, err := dset.Lookup("54")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertEqualDigests(t, dgst, digests[3])
|
||||
|
||||
dgst, err = dset.Lookup("1234")
|
||||
if err == nil {
|
||||
t.Fatal("Expected ambiguous error looking up: 1234")
|
||||
}
|
||||
if err != ErrDigestAmbiguous {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dgst, err = dset.Lookup("9876")
|
||||
if err == nil {
|
||||
t.Fatal("Expected ambiguous error looking up: 9876")
|
||||
}
|
||||
if err != ErrDigestNotFound {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dgst, err = dset.Lookup("sha256:1234")
|
||||
if err == nil {
|
||||
t.Fatal("Expected ambiguous error looking up: sha256:1234")
|
||||
}
|
||||
if err != ErrDigestAmbiguous {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dgst, err = dset.Lookup("sha256:12345")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertEqualDigests(t, dgst, digests[0])
|
||||
|
||||
dgst, err = dset.Lookup("sha256:12346")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertEqualDigests(t, dgst, digests[2])
|
||||
|
||||
dgst, err = dset.Lookup("12346")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertEqualDigests(t, dgst, digests[2])
|
||||
|
||||
dgst, err = dset.Lookup("12345")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertEqualDigests(t, dgst, digests[0])
|
||||
}
|
||||
|
||||
func TestAddDuplication(t *testing.T) {
|
||||
digests := []digest.Digest{
|
||||
"sha256:1234111111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:1234511111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:1234611111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:5432111111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:6543111111111111111111111111111111111111111111111111111111111111",
|
||||
"sha512:65431111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
|
||||
"sha512:65421111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
|
||||
"sha512:65321111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111",
|
||||
}
|
||||
|
||||
dset := NewSet()
|
||||
for i := range digests {
|
||||
if err := dset.Add(digests[i]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(dset.entries) != 8 {
|
||||
t.Fatal("Invalid dset size")
|
||||
}
|
||||
|
||||
if err := dset.Add(digest.Digest("sha256:1234511111111111111111111111111111111111111111111111111111111111")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(dset.entries) != 8 {
|
||||
t.Fatal("Duplicate digest insert allowed")
|
||||
}
|
||||
|
||||
if err := dset.Add(digest.Digest("sha384:123451111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(dset.entries) != 9 {
|
||||
t.Fatal("Insert with different algorithm not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
digests, err := createDigests(10)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dset := NewSet()
|
||||
for i := range digests {
|
||||
if err := dset.Add(digests[i]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
dgst, err := dset.Lookup(digests[0].String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dgst != digests[0] {
|
||||
t.Fatalf("Unexpected digest value:\n\tExpected: %s\n\tActual: %s", digests[0], dgst)
|
||||
}
|
||||
|
||||
if err := dset.Remove(digests[0]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := dset.Lookup(digests[0].String()); err != ErrDigestNotFound {
|
||||
t.Fatalf("Expected error %v when looking up removed digest, got %v", ErrDigestNotFound, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
digests, err := createDigests(100)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dset := NewSet()
|
||||
for i := range digests {
|
||||
if err := dset.Add(digests[i]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
all := map[digest.Digest]struct{}{}
|
||||
for _, dgst := range dset.All() {
|
||||
all[dgst] = struct{}{}
|
||||
}
|
||||
|
||||
if len(all) != len(digests) {
|
||||
t.Fatalf("Unexpected number of unique digests found:\n\tExpected: %d\n\tActual: %d", len(digests), len(all))
|
||||
}
|
||||
|
||||
for i, dgst := range digests {
|
||||
if _, ok := all[dgst]; !ok {
|
||||
t.Fatalf("Missing element at position %d: %s", i, dgst)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func assertEqualShort(t *testing.T, actual, expected string) {
|
||||
if actual != expected {
|
||||
t.Fatalf("Unexpected short value:\n\tExpected: %s\n\tActual: %s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShortCodeTable(t *testing.T) {
|
||||
digests := []digest.Digest{
|
||||
"sha256:1234111111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:1234511111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:1234611111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:5432111111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:6543111111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:6432111111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:6542111111111111111111111111111111111111111111111111111111111111",
|
||||
"sha256:6532111111111111111111111111111111111111111111111111111111111111",
|
||||
}
|
||||
|
||||
dset := NewSet()
|
||||
for i := range digests {
|
||||
if err := dset.Add(digests[i]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
dump := ShortCodeTable(dset, 2)
|
||||
|
||||
if len(dump) < len(digests) {
|
||||
t.Fatalf("Error unexpected size: %d, expecting %d", len(dump), len(digests))
|
||||
}
|
||||
assertEqualShort(t, dump[digests[0]], "12341")
|
||||
assertEqualShort(t, dump[digests[1]], "12345")
|
||||
assertEqualShort(t, dump[digests[2]], "12346")
|
||||
assertEqualShort(t, dump[digests[3]], "54")
|
||||
assertEqualShort(t, dump[digests[4]], "6543")
|
||||
assertEqualShort(t, dump[digests[5]], "64")
|
||||
assertEqualShort(t, dump[digests[6]], "6542")
|
||||
assertEqualShort(t, dump[digests[7]], "653")
|
||||
}
|
||||
|
||||
func createDigests(count int) ([]digest.Digest, error) {
|
||||
r := rand.New(rand.NewSource(25823))
|
||||
digests := make([]digest.Digest, count)
|
||||
for i := range digests {
|
||||
h := sha256.New()
|
||||
if err := binary.Write(h, binary.BigEndian, r.Int63()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
digests[i] = digest.NewDigest("sha256", h)
|
||||
}
|
||||
return digests, nil
|
||||
}
|
||||
|
||||
func benchAddNTable(b *testing.B, n int) {
|
||||
digests, err := createDigests(n)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
dset := &Set{entries: digestEntries(make([]*digestEntry, 0, n))}
|
||||
for j := range digests {
|
||||
if err = dset.Add(digests[j]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchLookupNTable(b *testing.B, n int, shortLen int) {
|
||||
digests, err := createDigests(n)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
dset := &Set{entries: digestEntries(make([]*digestEntry, 0, n))}
|
||||
for i := range digests {
|
||||
if err := dset.Add(digests[i]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
shorts := make([]string, 0, n)
|
||||
for _, short := range ShortCodeTable(dset, shortLen) {
|
||||
shorts = append(shorts, short)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err = dset.Lookup(shorts[i%n]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchRemoveNTable(b *testing.B, n int) {
|
||||
digests, err := createDigests(n)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
dset := &Set{entries: digestEntries(make([]*digestEntry, 0, n))}
|
||||
b.StopTimer()
|
||||
for j := range digests {
|
||||
if err = dset.Add(digests[j]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
b.StartTimer()
|
||||
for j := range digests {
|
||||
if err = dset.Remove(digests[j]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchShortCodeNTable(b *testing.B, n int, shortLen int) {
|
||||
digests, err := createDigests(n)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
dset := &Set{entries: digestEntries(make([]*digestEntry, 0, n))}
|
||||
for i := range digests {
|
||||
if err := dset.Add(digests[i]); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ShortCodeTable(dset, shortLen)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAdd10(b *testing.B) {
|
||||
benchAddNTable(b, 10)
|
||||
}
|
||||
|
||||
func BenchmarkAdd100(b *testing.B) {
|
||||
benchAddNTable(b, 100)
|
||||
}
|
||||
|
||||
func BenchmarkAdd1000(b *testing.B) {
|
||||
benchAddNTable(b, 1000)
|
||||
}
|
||||
|
||||
func BenchmarkRemove10(b *testing.B) {
|
||||
benchRemoveNTable(b, 10)
|
||||
}
|
||||
|
||||
func BenchmarkRemove100(b *testing.B) {
|
||||
benchRemoveNTable(b, 100)
|
||||
}
|
||||
|
||||
func BenchmarkRemove1000(b *testing.B) {
|
||||
benchRemoveNTable(b, 1000)
|
||||
}
|
||||
|
||||
func BenchmarkLookup10(b *testing.B) {
|
||||
benchLookupNTable(b, 10, 12)
|
||||
}
|
||||
|
||||
func BenchmarkLookup100(b *testing.B) {
|
||||
benchLookupNTable(b, 100, 12)
|
||||
}
|
||||
|
||||
func BenchmarkLookup1000(b *testing.B) {
|
||||
benchLookupNTable(b, 1000, 12)
|
||||
}
|
||||
|
||||
func BenchmarkShortCode10(b *testing.B) {
|
||||
benchShortCodeNTable(b, 10, 12)
|
||||
}
|
||||
func BenchmarkShortCode100(b *testing.B) {
|
||||
benchShortCodeNTable(b, 100, 12)
|
||||
}
|
||||
func BenchmarkShortCode1000(b *testing.B) {
|
||||
benchShortCodeNTable(b, 1000, 12)
|
||||
}
|
||||
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/opencontainers/go-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 "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)
|
||||
}
|
||||
125
vendor/github.com/docker/distribution/manifests.go
generated
vendored
Normal file
125
vendor/github.com/docker/distribution/manifests.go
generated
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
package distribution
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/opencontainers/go-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.
|
||||
// A reference is anything which can be represented by a
|
||||
// distribution.Descriptor. These can consist of layers, resources or other
|
||||
// manifests.
|
||||
//
|
||||
// While no particular order is required, implementations should return
|
||||
// them from highest to lowest priority. For example, one might want to
|
||||
// return the base layer before the top layer.
|
||||
References() []Descriptor
|
||||
|
||||
// Payload provides the serialized format of the manifest, in addition to
|
||||
// the media type.
|
||||
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.
|
||||
//
|
||||
// The destination of the reference is dependent on the manifest type and
|
||||
// the dependency type.
|
||||
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 media type 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 media type registration would overwrite existing: %s", mediaType)
|
||||
}
|
||||
mappings[mediaType] = u
|
||||
return nil
|
||||
}
|
||||
42
vendor/github.com/docker/distribution/reference/helpers.go
generated
vendored
Normal file
42
vendor/github.com/docker/distribution/reference/helpers.go
generated
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package reference
|
||||
|
||||
import "path"
|
||||
|
||||
// IsNameOnly returns true if reference only contains a repo name.
|
||||
func IsNameOnly(ref Named) bool {
|
||||
if _, ok := ref.(NamedTagged); ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := ref.(Canonical); ok {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// FamiliarName returns the familiar name string
|
||||
// for the given named, familiarizing if needed.
|
||||
func FamiliarName(ref Named) string {
|
||||
if nn, ok := ref.(normalizedNamed); ok {
|
||||
return nn.Familiar().Name()
|
||||
}
|
||||
return ref.Name()
|
||||
}
|
||||
|
||||
// FamiliarString returns the familiar string representation
|
||||
// for the given reference, familiarizing if needed.
|
||||
func FamiliarString(ref Reference) string {
|
||||
if nn, ok := ref.(normalizedNamed); ok {
|
||||
return nn.Familiar().String()
|
||||
}
|
||||
return ref.String()
|
||||
}
|
||||
|
||||
// FamiliarMatch reports whether ref matches the specified pattern.
|
||||
// See https://godoc.org/path#Match for supported patterns.
|
||||
func FamiliarMatch(pattern string, ref Reference) (bool, error) {
|
||||
matched, err := path.Match(pattern, FamiliarString(ref))
|
||||
if namedRef, isNamed := ref.(Named); isNamed && !matched {
|
||||
matched, _ = path.Match(pattern, FamiliarName(namedRef))
|
||||
}
|
||||
return matched, err
|
||||
}
|
||||
170
vendor/github.com/docker/distribution/reference/normalize.go
generated
vendored
Normal file
170
vendor/github.com/docker/distribution/reference/normalize.go
generated
vendored
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
package reference
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/distribution/digestset"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
var (
|
||||
legacyDefaultDomain = "index.docker.io"
|
||||
defaultDomain = "docker.io"
|
||||
officialRepoName = "library"
|
||||
defaultTag = "latest"
|
||||
)
|
||||
|
||||
// normalizedNamed represents a name which has been
|
||||
// normalized and has a familiar form. A familiar name
|
||||
// is what is used in Docker UI. An example normalized
|
||||
// name is "docker.io/library/ubuntu" and corresponding
|
||||
// familiar name of "ubuntu".
|
||||
type normalizedNamed interface {
|
||||
Named
|
||||
Familiar() Named
|
||||
}
|
||||
|
||||
// ParseNormalizedNamed parses a string into a named reference
|
||||
// transforming a familiar name from Docker UI to a fully
|
||||
// qualified reference. If the value may be an identifier
|
||||
// use ParseAnyReference.
|
||||
func ParseNormalizedNamed(s string) (Named, error) {
|
||||
if ok := anchoredIdentifierRegexp.MatchString(s); ok {
|
||||
return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s)
|
||||
}
|
||||
domain, remainder := splitDockerDomain(s)
|
||||
var remoteName string
|
||||
if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 {
|
||||
remoteName = remainder[:tagSep]
|
||||
} else {
|
||||
remoteName = remainder
|
||||
}
|
||||
if strings.ToLower(remoteName) != remoteName {
|
||||
return nil, errors.New("invalid reference format: repository name must be lowercase")
|
||||
}
|
||||
|
||||
ref, err := Parse(domain + "/" + remainder)
|
||||
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
|
||||
}
|
||||
|
||||
// splitDockerDomain splits a repository name to domain and remotename string.
|
||||
// If no valid domain is found, the default domain is used. Repository name
|
||||
// needs to be already validated before.
|
||||
func splitDockerDomain(name string) (domain, remainder string) {
|
||||
i := strings.IndexRune(name, '/')
|
||||
if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") {
|
||||
domain, remainder = defaultDomain, name
|
||||
} else {
|
||||
domain, remainder = name[:i], name[i+1:]
|
||||
}
|
||||
if domain == legacyDefaultDomain {
|
||||
domain = defaultDomain
|
||||
}
|
||||
if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
|
||||
remainder = officialRepoName + "/" + remainder
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// familiarizeName returns a shortened version of the name familiar
|
||||
// to to the Docker UI. Familiar names have the default domain
|
||||
// "docker.io" and "library/" repository prefix removed.
|
||||
// For example, "docker.io/library/redis" will have the familiar
|
||||
// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp".
|
||||
// Returns a familiarized named only reference.
|
||||
func familiarizeName(named namedRepository) repository {
|
||||
repo := repository{
|
||||
domain: named.Domain(),
|
||||
path: named.Path(),
|
||||
}
|
||||
|
||||
if repo.domain == defaultDomain {
|
||||
repo.domain = ""
|
||||
// Handle official repositories which have the pattern "library/<official repo name>"
|
||||
if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName {
|
||||
repo.path = split[1]
|
||||
}
|
||||
}
|
||||
return repo
|
||||
}
|
||||
|
||||
func (r reference) Familiar() Named {
|
||||
return reference{
|
||||
namedRepository: familiarizeName(r.namedRepository),
|
||||
tag: r.tag,
|
||||
digest: r.digest,
|
||||
}
|
||||
}
|
||||
|
||||
func (r repository) Familiar() Named {
|
||||
return familiarizeName(r)
|
||||
}
|
||||
|
||||
func (t taggedReference) Familiar() Named {
|
||||
return taggedReference{
|
||||
namedRepository: familiarizeName(t.namedRepository),
|
||||
tag: t.tag,
|
||||
}
|
||||
}
|
||||
|
||||
func (c canonicalReference) Familiar() Named {
|
||||
return canonicalReference{
|
||||
namedRepository: familiarizeName(c.namedRepository),
|
||||
digest: c.digest,
|
||||
}
|
||||
}
|
||||
|
||||
// TagNameOnly adds the default tag "latest" to a reference if it only has
|
||||
// a repo name.
|
||||
func TagNameOnly(ref Named) Named {
|
||||
if IsNameOnly(ref) {
|
||||
namedTagged, err := WithTag(ref, defaultTag)
|
||||
if err != nil {
|
||||
// Default tag must be valid, to create a NamedTagged
|
||||
// type with non-validated input the WithTag function
|
||||
// should be used instead
|
||||
panic(err)
|
||||
}
|
||||
return namedTagged
|
||||
}
|
||||
return ref
|
||||
}
|
||||
|
||||
// ParseAnyReference parses a reference string as a possible identifier,
|
||||
// full digest, or familiar name.
|
||||
func ParseAnyReference(ref string) (Reference, error) {
|
||||
if ok := anchoredIdentifierRegexp.MatchString(ref); ok {
|
||||
return digestReference("sha256:" + ref), nil
|
||||
}
|
||||
if dgst, err := digest.Parse(ref); err == nil {
|
||||
return digestReference(dgst), nil
|
||||
}
|
||||
|
||||
return ParseNormalizedNamed(ref)
|
||||
}
|
||||
|
||||
// ParseAnyReferenceWithSet parses a reference string as a possible short
|
||||
// identifier to be matched in a digest set, a full digest, or familiar name.
|
||||
func ParseAnyReferenceWithSet(ref string, ds *digestset.Set) (Reference, error) {
|
||||
if ok := anchoredShortIdentifierRegexp.MatchString(ref); ok {
|
||||
dgst, err := ds.Lookup(ref)
|
||||
if err == nil {
|
||||
return digestReference(dgst), nil
|
||||
}
|
||||
} else {
|
||||
if dgst, err := digest.Parse(ref); err == nil {
|
||||
return digestReference(dgst), nil
|
||||
}
|
||||
}
|
||||
|
||||
return ParseNormalizedNamed(ref)
|
||||
}
|
||||
625
vendor/github.com/docker/distribution/reference/normalize_test.go
generated
vendored
Normal file
625
vendor/github.com/docker/distribution/reference/normalize_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,625 @@
|
|||
package reference
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/distribution/digestset"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
func TestValidateReferenceName(t *testing.T) {
|
||||
validRepoNames := []string{
|
||||
"docker/docker",
|
||||
"library/debian",
|
||||
"debian",
|
||||
"docker.io/docker/docker",
|
||||
"docker.io/library/debian",
|
||||
"docker.io/debian",
|
||||
"index.docker.io/docker/docker",
|
||||
"index.docker.io/library/debian",
|
||||
"index.docker.io/debian",
|
||||
"127.0.0.1:5000/docker/docker",
|
||||
"127.0.0.1:5000/library/debian",
|
||||
"127.0.0.1:5000/debian",
|
||||
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
|
||||
|
||||
// This test case was moved from invalid to valid since it is valid input
|
||||
// when specified with a hostname, it removes the ambiguity from about
|
||||
// whether the value is an identifier or repository name
|
||||
"docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
|
||||
}
|
||||
invalidRepoNames := []string{
|
||||
"https://github.com/docker/docker",
|
||||
"docker/Docker",
|
||||
"-docker",
|
||||
"-docker/docker",
|
||||
"-docker.io/docker/docker",
|
||||
"docker///docker",
|
||||
"docker.io/docker/Docker",
|
||||
"docker.io/docker///docker",
|
||||
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
|
||||
}
|
||||
|
||||
for _, name := range invalidRepoNames {
|
||||
_, err := ParseNormalizedNamed(name)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected invalid repo name for %q", name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range validRepoNames {
|
||||
_, err := ParseNormalizedNamed(name)
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing repo name %s, got: %q", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRemoteName(t *testing.T) {
|
||||
validRepositoryNames := []string{
|
||||
// Sanity check.
|
||||
"docker/docker",
|
||||
|
||||
// Allow 64-character non-hexadecimal names (hexadecimal names are forbidden).
|
||||
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
|
||||
|
||||
// Allow embedded hyphens.
|
||||
"docker-rules/docker",
|
||||
|
||||
// Allow multiple hyphens as well.
|
||||
"docker---rules/docker",
|
||||
|
||||
//Username doc and image name docker being tested.
|
||||
"doc/docker",
|
||||
|
||||
// single character names are now allowed.
|
||||
"d/docker",
|
||||
"jess/t",
|
||||
|
||||
// Consecutive underscores.
|
||||
"dock__er/docker",
|
||||
}
|
||||
for _, repositoryName := range validRepositoryNames {
|
||||
_, err := ParseNormalizedNamed(repositoryName)
|
||||
if err != nil {
|
||||
t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err)
|
||||
}
|
||||
}
|
||||
|
||||
invalidRepositoryNames := []string{
|
||||
// Disallow capital letters.
|
||||
"docker/Docker",
|
||||
|
||||
// Only allow one slash.
|
||||
"docker///docker",
|
||||
|
||||
// Disallow 64-character hexadecimal.
|
||||
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
|
||||
|
||||
// Disallow leading and trailing hyphens in namespace.
|
||||
"-docker/docker",
|
||||
"docker-/docker",
|
||||
"-docker-/docker",
|
||||
|
||||
// Don't allow underscores everywhere (as opposed to hyphens).
|
||||
"____/____",
|
||||
|
||||
"_docker/_docker",
|
||||
|
||||
// Disallow consecutive periods.
|
||||
"dock..er/docker",
|
||||
"dock_.er/docker",
|
||||
"dock-.er/docker",
|
||||
|
||||
// No repository.
|
||||
"docker/",
|
||||
|
||||
//namespace too long
|
||||
"this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255/docker",
|
||||
}
|
||||
for _, repositoryName := range invalidRepositoryNames {
|
||||
if _, err := ParseNormalizedNamed(repositoryName); err == nil {
|
||||
t.Errorf("Repository name should be invalid: %v", repositoryName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRepositoryInfo(t *testing.T) {
|
||||
type tcase struct {
|
||||
RemoteName, FamiliarName, FullName, AmbiguousName, Domain string
|
||||
}
|
||||
|
||||
tcases := []tcase{
|
||||
{
|
||||
RemoteName: "fooo/bar",
|
||||
FamiliarName: "fooo/bar",
|
||||
FullName: "docker.io/fooo/bar",
|
||||
AmbiguousName: "index.docker.io/fooo/bar",
|
||||
Domain: "docker.io",
|
||||
},
|
||||
{
|
||||
RemoteName: "library/ubuntu",
|
||||
FamiliarName: "ubuntu",
|
||||
FullName: "docker.io/library/ubuntu",
|
||||
AmbiguousName: "library/ubuntu",
|
||||
Domain: "docker.io",
|
||||
},
|
||||
{
|
||||
RemoteName: "nonlibrary/ubuntu",
|
||||
FamiliarName: "nonlibrary/ubuntu",
|
||||
FullName: "docker.io/nonlibrary/ubuntu",
|
||||
AmbiguousName: "",
|
||||
Domain: "docker.io",
|
||||
},
|
||||
{
|
||||
RemoteName: "other/library",
|
||||
FamiliarName: "other/library",
|
||||
FullName: "docker.io/other/library",
|
||||
AmbiguousName: "",
|
||||
Domain: "docker.io",
|
||||
},
|
||||
{
|
||||
RemoteName: "private/moonbase",
|
||||
FamiliarName: "127.0.0.1:8000/private/moonbase",
|
||||
FullName: "127.0.0.1:8000/private/moonbase",
|
||||
AmbiguousName: "",
|
||||
Domain: "127.0.0.1:8000",
|
||||
},
|
||||
{
|
||||
RemoteName: "privatebase",
|
||||
FamiliarName: "127.0.0.1:8000/privatebase",
|
||||
FullName: "127.0.0.1:8000/privatebase",
|
||||
AmbiguousName: "",
|
||||
Domain: "127.0.0.1:8000",
|
||||
},
|
||||
{
|
||||
RemoteName: "private/moonbase",
|
||||
FamiliarName: "example.com/private/moonbase",
|
||||
FullName: "example.com/private/moonbase",
|
||||
AmbiguousName: "",
|
||||
Domain: "example.com",
|
||||
},
|
||||
{
|
||||
RemoteName: "privatebase",
|
||||
FamiliarName: "example.com/privatebase",
|
||||
FullName: "example.com/privatebase",
|
||||
AmbiguousName: "",
|
||||
Domain: "example.com",
|
||||
},
|
||||
{
|
||||
RemoteName: "private/moonbase",
|
||||
FamiliarName: "example.com:8000/private/moonbase",
|
||||
FullName: "example.com:8000/private/moonbase",
|
||||
AmbiguousName: "",
|
||||
Domain: "example.com:8000",
|
||||
},
|
||||
{
|
||||
RemoteName: "privatebasee",
|
||||
FamiliarName: "example.com:8000/privatebasee",
|
||||
FullName: "example.com:8000/privatebasee",
|
||||
AmbiguousName: "",
|
||||
Domain: "example.com:8000",
|
||||
},
|
||||
{
|
||||
RemoteName: "library/ubuntu-12.04-base",
|
||||
FamiliarName: "ubuntu-12.04-base",
|
||||
FullName: "docker.io/library/ubuntu-12.04-base",
|
||||
AmbiguousName: "index.docker.io/library/ubuntu-12.04-base",
|
||||
Domain: "docker.io",
|
||||
},
|
||||
{
|
||||
RemoteName: "library/foo",
|
||||
FamiliarName: "foo",
|
||||
FullName: "docker.io/library/foo",
|
||||
AmbiguousName: "docker.io/foo",
|
||||
Domain: "docker.io",
|
||||
},
|
||||
{
|
||||
RemoteName: "library/foo/bar",
|
||||
FamiliarName: "library/foo/bar",
|
||||
FullName: "docker.io/library/foo/bar",
|
||||
AmbiguousName: "",
|
||||
Domain: "docker.io",
|
||||
},
|
||||
{
|
||||
RemoteName: "store/foo/bar",
|
||||
FamiliarName: "store/foo/bar",
|
||||
FullName: "docker.io/store/foo/bar",
|
||||
AmbiguousName: "",
|
||||
Domain: "docker.io",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tcase := range tcases {
|
||||
refStrings := []string{tcase.FamiliarName, tcase.FullName}
|
||||
if tcase.AmbiguousName != "" {
|
||||
refStrings = append(refStrings, tcase.AmbiguousName)
|
||||
}
|
||||
|
||||
var refs []Named
|
||||
for _, r := range refStrings {
|
||||
named, err := ParseNormalizedNamed(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
refs = append(refs, named)
|
||||
}
|
||||
|
||||
for _, r := range refs {
|
||||
if expected, actual := tcase.FamiliarName, FamiliarName(r); expected != actual {
|
||||
t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual)
|
||||
}
|
||||
if expected, actual := tcase.FullName, r.String(); expected != actual {
|
||||
t.Fatalf("Invalid canonical reference for %q. Expected %q, got %q", r, expected, actual)
|
||||
}
|
||||
if expected, actual := tcase.Domain, Domain(r); expected != actual {
|
||||
t.Fatalf("Invalid domain for %q. Expected %q, got %q", r, expected, actual)
|
||||
}
|
||||
if expected, actual := tcase.RemoteName, Path(r); expected != actual {
|
||||
t.Fatalf("Invalid remoteName for %q. Expected %q, got %q", r, expected, actual)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseReferenceWithTagAndDigest(t *testing.T) {
|
||||
shortRef := "busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa"
|
||||
ref, err := ParseNormalizedNamed(shortRef)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if expected, actual := "docker.io/library/"+shortRef, ref.String(); actual != expected {
|
||||
t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual)
|
||||
}
|
||||
|
||||
if _, isTagged := ref.(NamedTagged); !isTagged {
|
||||
t.Fatalf("Reference from %q should support tag", ref)
|
||||
}
|
||||
if _, isCanonical := ref.(Canonical); !isCanonical {
|
||||
t.Fatalf("Reference from %q should support digest", ref)
|
||||
}
|
||||
if expected, actual := shortRef, FamiliarString(ref); actual != expected {
|
||||
t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidReferenceComponents(t *testing.T) {
|
||||
if _, err := ParseNormalizedNamed("-foo"); err == nil {
|
||||
t.Fatal("Expected WithName to detect invalid name")
|
||||
}
|
||||
ref, err := ParseNormalizedNamed("busybox")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := WithTag(ref, "-foo"); err == nil {
|
||||
t.Fatal("Expected WithName to detect invalid tag")
|
||||
}
|
||||
if _, err := WithDigest(ref, digest.Digest("foo")); err == nil {
|
||||
t.Fatal("Expected WithDigest to detect invalid digest")
|
||||
}
|
||||
}
|
||||
|
||||
func equalReference(r1, r2 Reference) bool {
|
||||
switch v1 := r1.(type) {
|
||||
case digestReference:
|
||||
if v2, ok := r2.(digestReference); ok {
|
||||
return v1 == v2
|
||||
}
|
||||
case repository:
|
||||
if v2, ok := r2.(repository); ok {
|
||||
return v1 == v2
|
||||
}
|
||||
case taggedReference:
|
||||
if v2, ok := r2.(taggedReference); ok {
|
||||
return v1 == v2
|
||||
}
|
||||
case canonicalReference:
|
||||
if v2, ok := r2.(canonicalReference); ok {
|
||||
return v1 == v2
|
||||
}
|
||||
case reference:
|
||||
if v2, ok := r2.(reference); ok {
|
||||
return v1 == v2
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestParseAnyReference(t *testing.T) {
|
||||
tcases := []struct {
|
||||
Reference string
|
||||
Equivalent string
|
||||
Expected Reference
|
||||
Digests []digest.Digest
|
||||
}{
|
||||
{
|
||||
Reference: "redis",
|
||||
Equivalent: "docker.io/library/redis",
|
||||
},
|
||||
{
|
||||
Reference: "redis:latest",
|
||||
Equivalent: "docker.io/library/redis:latest",
|
||||
},
|
||||
{
|
||||
Reference: "docker.io/library/redis:latest",
|
||||
Equivalent: "docker.io/library/redis:latest",
|
||||
},
|
||||
{
|
||||
Reference: "redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
Equivalent: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
},
|
||||
{
|
||||
Reference: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
Equivalent: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
},
|
||||
{
|
||||
Reference: "dmcgowan/myapp",
|
||||
Equivalent: "docker.io/dmcgowan/myapp",
|
||||
},
|
||||
{
|
||||
Reference: "dmcgowan/myapp:latest",
|
||||
Equivalent: "docker.io/dmcgowan/myapp:latest",
|
||||
},
|
||||
{
|
||||
Reference: "docker.io/mcgowan/myapp:latest",
|
||||
Equivalent: "docker.io/mcgowan/myapp:latest",
|
||||
},
|
||||
{
|
||||
Reference: "dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
Equivalent: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
},
|
||||
{
|
||||
Reference: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
Equivalent: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
},
|
||||
{
|
||||
Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
||||
Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
},
|
||||
{
|
||||
Reference: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
||||
Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
},
|
||||
{
|
||||
Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
|
||||
Equivalent: "docker.io/library/dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
|
||||
},
|
||||
{
|
||||
Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
|
||||
Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
||||
Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
Digests: []digest.Digest{
|
||||
digest.Digest("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
||||
digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
|
||||
Equivalent: "docker.io/library/dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9",
|
||||
Digests: []digest.Digest{
|
||||
digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Reference: "dbcc1c",
|
||||
Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
||||
Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c",
|
||||
Digests: []digest.Digest{
|
||||
digest.Digest("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
||||
digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Reference: "dbcc1",
|
||||
Equivalent: "docker.io/library/dbcc1",
|
||||
Digests: []digest.Digest{
|
||||
digest.Digest("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
||||
digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Reference: "dbcc1c",
|
||||
Equivalent: "docker.io/library/dbcc1c",
|
||||
Digests: []digest.Digest{
|
||||
digest.Digest("sha256:abcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tcase := range tcases {
|
||||
var ref Reference
|
||||
var err error
|
||||
if len(tcase.Digests) == 0 {
|
||||
ref, err = ParseAnyReference(tcase.Reference)
|
||||
} else {
|
||||
ds := digestset.NewSet()
|
||||
for _, dgst := range tcase.Digests {
|
||||
if err := ds.Add(dgst); err != nil {
|
||||
t.Fatalf("Error adding digest %s: %v", dgst.String(), err)
|
||||
}
|
||||
}
|
||||
ref, err = ParseAnyReferenceWithSet(tcase.Reference, ds)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing reference %s: %v", tcase.Reference, err)
|
||||
}
|
||||
if ref.String() != tcase.Equivalent {
|
||||
t.Fatalf("Unexpected string: %s, expected %s", ref.String(), tcase.Equivalent)
|
||||
}
|
||||
|
||||
expected := tcase.Expected
|
||||
if expected == nil {
|
||||
expected, err = Parse(tcase.Equivalent)
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing reference %s: %v", tcase.Equivalent, err)
|
||||
}
|
||||
}
|
||||
if !equalReference(ref, expected) {
|
||||
t.Errorf("Unexpected reference %#v, expected %#v", ref, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizedSplitHostname(t *testing.T) {
|
||||
testcases := []struct {
|
||||
input string
|
||||
domain string
|
||||
name string
|
||||
}{
|
||||
{
|
||||
input: "test.com/foo",
|
||||
domain: "test.com",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "test_com/foo",
|
||||
domain: "docker.io",
|
||||
name: "test_com/foo",
|
||||
},
|
||||
{
|
||||
input: "docker/migrator",
|
||||
domain: "docker.io",
|
||||
name: "docker/migrator",
|
||||
},
|
||||
{
|
||||
input: "test.com:8080/foo",
|
||||
domain: "test.com:8080",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "test-com:8080/foo",
|
||||
domain: "test-com:8080",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "foo",
|
||||
domain: "docker.io",
|
||||
name: "library/foo",
|
||||
},
|
||||
{
|
||||
input: "xn--n3h.com/foo",
|
||||
domain: "xn--n3h.com",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "xn--n3h.com:18080/foo",
|
||||
domain: "xn--n3h.com:18080",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "docker.io/foo",
|
||||
domain: "docker.io",
|
||||
name: "library/foo",
|
||||
},
|
||||
{
|
||||
input: "docker.io/library/foo",
|
||||
domain: "docker.io",
|
||||
name: "library/foo",
|
||||
},
|
||||
{
|
||||
input: "docker.io/library/foo/bar",
|
||||
domain: "docker.io",
|
||||
name: "library/foo/bar",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
failf := func(format string, v ...interface{}) {
|
||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
named, err := ParseNormalizedNamed(testcase.input)
|
||||
if err != nil {
|
||||
failf("error parsing name: %s", err)
|
||||
}
|
||||
domain, name := SplitHostname(named)
|
||||
if domain != testcase.domain {
|
||||
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
|
||||
}
|
||||
if name != testcase.name {
|
||||
failf("unexpected name: got %q, expected %q", name, testcase.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchError(t *testing.T) {
|
||||
named, err := ParseAnyReference("foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = FamiliarMatch("[-x]", named)
|
||||
if err == nil {
|
||||
t.Fatalf("expected an error, got nothing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
matchCases := []struct {
|
||||
reference string
|
||||
pattern string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
reference: "foo",
|
||||
pattern: "foo/**/ba[rz]",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
reference: "foo/any/bat",
|
||||
pattern: "foo/**/ba[rz]",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
reference: "foo/a/bar",
|
||||
pattern: "foo/**/ba[rz]",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
reference: "foo/b/baz",
|
||||
pattern: "foo/**/ba[rz]",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
reference: "foo/c/baz:tag",
|
||||
pattern: "foo/**/ba[rz]",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
reference: "foo/c/baz:tag",
|
||||
pattern: "foo/*/baz:tag",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
reference: "foo/c/baz:tag",
|
||||
pattern: "foo/c/baz:tag",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
reference: "example.com/foo/c/baz:tag",
|
||||
pattern: "*/foo/c/baz",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
reference: "example.com/foo/c/baz:tag",
|
||||
pattern: "example.com/foo/c/baz",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, c := range matchCases {
|
||||
named, err := ParseAnyReference(c.reference)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual, err := FamiliarMatch(c.pattern, named)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if actual != c.expected {
|
||||
t.Fatalf("expected %s match %s to be %v, was %v", c.reference, c.pattern, c.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
433
vendor/github.com/docker/distribution/reference/reference.go
generated
vendored
Normal file
433
vendor/github.com/docker/distribution/reference/reference.go
generated
vendored
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
// 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 := [domain '/'] path-component ['/' path-component]*
|
||||
// domain := domain-component ['.' domain-component]* [':' port-number]
|
||||
// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
|
||||
// port-number := /[0-9]+/
|
||||
// path-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
|
||||
//
|
||||
// identifier := /[a-f0-9]{64}/
|
||||
// short-identifier := /[a-f0-9]{6,64}/
|
||||
package reference
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/opencontainers/go-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")
|
||||
|
||||
// ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters.
|
||||
ErrNameContainsUppercase = errors.New("repository name must be lowercase")
|
||||
|
||||
// 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)
|
||||
|
||||
// ErrNameNotCanonical is returned when a name is not canonical.
|
||||
ErrNameNotCanonical = errors.New("repository name must be canonical")
|
||||
)
|
||||
|
||||
// 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 domain and digest
|
||||
type Canonical interface {
|
||||
Named
|
||||
Digest() digest.Digest
|
||||
}
|
||||
|
||||
// namedRepository is a reference to a repository with a name.
|
||||
// A namedRepository has both domain and path components.
|
||||
type namedRepository interface {
|
||||
Named
|
||||
Domain() string
|
||||
Path() string
|
||||
}
|
||||
|
||||
// Domain returns the domain part of the Named reference
|
||||
func Domain(named Named) string {
|
||||
if r, ok := named.(namedRepository); ok {
|
||||
return r.Domain()
|
||||
}
|
||||
domain, _ := splitDomain(named.Name())
|
||||
return domain
|
||||
}
|
||||
|
||||
// Path returns the name without the domain part of the Named reference
|
||||
func Path(named Named) (name string) {
|
||||
if r, ok := named.(namedRepository); ok {
|
||||
return r.Path()
|
||||
}
|
||||
_, path := splitDomain(named.Name())
|
||||
return path
|
||||
}
|
||||
|
||||
func splitDomain(name string) (string, string) {
|
||||
match := anchoredNameRegexp.FindStringSubmatch(name)
|
||||
if len(match) != 3 {
|
||||
return "", name
|
||||
}
|
||||
return match[1], match[2]
|
||||
}
|
||||
|
||||
// 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
|
||||
// DEPRECATED: Use Domain or Path
|
||||
func SplitHostname(named Named) (string, string) {
|
||||
if r, ok := named.(namedRepository); ok {
|
||||
return r.Domain(), r.Path()
|
||||
}
|
||||
return splitDomain(named.Name())
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil {
|
||||
return nil, ErrNameContainsUppercase
|
||||
}
|
||||
return nil, ErrReferenceInvalidFormat
|
||||
}
|
||||
|
||||
if len(matches[1]) > NameTotalLengthMax {
|
||||
return nil, ErrNameTooLong
|
||||
}
|
||||
|
||||
var repo repository
|
||||
|
||||
nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1])
|
||||
if nameMatch != nil && len(nameMatch) == 3 {
|
||||
repo.domain = nameMatch[1]
|
||||
repo.path = nameMatch[2]
|
||||
} else {
|
||||
repo.domain = ""
|
||||
repo.path = matches[1]
|
||||
}
|
||||
|
||||
ref := reference{
|
||||
namedRepository: repo,
|
||||
tag: matches[2],
|
||||
}
|
||||
if matches[3] != "" {
|
||||
var err error
|
||||
ref.digest, err = digest.Parse(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 and be in the canonical
|
||||
// form, 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) {
|
||||
named, err := ParseNormalizedNamed(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if named.String() != s {
|
||||
return nil, ErrNameNotCanonical
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
match := anchoredNameRegexp.FindStringSubmatch(name)
|
||||
if match == nil || len(match) != 3 {
|
||||
return nil, ErrReferenceInvalidFormat
|
||||
}
|
||||
return repository{
|
||||
domain: match[1],
|
||||
path: match[2],
|
||||
}, 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
|
||||
}
|
||||
var repo repository
|
||||
if r, ok := name.(namedRepository); ok {
|
||||
repo.domain = r.Domain()
|
||||
repo.path = r.Path()
|
||||
} else {
|
||||
repo.path = name.Name()
|
||||
}
|
||||
if canonical, ok := name.(Canonical); ok {
|
||||
return reference{
|
||||
namedRepository: repo,
|
||||
tag: tag,
|
||||
digest: canonical.Digest(),
|
||||
}, nil
|
||||
}
|
||||
return taggedReference{
|
||||
namedRepository: repo,
|
||||
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
|
||||
}
|
||||
var repo repository
|
||||
if r, ok := name.(namedRepository); ok {
|
||||
repo.domain = r.Domain()
|
||||
repo.path = r.Path()
|
||||
} else {
|
||||
repo.path = name.Name()
|
||||
}
|
||||
if tagged, ok := name.(Tagged); ok {
|
||||
return reference{
|
||||
namedRepository: repo,
|
||||
tag: tagged.Tag(),
|
||||
digest: digest,
|
||||
}, nil
|
||||
}
|
||||
return canonicalReference{
|
||||
namedRepository: repo,
|
||||
digest: digest,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TrimNamed removes any tag or digest from the named reference.
|
||||
func TrimNamed(ref Named) Named {
|
||||
domain, path := SplitHostname(ref)
|
||||
return repository{
|
||||
domain: domain,
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
namedRepository: ref.namedRepository,
|
||||
digest: ref.digest,
|
||||
}
|
||||
}
|
||||
return ref.namedRepository
|
||||
}
|
||||
if ref.digest == "" {
|
||||
return taggedReference{
|
||||
namedRepository: ref.namedRepository,
|
||||
tag: ref.tag,
|
||||
}
|
||||
}
|
||||
|
||||
return ref
|
||||
}
|
||||
|
||||
type reference struct {
|
||||
namedRepository
|
||||
tag string
|
||||
digest digest.Digest
|
||||
}
|
||||
|
||||
func (r reference) String() string {
|
||||
return r.Name() + ":" + r.tag + "@" + r.digest.String()
|
||||
}
|
||||
|
||||
func (r reference) Tag() string {
|
||||
return r.tag
|
||||
}
|
||||
|
||||
func (r reference) Digest() digest.Digest {
|
||||
return r.digest
|
||||
}
|
||||
|
||||
type repository struct {
|
||||
domain string
|
||||
path string
|
||||
}
|
||||
|
||||
func (r repository) String() string {
|
||||
return r.Name()
|
||||
}
|
||||
|
||||
func (r repository) Name() string {
|
||||
if r.domain == "" {
|
||||
return r.path
|
||||
}
|
||||
return r.domain + "/" + r.path
|
||||
}
|
||||
|
||||
func (r repository) Domain() string {
|
||||
return r.domain
|
||||
}
|
||||
|
||||
func (r repository) Path() string {
|
||||
return r.path
|
||||
}
|
||||
|
||||
type digestReference digest.Digest
|
||||
|
||||
func (d digestReference) String() string {
|
||||
return digest.Digest(d).String()
|
||||
}
|
||||
|
||||
func (d digestReference) Digest() digest.Digest {
|
||||
return digest.Digest(d)
|
||||
}
|
||||
|
||||
type taggedReference struct {
|
||||
namedRepository
|
||||
tag string
|
||||
}
|
||||
|
||||
func (t taggedReference) String() string {
|
||||
return t.Name() + ":" + t.tag
|
||||
}
|
||||
|
||||
func (t taggedReference) Tag() string {
|
||||
return t.tag
|
||||
}
|
||||
|
||||
type canonicalReference struct {
|
||||
namedRepository
|
||||
digest digest.Digest
|
||||
}
|
||||
|
||||
func (c canonicalReference) String() string {
|
||||
return c.Name() + "@" + c.digest.String()
|
||||
}
|
||||
|
||||
func (c canonicalReference) Digest() digest.Digest {
|
||||
return c.digest
|
||||
}
|
||||
659
vendor/github.com/docker/distribution/reference/reference_test.go
generated
vendored
Normal file
659
vendor/github.com/docker/distribution/reference/reference_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,659 @@
|
|||
package reference
|
||||
|
||||
import (
|
||||
_ "crypto/sha256"
|
||||
_ "crypto/sha512"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
func TestReferenceParse(t *testing.T) {
|
||||
// referenceTestcases is a unified set of testcases for
|
||||
// testing the parsing of references
|
||||
referenceTestcases := []struct {
|
||||
// input is the repository name or name component testcase
|
||||
input string
|
||||
// err is the error expected from Parse, or nil
|
||||
err error
|
||||
// repository is the string representation for the reference
|
||||
repository string
|
||||
// domain is the domain expected in the reference
|
||||
domain string
|
||||
// tag is the tag for the reference
|
||||
tag string
|
||||
// digest is the digest for the reference (enforces digest reference)
|
||||
digest string
|
||||
}{
|
||||
{
|
||||
input: "test_com",
|
||||
repository: "test_com",
|
||||
},
|
||||
{
|
||||
input: "test.com:tag",
|
||||
repository: "test.com",
|
||||
tag: "tag",
|
||||
},
|
||||
{
|
||||
input: "test.com:5000",
|
||||
repository: "test.com",
|
||||
tag: "5000",
|
||||
},
|
||||
{
|
||||
input: "test.com/repo:tag",
|
||||
domain: "test.com",
|
||||
repository: "test.com/repo",
|
||||
tag: "tag",
|
||||
},
|
||||
{
|
||||
input: "test:5000/repo",
|
||||
domain: "test:5000",
|
||||
repository: "test:5000/repo",
|
||||
},
|
||||
{
|
||||
input: "test:5000/repo:tag",
|
||||
domain: "test:5000",
|
||||
repository: "test:5000/repo",
|
||||
tag: "tag",
|
||||
},
|
||||
{
|
||||
input: "test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
domain: "test:5000",
|
||||
repository: "test:5000/repo",
|
||||
digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
},
|
||||
{
|
||||
input: "test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
domain: "test:5000",
|
||||
repository: "test:5000/repo",
|
||||
tag: "tag",
|
||||
digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
},
|
||||
{
|
||||
input: "test:5000/repo",
|
||||
domain: "test:5000",
|
||||
repository: "test:5000/repo",
|
||||
},
|
||||
{
|
||||
input: "",
|
||||
err: ErrNameEmpty,
|
||||
},
|
||||
{
|
||||
input: ":justtag",
|
||||
err: ErrReferenceInvalidFormat,
|
||||
},
|
||||
{
|
||||
input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
err: ErrReferenceInvalidFormat,
|
||||
},
|
||||
{
|
||||
input: "repo@sha256:ffffffffffffffffffffffffffffffffff",
|
||||
err: digest.ErrDigestInvalidLength,
|
||||
},
|
||||
{
|
||||
input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
err: digest.ErrDigestUnsupported,
|
||||
},
|
||||
{
|
||||
input: "Uppercase:tag",
|
||||
err: ErrNameContainsUppercase,
|
||||
},
|
||||
// FIXME "Uppercase" is incorrectly handled as a domain-name here, therefore passes.
|
||||
// See https://github.com/docker/distribution/pull/1778, and https://github.com/docker/docker/pull/20175
|
||||
//{
|
||||
// input: "Uppercase/lowercase:tag",
|
||||
// err: ErrNameContainsUppercase,
|
||||
//},
|
||||
{
|
||||
input: "test:5000/Uppercase/lowercase:tag",
|
||||
err: ErrNameContainsUppercase,
|
||||
},
|
||||
{
|
||||
input: "lowercase:Uppercase",
|
||||
repository: "lowercase",
|
||||
tag: "Uppercase",
|
||||
},
|
||||
{
|
||||
input: strings.Repeat("a/", 128) + "a:tag",
|
||||
err: ErrNameTooLong,
|
||||
},
|
||||
{
|
||||
input: strings.Repeat("a/", 127) + "a:tag-puts-this-over-max",
|
||||
domain: "a",
|
||||
repository: strings.Repeat("a/", 127) + "a",
|
||||
tag: "tag-puts-this-over-max",
|
||||
},
|
||||
{
|
||||
input: "aa/asdf$$^/aa",
|
||||
err: ErrReferenceInvalidFormat,
|
||||
},
|
||||
{
|
||||
input: "sub-dom1.foo.com/bar/baz/quux",
|
||||
domain: "sub-dom1.foo.com",
|
||||
repository: "sub-dom1.foo.com/bar/baz/quux",
|
||||
},
|
||||
{
|
||||
input: "sub-dom1.foo.com/bar/baz/quux:some-long-tag",
|
||||
domain: "sub-dom1.foo.com",
|
||||
repository: "sub-dom1.foo.com/bar/baz/quux",
|
||||
tag: "some-long-tag",
|
||||
},
|
||||
{
|
||||
input: "b.gcr.io/test.example.com/my-app:test.example.com",
|
||||
domain: "b.gcr.io",
|
||||
repository: "b.gcr.io/test.example.com/my-app",
|
||||
tag: "test.example.com",
|
||||
},
|
||||
{
|
||||
input: "xn--n3h.com/myimage:xn--n3h.com", // ☃.com in punycode
|
||||
domain: "xn--n3h.com",
|
||||
repository: "xn--n3h.com/myimage",
|
||||
tag: "xn--n3h.com",
|
||||
},
|
||||
{
|
||||
input: "xn--7o8h.com/myimage:xn--7o8h.com@sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // 🐳.com in punycode
|
||||
domain: "xn--7o8h.com",
|
||||
repository: "xn--7o8h.com/myimage",
|
||||
tag: "xn--7o8h.com",
|
||||
digest: "sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
},
|
||||
{
|
||||
input: "foo_bar.com:8080",
|
||||
repository: "foo_bar.com",
|
||||
tag: "8080",
|
||||
},
|
||||
{
|
||||
input: "foo/foo_bar.com:8080",
|
||||
domain: "foo",
|
||||
repository: "foo/foo_bar.com",
|
||||
tag: "8080",
|
||||
},
|
||||
}
|
||||
for _, testcase := range referenceTestcases {
|
||||
failf := func(format string, v ...interface{}) {
|
||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
repo, err := Parse(testcase.input)
|
||||
if testcase.err != nil {
|
||||
if err == nil {
|
||||
failf("missing expected error: %v", testcase.err)
|
||||
} else if testcase.err != err {
|
||||
failf("mismatched error: got %v, expected %v", err, testcase.err)
|
||||
}
|
||||
continue
|
||||
} else if err != nil {
|
||||
failf("unexpected parse error: %v", err)
|
||||
continue
|
||||
}
|
||||
if repo.String() != testcase.input {
|
||||
failf("mismatched repo: got %q, expected %q", repo.String(), testcase.input)
|
||||
}
|
||||
|
||||
if named, ok := repo.(Named); ok {
|
||||
if named.Name() != testcase.repository {
|
||||
failf("unexpected repository: got %q, expected %q", named.Name(), testcase.repository)
|
||||
}
|
||||
domain, _ := SplitHostname(named)
|
||||
if domain != testcase.domain {
|
||||
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
|
||||
}
|
||||
} else if testcase.repository != "" || testcase.domain != "" {
|
||||
failf("expected named type, got %T", repo)
|
||||
}
|
||||
|
||||
tagged, ok := repo.(Tagged)
|
||||
if testcase.tag != "" {
|
||||
if ok {
|
||||
if tagged.Tag() != testcase.tag {
|
||||
failf("unexpected tag: got %q, expected %q", tagged.Tag(), testcase.tag)
|
||||
}
|
||||
} else {
|
||||
failf("expected tagged type, got %T", repo)
|
||||
}
|
||||
} else if ok {
|
||||
failf("unexpected tagged type")
|
||||
}
|
||||
|
||||
digested, ok := repo.(Digested)
|
||||
if testcase.digest != "" {
|
||||
if ok {
|
||||
if digested.Digest().String() != testcase.digest {
|
||||
failf("unexpected digest: got %q, expected %q", digested.Digest().String(), testcase.digest)
|
||||
}
|
||||
} else {
|
||||
failf("expected digested type, got %T", repo)
|
||||
}
|
||||
} else if ok {
|
||||
failf("unexpected digested type")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// TestWithNameFailure tests cases where WithName should fail. Cases where it
|
||||
// should succeed are covered by TestSplitHostname, below.
|
||||
func TestWithNameFailure(t *testing.T) {
|
||||
testcases := []struct {
|
||||
input string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
input: "",
|
||||
err: ErrNameEmpty,
|
||||
},
|
||||
{
|
||||
input: ":justtag",
|
||||
err: ErrReferenceInvalidFormat,
|
||||
},
|
||||
{
|
||||
input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
err: ErrReferenceInvalidFormat,
|
||||
},
|
||||
{
|
||||
input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
err: ErrReferenceInvalidFormat,
|
||||
},
|
||||
{
|
||||
input: strings.Repeat("a/", 128) + "a:tag",
|
||||
err: ErrNameTooLong,
|
||||
},
|
||||
{
|
||||
input: "aa/asdf$$^/aa",
|
||||
err: ErrReferenceInvalidFormat,
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
failf := func(format string, v ...interface{}) {
|
||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
_, err := WithName(testcase.input)
|
||||
if err == nil {
|
||||
failf("no error parsing name. expected: %s", testcase.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitHostname(t *testing.T) {
|
||||
testcases := []struct {
|
||||
input string
|
||||
domain string
|
||||
name string
|
||||
}{
|
||||
{
|
||||
input: "test.com/foo",
|
||||
domain: "test.com",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "test_com/foo",
|
||||
domain: "",
|
||||
name: "test_com/foo",
|
||||
},
|
||||
{
|
||||
input: "test:8080/foo",
|
||||
domain: "test:8080",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "test.com:8080/foo",
|
||||
domain: "test.com:8080",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "test-com:8080/foo",
|
||||
domain: "test-com:8080",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "xn--n3h.com:18080/foo",
|
||||
domain: "xn--n3h.com:18080",
|
||||
name: "foo",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
failf := func(format string, v ...interface{}) {
|
||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
named, err := WithName(testcase.input)
|
||||
if err != nil {
|
||||
failf("error parsing name: %s", err)
|
||||
}
|
||||
domain, name := SplitHostname(named)
|
||||
if domain != testcase.domain {
|
||||
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
|
||||
}
|
||||
if name != testcase.name {
|
||||
failf("unexpected name: got %q, expected %q", name, testcase.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type serializationType struct {
|
||||
Description string
|
||||
Field Field
|
||||
}
|
||||
|
||||
func TestSerialization(t *testing.T) {
|
||||
testcases := []struct {
|
||||
description string
|
||||
input string
|
||||
name string
|
||||
tag string
|
||||
digest string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
description: "empty value",
|
||||
err: ErrNameEmpty,
|
||||
},
|
||||
{
|
||||
description: "just a name",
|
||||
input: "example.com:8000/named",
|
||||
name: "example.com:8000/named",
|
||||
},
|
||||
{
|
||||
description: "name with a tag",
|
||||
input: "example.com:8000/named:tagged",
|
||||
name: "example.com:8000/named",
|
||||
tag: "tagged",
|
||||
},
|
||||
{
|
||||
description: "name with digest",
|
||||
input: "other.com/named@sha256:1234567890098765432112345667890098765432112345667890098765432112",
|
||||
name: "other.com/named",
|
||||
digest: "sha256:1234567890098765432112345667890098765432112345667890098765432112",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
failf := func(format string, v ...interface{}) {
|
||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
m := map[string]string{
|
||||
"Description": testcase.description,
|
||||
"Field": testcase.input,
|
||||
}
|
||||
b, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
failf("error marshalling: %v", err)
|
||||
}
|
||||
t := serializationType{}
|
||||
|
||||
if err := json.Unmarshal(b, &t); err != nil {
|
||||
if testcase.err == nil {
|
||||
failf("error unmarshalling: %v", err)
|
||||
}
|
||||
if err != testcase.err {
|
||||
failf("wrong error, expected %v, got %v", testcase.err, err)
|
||||
}
|
||||
|
||||
continue
|
||||
} else if testcase.err != nil {
|
||||
failf("expected error unmarshalling: %v", testcase.err)
|
||||
}
|
||||
|
||||
if t.Description != testcase.description {
|
||||
failf("wrong description, expected %q, got %q", testcase.description, t.Description)
|
||||
}
|
||||
|
||||
ref := t.Field.Reference()
|
||||
|
||||
if named, ok := ref.(Named); ok {
|
||||
if named.Name() != testcase.name {
|
||||
failf("unexpected repository: got %q, expected %q", named.Name(), testcase.name)
|
||||
}
|
||||
} else if testcase.name != "" {
|
||||
failf("expected named type, got %T", ref)
|
||||
}
|
||||
|
||||
tagged, ok := ref.(Tagged)
|
||||
if testcase.tag != "" {
|
||||
if ok {
|
||||
if tagged.Tag() != testcase.tag {
|
||||
failf("unexpected tag: got %q, expected %q", tagged.Tag(), testcase.tag)
|
||||
}
|
||||
} else {
|
||||
failf("expected tagged type, got %T", ref)
|
||||
}
|
||||
} else if ok {
|
||||
failf("unexpected tagged type")
|
||||
}
|
||||
|
||||
digested, ok := ref.(Digested)
|
||||
if testcase.digest != "" {
|
||||
if ok {
|
||||
if digested.Digest().String() != testcase.digest {
|
||||
failf("unexpected digest: got %q, expected %q", digested.Digest().String(), testcase.digest)
|
||||
}
|
||||
} else {
|
||||
failf("expected digested type, got %T", ref)
|
||||
}
|
||||
} else if ok {
|
||||
failf("unexpected digested type")
|
||||
}
|
||||
|
||||
t = serializationType{
|
||||
Description: testcase.description,
|
||||
Field: AsField(ref),
|
||||
}
|
||||
|
||||
b2, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
failf("error marshing serialization type: %v", err)
|
||||
}
|
||||
|
||||
if string(b) != string(b2) {
|
||||
failf("unexpected serialized value: expected %q, got %q", string(b), string(b2))
|
||||
}
|
||||
|
||||
// Ensure t.Field is not implementing "Reference" directly, getting
|
||||
// around the Reference type system
|
||||
var fieldInterface interface{} = t.Field
|
||||
if _, ok := fieldInterface.(Reference); ok {
|
||||
failf("field should not implement Reference interface")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithTag(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
digest digest.Digest
|
||||
tag string
|
||||
combined string
|
||||
}{
|
||||
{
|
||||
name: "test.com/foo",
|
||||
tag: "tag",
|
||||
combined: "test.com/foo:tag",
|
||||
},
|
||||
{
|
||||
name: "foo",
|
||||
tag: "tag2",
|
||||
combined: "foo:tag2",
|
||||
},
|
||||
{
|
||||
name: "test.com:8000/foo",
|
||||
tag: "tag4",
|
||||
combined: "test.com:8000/foo:tag4",
|
||||
},
|
||||
{
|
||||
name: "test.com:8000/foo",
|
||||
tag: "TAG5",
|
||||
combined: "test.com:8000/foo:TAG5",
|
||||
},
|
||||
{
|
||||
name: "test.com:8000/foo",
|
||||
digest: "sha256:1234567890098765432112345667890098765",
|
||||
tag: "TAG5",
|
||||
combined: "test.com:8000/foo:TAG5@sha256:1234567890098765432112345667890098765",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
failf := func(format string, v ...interface{}) {
|
||||
t.Logf(strconv.Quote(testcase.name)+": "+format, v...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
named, err := WithName(testcase.name)
|
||||
if err != nil {
|
||||
failf("error parsing name: %s", err)
|
||||
}
|
||||
if testcase.digest != "" {
|
||||
canonical, err := WithDigest(named, testcase.digest)
|
||||
if err != nil {
|
||||
failf("error adding digest")
|
||||
}
|
||||
named = canonical
|
||||
}
|
||||
|
||||
tagged, err := WithTag(named, testcase.tag)
|
||||
if err != nil {
|
||||
failf("WithTag failed: %s", err)
|
||||
}
|
||||
if tagged.String() != testcase.combined {
|
||||
failf("unexpected: got %q, expected %q", tagged.String(), testcase.combined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithDigest(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
digest digest.Digest
|
||||
tag string
|
||||
combined string
|
||||
}{
|
||||
{
|
||||
name: "test.com/foo",
|
||||
digest: "sha256:1234567890098765432112345667890098765",
|
||||
combined: "test.com/foo@sha256:1234567890098765432112345667890098765",
|
||||
},
|
||||
{
|
||||
name: "foo",
|
||||
digest: "sha256:1234567890098765432112345667890098765",
|
||||
combined: "foo@sha256:1234567890098765432112345667890098765",
|
||||
},
|
||||
{
|
||||
name: "test.com:8000/foo",
|
||||
digest: "sha256:1234567890098765432112345667890098765",
|
||||
combined: "test.com:8000/foo@sha256:1234567890098765432112345667890098765",
|
||||
},
|
||||
{
|
||||
name: "test.com:8000/foo",
|
||||
digest: "sha256:1234567890098765432112345667890098765",
|
||||
tag: "latest",
|
||||
combined: "test.com:8000/foo:latest@sha256:1234567890098765432112345667890098765",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
failf := func(format string, v ...interface{}) {
|
||||
t.Logf(strconv.Quote(testcase.name)+": "+format, v...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
named, err := WithName(testcase.name)
|
||||
if err != nil {
|
||||
failf("error parsing name: %s", err)
|
||||
}
|
||||
if testcase.tag != "" {
|
||||
tagged, err := WithTag(named, testcase.tag)
|
||||
if err != nil {
|
||||
failf("error adding tag")
|
||||
}
|
||||
named = tagged
|
||||
}
|
||||
digested, err := WithDigest(named, testcase.digest)
|
||||
if err != nil {
|
||||
failf("WithDigest failed: %s", err)
|
||||
}
|
||||
if digested.String() != testcase.combined {
|
||||
failf("unexpected: got %q, expected %q", digested.String(), testcase.combined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNamed(t *testing.T) {
|
||||
testcases := []struct {
|
||||
input string
|
||||
domain string
|
||||
name string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
input: "test.com/foo",
|
||||
domain: "test.com",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "test:8080/foo",
|
||||
domain: "test:8080",
|
||||
name: "foo",
|
||||
},
|
||||
{
|
||||
input: "test_com/foo",
|
||||
err: ErrNameNotCanonical,
|
||||
},
|
||||
{
|
||||
input: "test.com",
|
||||
err: ErrNameNotCanonical,
|
||||
},
|
||||
{
|
||||
input: "foo",
|
||||
err: ErrNameNotCanonical,
|
||||
},
|
||||
{
|
||||
input: "library/foo",
|
||||
err: ErrNameNotCanonical,
|
||||
},
|
||||
{
|
||||
input: "docker.io/library/foo",
|
||||
domain: "docker.io",
|
||||
name: "library/foo",
|
||||
},
|
||||
// Ambiguous case, parser will add "library/" to foo
|
||||
{
|
||||
input: "docker.io/foo",
|
||||
err: ErrNameNotCanonical,
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
failf := func(format string, v ...interface{}) {
|
||||
t.Logf(strconv.Quote(testcase.input)+": "+format, v...)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
named, err := ParseNamed(testcase.input)
|
||||
if err != nil && testcase.err == nil {
|
||||
failf("error parsing name: %s", err)
|
||||
continue
|
||||
} else if err == nil && testcase.err != nil {
|
||||
failf("parsing succeded: expected error %v", testcase.err)
|
||||
continue
|
||||
} else if err != testcase.err {
|
||||
failf("unexpected error %v, expected %v", err, testcase.err)
|
||||
continue
|
||||
} else if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
domain, name := SplitHostname(named)
|
||||
if domain != testcase.domain {
|
||||
failf("unexpected domain: got %q, expected %q", domain, testcase.domain)
|
||||
}
|
||||
if name != testcase.name {
|
||||
failf("unexpected name: got %q, expected %q", name, testcase.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
143
vendor/github.com/docker/distribution/reference/regexp.go
generated
vendored
Normal file
143
vendor/github.com/docker/distribution/reference/regexp.go
generated
vendored
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
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)))
|
||||
|
||||
// domainComponentRegexp restricts the registry domain component of a
|
||||
// repository name to start with a component as defined by DomainRegexp
|
||||
// and followed by an optional port.
|
||||
domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
|
||||
|
||||
// DomainRegexp defines the structure of potential domain 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.
|
||||
DomainRegexp = expression(
|
||||
domainComponentRegexp,
|
||||
optional(repeated(literal(`.`), domainComponentRegexp)),
|
||||
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 domain and name part omitting
|
||||
// the separating forward slash from either.
|
||||
NameRegexp = expression(
|
||||
optional(DomainRegexp, literal(`/`)),
|
||||
nameComponentRegexp,
|
||||
optional(repeated(literal(`/`), nameComponentRegexp)))
|
||||
|
||||
// anchoredNameRegexp is used to parse a name value, capturing the
|
||||
// domain and trailing components.
|
||||
anchoredNameRegexp = anchored(
|
||||
optional(capture(DomainRegexp), 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)))
|
||||
|
||||
// IdentifierRegexp is the format for string identifier used as a
|
||||
// content addressable identifier using sha256. These identifiers
|
||||
// are like digests without the algorithm, since sha256 is used.
|
||||
IdentifierRegexp = match(`([a-f0-9]{64})`)
|
||||
|
||||
// ShortIdentifierRegexp is the format used to represent a prefix
|
||||
// of an identifier. A prefix may be used to match a sha256 identifier
|
||||
// within a list of trusted identifiers.
|
||||
ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`)
|
||||
|
||||
// anchoredIdentifierRegexp is used to check or match an
|
||||
// identifier value, anchored at start and end of string.
|
||||
anchoredIdentifierRegexp = anchored(IdentifierRegexp)
|
||||
|
||||
// anchoredShortIdentifierRegexp is used to check if a value
|
||||
// is a possible identifier prefix, anchored at start and end
|
||||
// of string.
|
||||
anchoredShortIdentifierRegexp = anchored(ShortIdentifierRegexp)
|
||||
)
|
||||
|
||||
// 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() + `$`)
|
||||
}
|
||||
553
vendor/github.com/docker/distribution/reference/regexp_test.go
generated
vendored
Normal file
553
vendor/github.com/docker/distribution/reference/regexp_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,553 @@
|
|||
package reference
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type regexpMatch struct {
|
||||
input string
|
||||
match bool
|
||||
subs []string
|
||||
}
|
||||
|
||||
func checkRegexp(t *testing.T, r *regexp.Regexp, m regexpMatch) {
|
||||
matches := r.FindStringSubmatch(m.input)
|
||||
if m.match && matches != nil {
|
||||
if len(matches) != (r.NumSubexp()+1) || matches[0] != m.input {
|
||||
t.Fatalf("Bad match result %#v for %q", matches, m.input)
|
||||
}
|
||||
if len(matches) < (len(m.subs) + 1) {
|
||||
t.Errorf("Expected %d sub matches, only have %d for %q", len(m.subs), len(matches)-1, m.input)
|
||||
}
|
||||
for i := range m.subs {
|
||||
if m.subs[i] != matches[i+1] {
|
||||
t.Errorf("Unexpected submatch %d: %q, expected %q for %q", i+1, matches[i+1], m.subs[i], m.input)
|
||||
}
|
||||
}
|
||||
} else if m.match {
|
||||
t.Errorf("Expected match for %q", m.input)
|
||||
} else if matches != nil {
|
||||
t.Errorf("Unexpected match for %q", m.input)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainRegexp(t *testing.T) {
|
||||
hostcases := []regexpMatch{
|
||||
{
|
||||
input: "test.com",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "test.com:10304",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "test.com:http",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "localhost",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "localhost:8080",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "a",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "a.b",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "ab.cd.com",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "a-b.com",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "-ab.com",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "ab-.com",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "ab.c-om",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "ab.-com",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "ab.com-",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "0101.com",
|
||||
match: true, // TODO(dmcgowan): valid if this should be allowed
|
||||
},
|
||||
{
|
||||
input: "001a.com",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "b.gbc.io:443",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "b.gbc.io",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "xn--n3h.com", // ☃.com in punycode
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "Asdf.com", // uppercase character
|
||||
match: true,
|
||||
},
|
||||
}
|
||||
r := regexp.MustCompile(`^` + DomainRegexp.String() + `$`)
|
||||
for i := range hostcases {
|
||||
checkRegexp(t, r, hostcases[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestFullNameRegexp(t *testing.T) {
|
||||
if anchoredNameRegexp.NumSubexp() != 2 {
|
||||
t.Fatalf("anchored name regexp should have two submatches: %v, %v != 2",
|
||||
anchoredNameRegexp, anchoredNameRegexp.NumSubexp())
|
||||
}
|
||||
|
||||
testcases := []regexpMatch{
|
||||
{
|
||||
input: "",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "short",
|
||||
match: true,
|
||||
subs: []string{"", "short"},
|
||||
},
|
||||
{
|
||||
input: "simple/name",
|
||||
match: true,
|
||||
subs: []string{"simple", "name"},
|
||||
},
|
||||
{
|
||||
input: "library/ubuntu",
|
||||
match: true,
|
||||
subs: []string{"library", "ubuntu"},
|
||||
},
|
||||
{
|
||||
input: "docker/stevvooe/app",
|
||||
match: true,
|
||||
subs: []string{"docker", "stevvooe/app"},
|
||||
},
|
||||
{
|
||||
input: "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb",
|
||||
match: true,
|
||||
subs: []string{"aa", "aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb"},
|
||||
},
|
||||
{
|
||||
input: "aa/aa/bb/bb/bb",
|
||||
match: true,
|
||||
subs: []string{"aa", "aa/bb/bb/bb"},
|
||||
},
|
||||
{
|
||||
input: "a/a/a/a",
|
||||
match: true,
|
||||
subs: []string{"a", "a/a/a"},
|
||||
},
|
||||
{
|
||||
input: "a/a/a/a/",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "a//a/a",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "a",
|
||||
match: true,
|
||||
subs: []string{"", "a"},
|
||||
},
|
||||
{
|
||||
input: "a/aa",
|
||||
match: true,
|
||||
subs: []string{"a", "aa"},
|
||||
},
|
||||
{
|
||||
input: "a/aa/a",
|
||||
match: true,
|
||||
subs: []string{"a", "aa/a"},
|
||||
},
|
||||
{
|
||||
input: "foo.com",
|
||||
match: true,
|
||||
subs: []string{"", "foo.com"},
|
||||
},
|
||||
{
|
||||
input: "foo.com/",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo.com:8080/bar",
|
||||
match: true,
|
||||
subs: []string{"foo.com:8080", "bar"},
|
||||
},
|
||||
{
|
||||
input: "foo.com:http/bar",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo.com/bar",
|
||||
match: true,
|
||||
subs: []string{"foo.com", "bar"},
|
||||
},
|
||||
{
|
||||
input: "foo.com/bar/baz",
|
||||
match: true,
|
||||
subs: []string{"foo.com", "bar/baz"},
|
||||
},
|
||||
{
|
||||
input: "localhost:8080/bar",
|
||||
match: true,
|
||||
subs: []string{"localhost:8080", "bar"},
|
||||
},
|
||||
{
|
||||
input: "sub-dom1.foo.com/bar/baz/quux",
|
||||
match: true,
|
||||
subs: []string{"sub-dom1.foo.com", "bar/baz/quux"},
|
||||
},
|
||||
{
|
||||
input: "blog.foo.com/bar/baz",
|
||||
match: true,
|
||||
subs: []string{"blog.foo.com", "bar/baz"},
|
||||
},
|
||||
{
|
||||
input: "a^a",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "aa/asdf$$^/aa",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "asdf$$^/aa",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "aa-a/a",
|
||||
match: true,
|
||||
subs: []string{"aa-a", "a"},
|
||||
},
|
||||
{
|
||||
input: strings.Repeat("a/", 128) + "a",
|
||||
match: true,
|
||||
subs: []string{"a", strings.Repeat("a/", 127) + "a"},
|
||||
},
|
||||
{
|
||||
input: "a-/a/a/a",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo.com/a-/a/a",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "-foo/bar",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo/bar-",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo-/bar",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo/-bar",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "_foo/bar",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo_bar",
|
||||
match: true,
|
||||
subs: []string{"", "foo_bar"},
|
||||
},
|
||||
{
|
||||
input: "foo_bar.com",
|
||||
match: true,
|
||||
subs: []string{"", "foo_bar.com"},
|
||||
},
|
||||
{
|
||||
input: "foo_bar.com:8080",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo_bar.com:8080/app",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "foo.com/foo_bar",
|
||||
match: true,
|
||||
subs: []string{"foo.com", "foo_bar"},
|
||||
},
|
||||
{
|
||||
input: "____/____",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "_docker/_docker",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "docker_/docker_",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "b.gcr.io/test.example.com/my-app",
|
||||
match: true,
|
||||
subs: []string{"b.gcr.io", "test.example.com/my-app"},
|
||||
},
|
||||
{
|
||||
input: "xn--n3h.com/myimage", // ☃.com in punycode
|
||||
match: true,
|
||||
subs: []string{"xn--n3h.com", "myimage"},
|
||||
},
|
||||
{
|
||||
input: "xn--7o8h.com/myimage", // 🐳.com in punycode
|
||||
match: true,
|
||||
subs: []string{"xn--7o8h.com", "myimage"},
|
||||
},
|
||||
{
|
||||
input: "example.com/xn--7o8h.com/myimage", // 🐳.com in punycode
|
||||
match: true,
|
||||
subs: []string{"example.com", "xn--7o8h.com/myimage"},
|
||||
},
|
||||
{
|
||||
input: "example.com/some_separator__underscore/myimage",
|
||||
match: true,
|
||||
subs: []string{"example.com", "some_separator__underscore/myimage"},
|
||||
},
|
||||
{
|
||||
input: "example.com/__underscore/myimage",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "example.com/..dots/myimage",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "example.com/.dots/myimage",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "example.com/nodouble..dots/myimage",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "example.com/nodouble..dots/myimage",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "docker./docker",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: ".docker/docker",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "docker-/docker",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "-docker/docker",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "do..cker/docker",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "do__cker:8080/docker",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "do__cker/docker",
|
||||
match: true,
|
||||
subs: []string{"", "do__cker/docker"},
|
||||
},
|
||||
{
|
||||
input: "b.gcr.io/test.example.com/my-app",
|
||||
match: true,
|
||||
subs: []string{"b.gcr.io", "test.example.com/my-app"},
|
||||
},
|
||||
{
|
||||
input: "registry.io/foo/project--id.module--name.ver---sion--name",
|
||||
match: true,
|
||||
subs: []string{"registry.io", "foo/project--id.module--name.ver---sion--name"},
|
||||
},
|
||||
{
|
||||
input: "Asdf.com/foo/bar", // uppercase character in hostname
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "Foo/FarB", // uppercase characters in remote name
|
||||
match: false,
|
||||
},
|
||||
}
|
||||
for i := range testcases {
|
||||
checkRegexp(t, anchoredNameRegexp, testcases[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestReferenceRegexp(t *testing.T) {
|
||||
if ReferenceRegexp.NumSubexp() != 3 {
|
||||
t.Fatalf("anchored name regexp should have three submatches: %v, %v != 3",
|
||||
ReferenceRegexp, ReferenceRegexp.NumSubexp())
|
||||
}
|
||||
|
||||
testcases := []regexpMatch{
|
||||
{
|
||||
input: "registry.com:8080/myapp:tag",
|
||||
match: true,
|
||||
subs: []string{"registry.com:8080/myapp", "tag", ""},
|
||||
},
|
||||
{
|
||||
input: "registry.com:8080/myapp@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: true,
|
||||
subs: []string{"registry.com:8080/myapp", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
},
|
||||
{
|
||||
input: "registry.com:8080/myapp:tag2@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: true,
|
||||
subs: []string{"registry.com:8080/myapp", "tag2", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
},
|
||||
{
|
||||
input: "registry.com:8080/myapp@sha256:badbadbadbad",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "registry.com:8080/myapp:invalid~tag",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "bad_hostname.com:8080/myapp:tag",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input:// localhost treated as name, missing tag with 8080 as tag
|
||||
"localhost:8080@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: true,
|
||||
subs: []string{"localhost", "8080", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
},
|
||||
{
|
||||
input: "localhost:8080/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: true,
|
||||
subs: []string{"localhost:8080/name", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
},
|
||||
{
|
||||
input: "localhost:http/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
// localhost will be treated as an image name without a host
|
||||
input: "localhost@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
|
||||
match: true,
|
||||
subs: []string{"localhost", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
|
||||
},
|
||||
{
|
||||
input: "registry.com:8080/myapp@bad",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "registry.com:8080/myapp@2bad",
|
||||
match: false, // TODO(dmcgowan): Support this as valid
|
||||
},
|
||||
}
|
||||
|
||||
for i := range testcases {
|
||||
checkRegexp(t, ReferenceRegexp, testcases[i])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIdentifierRegexp(t *testing.T) {
|
||||
fullCases := []regexpMatch{
|
||||
{
|
||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "7EC43B381E5AEFE6E04EFB0B3F0693FF2A4A50652D64AEC573905F2DB5889A1C",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "sha256:da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf98218482",
|
||||
match: false,
|
||||
},
|
||||
}
|
||||
|
||||
shortCases := []regexpMatch{
|
||||
{
|
||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "7EC43B381E5AEFE6E04EFB0B3F0693FF2A4A50652D64AEC573905F2DB5889A1C",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf",
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "sha256:da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf98218482",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "da304",
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "da304e",
|
||||
match: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i := range fullCases {
|
||||
checkRegexp(t, anchoredIdentifierRegexp, fullCases[i])
|
||||
}
|
||||
|
||||
for i := range shortCases {
|
||||
checkRegexp(t, anchoredShortIdentifierRegexp, shortCases[i])
|
||||
}
|
||||
}
|
||||
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 lexicographically 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)
|
||||
}
|
||||
43
vendor/github.com/docker/distribution/vendor.conf
generated
vendored
Normal file
43
vendor/github.com/docker/distribution/vendor.conf
generated
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
github.com/Azure/azure-sdk-for-go 088007b3b08cc02b27f2eadfdcd870958460ce7e
|
||||
github.com/Azure/go-autorest ec5f4903f77ed9927ac95b19ab8e44ada64c1356
|
||||
github.com/sirupsen/logrus 3d4380f53a34dcdc95f0c1db702615992b38d9a4
|
||||
github.com/aws/aws-sdk-go c6fc52983ea2375810aa38ddb5370e9cdf611716
|
||||
github.com/bshuster-repo/logrus-logstash-hook d2c0ecc1836d91814e15e23bb5dc309c3ef51f4a
|
||||
github.com/bugsnag/bugsnag-go b1d153021fcd90ca3f080db36bec96dc690fb274
|
||||
github.com/bugsnag/osext 0dd3f918b21bec95ace9dc86c7e70266cfc5c702
|
||||
github.com/bugsnag/panicwrap e2c28503fcd0675329da73bf48b33404db873782
|
||||
github.com/denverdino/aliyungo afedced274aa9a7fcdd47ac97018f0f8db4e5de2
|
||||
github.com/dgrijalva/jwt-go a601269ab70c205d26370c16f7c81e9017c14e04
|
||||
github.com/docker/goamz f0a21f5b2e12f83a505ecf79b633bb2035cf6f85
|
||||
github.com/docker/libtrust fa567046d9b14f6aa788882a950d69651d230b21
|
||||
github.com/garyburd/redigo 535138d7bcd717d6531c701ef5933d98b1866257
|
||||
github.com/go-ini/ini 2ba15ac2dc9cdf88c110ec2dc0ced7fa45f5678c
|
||||
github.com/golang/protobuf 8d92cf5fc15a4382f8964b08e1f42a75c0591aa3
|
||||
github.com/gorilla/context 14f550f51af52180c2eefed15e5fd18d63c0a64a
|
||||
github.com/gorilla/handlers 60c7bfde3e33c201519a200a4507a158cc03a17b
|
||||
github.com/gorilla/mux 599cba5e7b6137d46ddf58fb1765f5d928e69604
|
||||
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||
github.com/jmespath/go-jmespath bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d
|
||||
github.com/miekg/dns 271c58e0c14f552178ea321a545ff9af38930f39
|
||||
github.com/mitchellh/mapstructure 482a9fd5fa83e8c4e7817413b80f3eb8feec03ef
|
||||
github.com/ncw/swift b964f2ca856aac39885e258ad25aec08d5f64ee6
|
||||
github.com/spf13/cobra 312092086bed4968099259622145a0c9ae280064
|
||||
github.com/spf13/pflag 5644820622454e71517561946e3d94b9f9db6842
|
||||
github.com/stevvooe/resumable 2aaf90b2ceea5072cb503ef2a620b08ff3119870
|
||||
github.com/xenolf/lego a9d8cec0e6563575e5868a005359ac97911b5985
|
||||
github.com/yvasiyarov/go-metrics 57bccd1ccd43f94bb17fdd8bf3007059b802f85e
|
||||
github.com/yvasiyarov/gorelic a9bba5b9ab508a086f9a12b8c51fab68478e2128
|
||||
github.com/yvasiyarov/newrelic_platform_go b21fdbd4370f3717f3bbd2bf41c223bc273068e6
|
||||
golang.org/x/crypto c10c31b5e94b6f7a0283272dc2bb27163dcea24b
|
||||
golang.org/x/net 4876518f9e71663000c348837735820161a42df7
|
||||
golang.org/x/oauth2 045497edb6234273d67dbc25da3f2ddbc4c4cacf
|
||||
golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb
|
||||
google.golang.org/api 9bf6e6e569ff057f75d9604a46c52928f17d2b54
|
||||
google.golang.org/appengine 12d5545dc1cfa6047a286d5e853841b6471f4c19
|
||||
google.golang.org/cloud 975617b05ea8a58727e6c1a06b6161ff4185a9f2
|
||||
google.golang.org/grpc d3ddb4469d5a1b949fc7a7da7c1d6a0d1b6de994
|
||||
gopkg.in/check.v1 64131543e7896d5bcc6bd5a76287eb75ea96c673
|
||||
gopkg.in/square/go-jose.v1 40d457b439244b546f023d056628e5184136899b
|
||||
gopkg.in/yaml.v2 bef53efd0c76e49e6de55ead051f886bea7e9420
|
||||
rsc.io/letsencrypt e770c10b0f1a64775ae91d240407ce00d1a5bdeb https://github.com/dmcgowan/letsencrypt.git
|
||||
github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb
|
||||
13
vendor/github.com/docker/spdystream/CONTRIBUTING.md
generated
vendored
Normal file
13
vendor/github.com/docker/spdystream/CONTRIBUTING.md
generated
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Contributing to SpdyStream
|
||||
|
||||
Want to hack on spdystream? Awesome! Here are instructions to get you
|
||||
started.
|
||||
|
||||
SpdyStream is a part of the [Docker](https://docker.io) project, and follows
|
||||
the same rules and principles. If you're already familiar with the way
|
||||
Docker does things, you'll feel right at home.
|
||||
|
||||
Otherwise, go read
|
||||
[Docker's contributions guidelines](https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md).
|
||||
|
||||
Happy hacking!
|
||||
191
vendor/github.com/docker/spdystream/LICENSE
generated
vendored
Normal file
191
vendor/github.com/docker/spdystream/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2014-2015 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
|
||||
|
||||
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.
|
||||
425
vendor/github.com/docker/spdystream/LICENSE.docs
generated
vendored
Normal file
425
vendor/github.com/docker/spdystream/LICENSE.docs
generated
vendored
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
Attribution-ShareAlike 4.0 International
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More_considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution-ShareAlike 4.0 International Public
|
||||
License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution-ShareAlike 4.0 International Public License ("Public
|
||||
License"). To the extent this Public License may be interpreted as a
|
||||
contract, You are granted the Licensed Rights in consideration of Your
|
||||
acceptance of these terms and conditions, and the Licensor grants You
|
||||
such rights in consideration of benefits the Licensor receives from
|
||||
making the Licensed Material available under these terms and
|
||||
conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. BY-SA Compatible License means a license listed at
|
||||
creativecommons.org/compatiblelicenses, approved by Creative
|
||||
Commons as essentially the equivalent of this Public License.
|
||||
|
||||
d. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
e. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
f. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
g. License Elements means the license attributes listed in the name
|
||||
of a Creative Commons Public License. The License Elements of this
|
||||
Public License are Attribution and ShareAlike.
|
||||
|
||||
h. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
i. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
j. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
k. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
l. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
m. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. Additional offer from the Licensor -- Adapted Material.
|
||||
Every recipient of Adapted Material from You
|
||||
automatically receives an offer from the Licensor to
|
||||
exercise the Licensed Rights in the Adapted Material
|
||||
under the conditions of the Adapter's License You apply.
|
||||
|
||||
c. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
b. ShareAlike.
|
||||
|
||||
In addition to the conditions in Section 3(a), if You Share
|
||||
Adapted Material You produce, the following conditions also apply.
|
||||
|
||||
1. The Adapter's License You apply must be a Creative Commons
|
||||
license with the same License Elements, this version or
|
||||
later, or a BY-SA Compatible License.
|
||||
|
||||
2. You must include the text of, or the URI or hyperlink to, the
|
||||
Adapter's License You apply. You may satisfy this condition
|
||||
in any reasonable manner based on the medium, means, and
|
||||
context in which You Share Adapted Material.
|
||||
|
||||
3. You may not offer or impose any additional or different terms
|
||||
or conditions on, or apply any Effective Technological
|
||||
Measures to, Adapted Material that restrict exercise of the
|
||||
rights granted under the Adapter's License You apply.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material,
|
||||
|
||||
including for purposes of Section 3(b); and
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public licenses.
|
||||
Notwithstanding, Creative Commons may elect to apply one of its public
|
||||
licenses to material it publishes and in those instances will be
|
||||
considered the "Licensor." Except for the limited purpose of indicating
|
||||
that material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the public
|
||||
licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
28
vendor/github.com/docker/spdystream/MAINTAINERS
generated
vendored
Normal file
28
vendor/github.com/docker/spdystream/MAINTAINERS
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Spdystream maintainers file
|
||||
#
|
||||
# This file describes who runs the docker/spdystream 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 = [
|
||||
"dmcgowan",
|
||||
]
|
||||
|
||||
[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.dmcgowan]
|
||||
Name = "Derek McGowan"
|
||||
Email = "derek@docker.com"
|
||||
GitHub = "dmcgowan"
|
||||
77
vendor/github.com/docker/spdystream/README.md
generated
vendored
Normal file
77
vendor/github.com/docker/spdystream/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# SpdyStream
|
||||
|
||||
A multiplexed stream library using spdy
|
||||
|
||||
## Usage
|
||||
|
||||
Client example (connecting to mirroring server without auth)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/docker/spdystream"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
conn, err := net.Dial("tcp", "localhost:8080")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
spdyConn, err := spdystream.NewConnection(conn, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go spdyConn.Serve(spdystream.NoOpStreamHandler)
|
||||
stream, err := spdyConn.CreateStream(http.Header{}, nil, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stream.Wait()
|
||||
|
||||
fmt.Fprint(stream, "Writing to stream")
|
||||
|
||||
buf := make([]byte, 25)
|
||||
stream.Read(buf)
|
||||
fmt.Println(string(buf))
|
||||
|
||||
stream.Close()
|
||||
}
|
||||
```
|
||||
|
||||
Server example (mirroring server without auth)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/spdystream"
|
||||
"net"
|
||||
)
|
||||
|
||||
func main() {
|
||||
listener, err := net.Listen("tcp", "localhost:8080")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
spdyConn, err := spdystream.NewConnection(conn, true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go spdyConn.Serve(spdystream.MirrorStreamHandler)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Copyright and license
|
||||
|
||||
Copyright © 2014-2015 Docker, Inc. All rights reserved, except as follows. Code is released under the Apache 2.0 license. The README.md file, and files in the "docs" folder are licensed under the Creative Commons Attribution 4.0 International License under the terms and conditions set forth in the file "LICENSE.docs". You may obtain a duplicate copy of the same license, titled CC-BY-SA-4.0, at http://creativecommons.org/licenses/by/4.0/.
|
||||
959
vendor/github.com/docker/spdystream/connection.go
generated
vendored
Normal file
959
vendor/github.com/docker/spdystream/connection.go
generated
vendored
Normal file
|
|
@ -0,0 +1,959 @@
|
|||
package spdystream
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/spdystream/spdy"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidStreamId = errors.New("Invalid stream id")
|
||||
ErrTimeout = errors.New("Timeout occurred")
|
||||
ErrReset = errors.New("Stream reset")
|
||||
ErrWriteClosedStream = errors.New("Write on closed stream")
|
||||
)
|
||||
|
||||
const (
|
||||
FRAME_WORKERS = 5
|
||||
QUEUE_SIZE = 50
|
||||
)
|
||||
|
||||
type StreamHandler func(stream *Stream)
|
||||
|
||||
type AuthHandler func(header http.Header, slot uint8, parent uint32) bool
|
||||
|
||||
type idleAwareFramer struct {
|
||||
f *spdy.Framer
|
||||
conn *Connection
|
||||
writeLock sync.Mutex
|
||||
resetChan chan struct{}
|
||||
setTimeoutLock sync.Mutex
|
||||
setTimeoutChan chan time.Duration
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func newIdleAwareFramer(framer *spdy.Framer) *idleAwareFramer {
|
||||
iaf := &idleAwareFramer{
|
||||
f: framer,
|
||||
resetChan: make(chan struct{}, 2),
|
||||
// setTimeoutChan needs to be buffered to avoid deadlocks when calling setIdleTimeout at about
|
||||
// the same time the connection is being closed
|
||||
setTimeoutChan: make(chan time.Duration, 1),
|
||||
}
|
||||
return iaf
|
||||
}
|
||||
|
||||
func (i *idleAwareFramer) monitor() {
|
||||
var (
|
||||
timer *time.Timer
|
||||
expired <-chan time.Time
|
||||
resetChan = i.resetChan
|
||||
setTimeoutChan = i.setTimeoutChan
|
||||
)
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case timeout := <-i.setTimeoutChan:
|
||||
i.timeout = timeout
|
||||
if timeout == 0 {
|
||||
if timer != nil {
|
||||
timer.Stop()
|
||||
}
|
||||
} else {
|
||||
if timer == nil {
|
||||
timer = time.NewTimer(timeout)
|
||||
expired = timer.C
|
||||
} else {
|
||||
timer.Reset(timeout)
|
||||
}
|
||||
}
|
||||
case <-resetChan:
|
||||
if timer != nil && i.timeout > 0 {
|
||||
timer.Reset(i.timeout)
|
||||
}
|
||||
case <-expired:
|
||||
i.conn.streamCond.L.Lock()
|
||||
streams := i.conn.streams
|
||||
i.conn.streams = make(map[spdy.StreamId]*Stream)
|
||||
i.conn.streamCond.Broadcast()
|
||||
i.conn.streamCond.L.Unlock()
|
||||
go func() {
|
||||
for _, stream := range streams {
|
||||
stream.resetStream()
|
||||
}
|
||||
i.conn.Close()
|
||||
}()
|
||||
case <-i.conn.closeChan:
|
||||
if timer != nil {
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
// Start a goroutine to drain resetChan. This is needed because we've seen
|
||||
// some unit tests with large numbers of goroutines get into a situation
|
||||
// where resetChan fills up, at least 1 call to Write() is still trying to
|
||||
// send to resetChan, the connection gets closed, and this case statement
|
||||
// attempts to grab the write lock that Write() already has, causing a
|
||||
// deadlock.
|
||||
//
|
||||
// See https://github.com/docker/spdystream/issues/49 for more details.
|
||||
go func() {
|
||||
for _ = range resetChan {
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for _ = range setTimeoutChan {
|
||||
}
|
||||
}()
|
||||
|
||||
i.writeLock.Lock()
|
||||
close(resetChan)
|
||||
i.resetChan = nil
|
||||
i.writeLock.Unlock()
|
||||
|
||||
i.setTimeoutLock.Lock()
|
||||
close(i.setTimeoutChan)
|
||||
i.setTimeoutChan = nil
|
||||
i.setTimeoutLock.Unlock()
|
||||
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
|
||||
// Drain resetChan
|
||||
for _ = range resetChan {
|
||||
}
|
||||
}
|
||||
|
||||
func (i *idleAwareFramer) WriteFrame(frame spdy.Frame) error {
|
||||
i.writeLock.Lock()
|
||||
defer i.writeLock.Unlock()
|
||||
if i.resetChan == nil {
|
||||
return io.EOF
|
||||
}
|
||||
err := i.f.WriteFrame(frame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.resetChan <- struct{}{}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *idleAwareFramer) ReadFrame() (spdy.Frame, error) {
|
||||
frame, err := i.f.ReadFrame()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// resetChan should never be closed since it is only closed
|
||||
// when the connection has closed its closeChan. This closure
|
||||
// only occurs after all Reads have finished
|
||||
// TODO (dmcgowan): refactor relationship into connection
|
||||
i.resetChan <- struct{}{}
|
||||
|
||||
return frame, nil
|
||||
}
|
||||
|
||||
func (i *idleAwareFramer) setIdleTimeout(timeout time.Duration) {
|
||||
i.setTimeoutLock.Lock()
|
||||
defer i.setTimeoutLock.Unlock()
|
||||
|
||||
if i.setTimeoutChan == nil {
|
||||
return
|
||||
}
|
||||
|
||||
i.setTimeoutChan <- timeout
|
||||
}
|
||||
|
||||
type Connection struct {
|
||||
conn net.Conn
|
||||
framer *idleAwareFramer
|
||||
|
||||
closeChan chan bool
|
||||
goneAway bool
|
||||
lastStreamChan chan<- *Stream
|
||||
goAwayTimeout time.Duration
|
||||
closeTimeout time.Duration
|
||||
|
||||
streamLock *sync.RWMutex
|
||||
streamCond *sync.Cond
|
||||
streams map[spdy.StreamId]*Stream
|
||||
|
||||
nextIdLock sync.Mutex
|
||||
receiveIdLock sync.Mutex
|
||||
nextStreamId spdy.StreamId
|
||||
receivedStreamId spdy.StreamId
|
||||
|
||||
pingIdLock sync.Mutex
|
||||
pingId uint32
|
||||
pingChans map[uint32]chan error
|
||||
|
||||
shutdownLock sync.Mutex
|
||||
shutdownChan chan error
|
||||
hasShutdown bool
|
||||
|
||||
// for testing https://github.com/docker/spdystream/pull/56
|
||||
dataFrameHandler func(*spdy.DataFrame) error
|
||||
}
|
||||
|
||||
// NewConnection creates a new spdy connection from an existing
|
||||
// network connection.
|
||||
func NewConnection(conn net.Conn, server bool) (*Connection, error) {
|
||||
framer, framerErr := spdy.NewFramer(conn, conn)
|
||||
if framerErr != nil {
|
||||
return nil, framerErr
|
||||
}
|
||||
idleAwareFramer := newIdleAwareFramer(framer)
|
||||
var sid spdy.StreamId
|
||||
var rid spdy.StreamId
|
||||
var pid uint32
|
||||
if server {
|
||||
sid = 2
|
||||
rid = 1
|
||||
pid = 2
|
||||
} else {
|
||||
sid = 1
|
||||
rid = 2
|
||||
pid = 1
|
||||
}
|
||||
|
||||
streamLock := new(sync.RWMutex)
|
||||
streamCond := sync.NewCond(streamLock)
|
||||
|
||||
session := &Connection{
|
||||
conn: conn,
|
||||
framer: idleAwareFramer,
|
||||
|
||||
closeChan: make(chan bool),
|
||||
goAwayTimeout: time.Duration(0),
|
||||
closeTimeout: time.Duration(0),
|
||||
|
||||
streamLock: streamLock,
|
||||
streamCond: streamCond,
|
||||
streams: make(map[spdy.StreamId]*Stream),
|
||||
nextStreamId: sid,
|
||||
receivedStreamId: rid,
|
||||
|
||||
pingId: pid,
|
||||
pingChans: make(map[uint32]chan error),
|
||||
|
||||
shutdownChan: make(chan error),
|
||||
}
|
||||
session.dataFrameHandler = session.handleDataFrame
|
||||
idleAwareFramer.conn = session
|
||||
go idleAwareFramer.monitor()
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// Ping sends a ping frame across the connection and
|
||||
// returns the response time
|
||||
func (s *Connection) Ping() (time.Duration, error) {
|
||||
pid := s.pingId
|
||||
s.pingIdLock.Lock()
|
||||
if s.pingId > 0x7ffffffe {
|
||||
s.pingId = s.pingId - 0x7ffffffe
|
||||
} else {
|
||||
s.pingId = s.pingId + 2
|
||||
}
|
||||
s.pingIdLock.Unlock()
|
||||
pingChan := make(chan error)
|
||||
s.pingChans[pid] = pingChan
|
||||
defer delete(s.pingChans, pid)
|
||||
|
||||
frame := &spdy.PingFrame{Id: pid}
|
||||
startTime := time.Now()
|
||||
writeErr := s.framer.WriteFrame(frame)
|
||||
if writeErr != nil {
|
||||
return time.Duration(0), writeErr
|
||||
}
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
return time.Duration(0), errors.New("connection closed")
|
||||
case err, ok := <-pingChan:
|
||||
if ok && err != nil {
|
||||
return time.Duration(0), err
|
||||
}
|
||||
break
|
||||
}
|
||||
return time.Now().Sub(startTime), nil
|
||||
}
|
||||
|
||||
// Serve handles frames sent from the server, including reply frames
|
||||
// which are needed to fully initiate connections. Both clients and servers
|
||||
// should call Serve in a separate goroutine before creating streams.
|
||||
func (s *Connection) Serve(newHandler StreamHandler) {
|
||||
// use a WaitGroup to wait for all frames to be drained after receiving
|
||||
// go-away.
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Parition queues to ensure stream frames are handled
|
||||
// by the same worker, ensuring order is maintained
|
||||
frameQueues := make([]*PriorityFrameQueue, FRAME_WORKERS)
|
||||
for i := 0; i < FRAME_WORKERS; i++ {
|
||||
frameQueues[i] = NewPriorityFrameQueue(QUEUE_SIZE)
|
||||
|
||||
// Ensure frame queue is drained when connection is closed
|
||||
go func(frameQueue *PriorityFrameQueue) {
|
||||
<-s.closeChan
|
||||
frameQueue.Drain()
|
||||
}(frameQueues[i])
|
||||
|
||||
wg.Add(1)
|
||||
go func(frameQueue *PriorityFrameQueue) {
|
||||
// let the WaitGroup know this worker is done
|
||||
defer wg.Done()
|
||||
|
||||
s.frameHandler(frameQueue, newHandler)
|
||||
}(frameQueues[i])
|
||||
}
|
||||
|
||||
var (
|
||||
partitionRoundRobin int
|
||||
goAwayFrame *spdy.GoAwayFrame
|
||||
)
|
||||
Loop:
|
||||
for {
|
||||
readFrame, err := s.framer.ReadFrame()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
debugMessage("frame read error: %s", err)
|
||||
} else {
|
||||
debugMessage("(%p) EOF received", s)
|
||||
}
|
||||
break
|
||||
}
|
||||
var priority uint8
|
||||
var partition int
|
||||
switch frame := readFrame.(type) {
|
||||
case *spdy.SynStreamFrame:
|
||||
if s.checkStreamFrame(frame) {
|
||||
priority = frame.Priority
|
||||
partition = int(frame.StreamId % FRAME_WORKERS)
|
||||
debugMessage("(%p) Add stream frame: %d ", s, frame.StreamId)
|
||||
s.addStreamFrame(frame)
|
||||
} else {
|
||||
debugMessage("(%p) Rejected stream frame: %d ", s, frame.StreamId)
|
||||
continue
|
||||
}
|
||||
case *spdy.SynReplyFrame:
|
||||
priority = s.getStreamPriority(frame.StreamId)
|
||||
partition = int(frame.StreamId % FRAME_WORKERS)
|
||||
case *spdy.DataFrame:
|
||||
priority = s.getStreamPriority(frame.StreamId)
|
||||
partition = int(frame.StreamId % FRAME_WORKERS)
|
||||
case *spdy.RstStreamFrame:
|
||||
priority = s.getStreamPriority(frame.StreamId)
|
||||
partition = int(frame.StreamId % FRAME_WORKERS)
|
||||
case *spdy.HeadersFrame:
|
||||
priority = s.getStreamPriority(frame.StreamId)
|
||||
partition = int(frame.StreamId % FRAME_WORKERS)
|
||||
case *spdy.PingFrame:
|
||||
priority = 0
|
||||
partition = partitionRoundRobin
|
||||
partitionRoundRobin = (partitionRoundRobin + 1) % FRAME_WORKERS
|
||||
case *spdy.GoAwayFrame:
|
||||
// hold on to the go away frame and exit the loop
|
||||
goAwayFrame = frame
|
||||
break Loop
|
||||
default:
|
||||
priority = 7
|
||||
partition = partitionRoundRobin
|
||||
partitionRoundRobin = (partitionRoundRobin + 1) % FRAME_WORKERS
|
||||
}
|
||||
frameQueues[partition].Push(readFrame, priority)
|
||||
}
|
||||
close(s.closeChan)
|
||||
|
||||
// wait for all frame handler workers to indicate they've drained their queues
|
||||
// before handling the go away frame
|
||||
wg.Wait()
|
||||
|
||||
if goAwayFrame != nil {
|
||||
s.handleGoAwayFrame(goAwayFrame)
|
||||
}
|
||||
|
||||
// now it's safe to close remote channels and empty s.streams
|
||||
s.streamCond.L.Lock()
|
||||
// notify streams that they're now closed, which will
|
||||
// unblock any stream Read() calls
|
||||
for _, stream := range s.streams {
|
||||
stream.closeRemoteChannels()
|
||||
}
|
||||
s.streams = make(map[spdy.StreamId]*Stream)
|
||||
s.streamCond.Broadcast()
|
||||
s.streamCond.L.Unlock()
|
||||
}
|
||||
|
||||
func (s *Connection) frameHandler(frameQueue *PriorityFrameQueue, newHandler StreamHandler) {
|
||||
for {
|
||||
popFrame := frameQueue.Pop()
|
||||
if popFrame == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var frameErr error
|
||||
switch frame := popFrame.(type) {
|
||||
case *spdy.SynStreamFrame:
|
||||
frameErr = s.handleStreamFrame(frame, newHandler)
|
||||
case *spdy.SynReplyFrame:
|
||||
frameErr = s.handleReplyFrame(frame)
|
||||
case *spdy.DataFrame:
|
||||
frameErr = s.dataFrameHandler(frame)
|
||||
case *spdy.RstStreamFrame:
|
||||
frameErr = s.handleResetFrame(frame)
|
||||
case *spdy.HeadersFrame:
|
||||
frameErr = s.handleHeaderFrame(frame)
|
||||
case *spdy.PingFrame:
|
||||
frameErr = s.handlePingFrame(frame)
|
||||
case *spdy.GoAwayFrame:
|
||||
frameErr = s.handleGoAwayFrame(frame)
|
||||
default:
|
||||
frameErr = fmt.Errorf("unhandled frame type: %T", frame)
|
||||
}
|
||||
|
||||
if frameErr != nil {
|
||||
debugMessage("frame handling error: %s", frameErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Connection) getStreamPriority(streamId spdy.StreamId) uint8 {
|
||||
stream, streamOk := s.getStream(streamId)
|
||||
if !streamOk {
|
||||
return 7
|
||||
}
|
||||
return stream.priority
|
||||
}
|
||||
|
||||
func (s *Connection) addStreamFrame(frame *spdy.SynStreamFrame) {
|
||||
var parent *Stream
|
||||
if frame.AssociatedToStreamId != spdy.StreamId(0) {
|
||||
parent, _ = s.getStream(frame.AssociatedToStreamId)
|
||||
}
|
||||
|
||||
stream := &Stream{
|
||||
streamId: frame.StreamId,
|
||||
parent: parent,
|
||||
conn: s,
|
||||
startChan: make(chan error),
|
||||
headers: frame.Headers,
|
||||
finished: (frame.CFHeader.Flags & spdy.ControlFlagUnidirectional) != 0x00,
|
||||
replyCond: sync.NewCond(new(sync.Mutex)),
|
||||
dataChan: make(chan []byte),
|
||||
headerChan: make(chan http.Header),
|
||||
closeChan: make(chan bool),
|
||||
priority: frame.Priority,
|
||||
}
|
||||
if frame.CFHeader.Flags&spdy.ControlFlagFin != 0x00 {
|
||||
stream.closeRemoteChannels()
|
||||
}
|
||||
|
||||
s.addStream(stream)
|
||||
}
|
||||
|
||||
// checkStreamFrame checks to see if a stream frame is allowed.
|
||||
// If the stream is invalid, then a reset frame with protocol error
|
||||
// will be returned.
|
||||
func (s *Connection) checkStreamFrame(frame *spdy.SynStreamFrame) bool {
|
||||
s.receiveIdLock.Lock()
|
||||
defer s.receiveIdLock.Unlock()
|
||||
if s.goneAway {
|
||||
return false
|
||||
}
|
||||
validationErr := s.validateStreamId(frame.StreamId)
|
||||
if validationErr != nil {
|
||||
go func() {
|
||||
resetErr := s.sendResetFrame(spdy.ProtocolError, frame.StreamId)
|
||||
if resetErr != nil {
|
||||
debugMessage("reset error: %s", resetErr)
|
||||
}
|
||||
}()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Connection) handleStreamFrame(frame *spdy.SynStreamFrame, newHandler StreamHandler) error {
|
||||
stream, ok := s.getStream(frame.StreamId)
|
||||
if !ok {
|
||||
return fmt.Errorf("Missing stream: %d", frame.StreamId)
|
||||
}
|
||||
|
||||
newHandler(stream)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Connection) handleReplyFrame(frame *spdy.SynReplyFrame) error {
|
||||
debugMessage("(%p) Reply frame received for %d", s, frame.StreamId)
|
||||
stream, streamOk := s.getStream(frame.StreamId)
|
||||
if !streamOk {
|
||||
debugMessage("Reply frame gone away for %d", frame.StreamId)
|
||||
// Stream has already gone away
|
||||
return nil
|
||||
}
|
||||
if stream.replied {
|
||||
// Stream has already received reply
|
||||
return nil
|
||||
}
|
||||
stream.replied = true
|
||||
|
||||
// TODO Check for error
|
||||
if (frame.CFHeader.Flags & spdy.ControlFlagFin) != 0x00 {
|
||||
s.remoteStreamFinish(stream)
|
||||
}
|
||||
|
||||
close(stream.startChan)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Connection) handleResetFrame(frame *spdy.RstStreamFrame) error {
|
||||
stream, streamOk := s.getStream(frame.StreamId)
|
||||
if !streamOk {
|
||||
// Stream has already been removed
|
||||
return nil
|
||||
}
|
||||
s.removeStream(stream)
|
||||
stream.closeRemoteChannels()
|
||||
|
||||
if !stream.replied {
|
||||
stream.replied = true
|
||||
stream.startChan <- ErrReset
|
||||
close(stream.startChan)
|
||||
}
|
||||
|
||||
stream.finishLock.Lock()
|
||||
stream.finished = true
|
||||
stream.finishLock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Connection) handleHeaderFrame(frame *spdy.HeadersFrame) error {
|
||||
stream, streamOk := s.getStream(frame.StreamId)
|
||||
if !streamOk {
|
||||
// Stream has already gone away
|
||||
return nil
|
||||
}
|
||||
if !stream.replied {
|
||||
// No reply received...Protocol error?
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO limit headers while not blocking (use buffered chan or goroutine?)
|
||||
select {
|
||||
case <-stream.closeChan:
|
||||
return nil
|
||||
case stream.headerChan <- frame.Headers:
|
||||
}
|
||||
|
||||
if (frame.CFHeader.Flags & spdy.ControlFlagFin) != 0x00 {
|
||||
s.remoteStreamFinish(stream)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Connection) handleDataFrame(frame *spdy.DataFrame) error {
|
||||
debugMessage("(%p) Data frame received for %d", s, frame.StreamId)
|
||||
stream, streamOk := s.getStream(frame.StreamId)
|
||||
if !streamOk {
|
||||
debugMessage("(%p) Data frame gone away for %d", s, frame.StreamId)
|
||||
// Stream has already gone away
|
||||
return nil
|
||||
}
|
||||
if !stream.replied {
|
||||
debugMessage("(%p) Data frame not replied %d", s, frame.StreamId)
|
||||
// No reply received...Protocol error?
|
||||
return nil
|
||||
}
|
||||
|
||||
debugMessage("(%p) (%d) Data frame handling", stream, stream.streamId)
|
||||
if len(frame.Data) > 0 {
|
||||
stream.dataLock.RLock()
|
||||
select {
|
||||
case <-stream.closeChan:
|
||||
debugMessage("(%p) (%d) Data frame not sent (stream shut down)", stream, stream.streamId)
|
||||
case stream.dataChan <- frame.Data:
|
||||
debugMessage("(%p) (%d) Data frame sent", stream, stream.streamId)
|
||||
}
|
||||
stream.dataLock.RUnlock()
|
||||
}
|
||||
if (frame.Flags & spdy.DataFlagFin) != 0x00 {
|
||||
s.remoteStreamFinish(stream)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Connection) handlePingFrame(frame *spdy.PingFrame) error {
|
||||
if s.pingId&0x01 != frame.Id&0x01 {
|
||||
return s.framer.WriteFrame(frame)
|
||||
}
|
||||
pingChan, pingOk := s.pingChans[frame.Id]
|
||||
if pingOk {
|
||||
close(pingChan)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Connection) handleGoAwayFrame(frame *spdy.GoAwayFrame) error {
|
||||
debugMessage("(%p) Go away received", s)
|
||||
s.receiveIdLock.Lock()
|
||||
if s.goneAway {
|
||||
s.receiveIdLock.Unlock()
|
||||
return nil
|
||||
}
|
||||
s.goneAway = true
|
||||
s.receiveIdLock.Unlock()
|
||||
|
||||
if s.lastStreamChan != nil {
|
||||
stream, _ := s.getStream(frame.LastGoodStreamId)
|
||||
go func() {
|
||||
s.lastStreamChan <- stream
|
||||
}()
|
||||
}
|
||||
|
||||
// Do not block frame handler waiting for closure
|
||||
go s.shutdown(s.goAwayTimeout)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Connection) remoteStreamFinish(stream *Stream) {
|
||||
stream.closeRemoteChannels()
|
||||
|
||||
stream.finishLock.Lock()
|
||||
if stream.finished {
|
||||
// Stream is fully closed, cleanup
|
||||
s.removeStream(stream)
|
||||
}
|
||||
stream.finishLock.Unlock()
|
||||
}
|
||||
|
||||
// CreateStream creates a new spdy stream using the parameters for
|
||||
// creating the stream frame. The stream frame will be sent upon
|
||||
// calling this function, however this function does not wait for
|
||||
// the reply frame. If waiting for the reply is desired, use
|
||||
// the stream Wait or WaitTimeout function on the stream returned
|
||||
// by this function.
|
||||
func (s *Connection) CreateStream(headers http.Header, parent *Stream, fin bool) (*Stream, error) {
|
||||
// MUST synchronize stream creation (all the way to writing the frame)
|
||||
// as stream IDs **MUST** increase monotonically.
|
||||
s.nextIdLock.Lock()
|
||||
defer s.nextIdLock.Unlock()
|
||||
|
||||
streamId := s.getNextStreamId()
|
||||
if streamId == 0 {
|
||||
return nil, fmt.Errorf("Unable to get new stream id")
|
||||
}
|
||||
|
||||
stream := &Stream{
|
||||
streamId: streamId,
|
||||
parent: parent,
|
||||
conn: s,
|
||||
startChan: make(chan error),
|
||||
headers: headers,
|
||||
dataChan: make(chan []byte),
|
||||
headerChan: make(chan http.Header),
|
||||
closeChan: make(chan bool),
|
||||
}
|
||||
|
||||
debugMessage("(%p) (%p) Create stream", s, stream)
|
||||
|
||||
s.addStream(stream)
|
||||
|
||||
return stream, s.sendStream(stream, fin)
|
||||
}
|
||||
|
||||
func (s *Connection) shutdown(closeTimeout time.Duration) {
|
||||
// TODO Ensure this isn't called multiple times
|
||||
s.shutdownLock.Lock()
|
||||
if s.hasShutdown {
|
||||
s.shutdownLock.Unlock()
|
||||
return
|
||||
}
|
||||
s.hasShutdown = true
|
||||
s.shutdownLock.Unlock()
|
||||
|
||||
var timeout <-chan time.Time
|
||||
if closeTimeout > time.Duration(0) {
|
||||
timeout = time.After(closeTimeout)
|
||||
}
|
||||
streamsClosed := make(chan bool)
|
||||
|
||||
go func() {
|
||||
s.streamCond.L.Lock()
|
||||
for len(s.streams) > 0 {
|
||||
debugMessage("Streams opened: %d, %#v", len(s.streams), s.streams)
|
||||
s.streamCond.Wait()
|
||||
}
|
||||
s.streamCond.L.Unlock()
|
||||
close(streamsClosed)
|
||||
}()
|
||||
|
||||
var err error
|
||||
select {
|
||||
case <-streamsClosed:
|
||||
// No active streams, close should be safe
|
||||
err = s.conn.Close()
|
||||
case <-timeout:
|
||||
// Force ungraceful close
|
||||
err = s.conn.Close()
|
||||
// Wait for cleanup to clear active streams
|
||||
<-streamsClosed
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
duration := 10 * time.Minute
|
||||
time.AfterFunc(duration, func() {
|
||||
select {
|
||||
case err, ok := <-s.shutdownChan:
|
||||
if ok {
|
||||
debugMessage("Unhandled close error after %s: %s", duration, err)
|
||||
}
|
||||
default:
|
||||
}
|
||||
})
|
||||
s.shutdownChan <- err
|
||||
}
|
||||
close(s.shutdownChan)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Closes spdy connection by sending GoAway frame and initiating shutdown
|
||||
func (s *Connection) Close() error {
|
||||
s.receiveIdLock.Lock()
|
||||
if s.goneAway {
|
||||
s.receiveIdLock.Unlock()
|
||||
return nil
|
||||
}
|
||||
s.goneAway = true
|
||||
s.receiveIdLock.Unlock()
|
||||
|
||||
var lastStreamId spdy.StreamId
|
||||
if s.receivedStreamId > 2 {
|
||||
lastStreamId = s.receivedStreamId - 2
|
||||
}
|
||||
|
||||
goAwayFrame := &spdy.GoAwayFrame{
|
||||
LastGoodStreamId: lastStreamId,
|
||||
Status: spdy.GoAwayOK,
|
||||
}
|
||||
|
||||
err := s.framer.WriteFrame(goAwayFrame)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go s.shutdown(s.closeTimeout)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseWait closes the connection and waits for shutdown
|
||||
// to finish. Note the underlying network Connection
|
||||
// is not closed until the end of shutdown.
|
||||
func (s *Connection) CloseWait() error {
|
||||
closeErr := s.Close()
|
||||
if closeErr != nil {
|
||||
return closeErr
|
||||
}
|
||||
shutdownErr, ok := <-s.shutdownChan
|
||||
if ok {
|
||||
return shutdownErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait waits for the connection to finish shutdown or for
|
||||
// the wait timeout duration to expire. This needs to be
|
||||
// called either after Close has been called or the GOAWAYFRAME
|
||||
// has been received. If the wait timeout is 0, this function
|
||||
// will block until shutdown finishes. If wait is never called
|
||||
// and a shutdown error occurs, that error will be logged as an
|
||||
// unhandled error.
|
||||
func (s *Connection) Wait(waitTimeout time.Duration) error {
|
||||
var timeout <-chan time.Time
|
||||
if waitTimeout > time.Duration(0) {
|
||||
timeout = time.After(waitTimeout)
|
||||
}
|
||||
|
||||
select {
|
||||
case err, ok := <-s.shutdownChan:
|
||||
if ok {
|
||||
return err
|
||||
}
|
||||
case <-timeout:
|
||||
return ErrTimeout
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotifyClose registers a channel to be called when the remote
|
||||
// peer inidicates connection closure. The last stream to be
|
||||
// received by the remote will be sent on the channel. The notify
|
||||
// timeout will determine the duration between go away received
|
||||
// and the connection being closed.
|
||||
func (s *Connection) NotifyClose(c chan<- *Stream, timeout time.Duration) {
|
||||
s.goAwayTimeout = timeout
|
||||
s.lastStreamChan = c
|
||||
}
|
||||
|
||||
// SetCloseTimeout sets the amount of time close will wait for
|
||||
// streams to finish before terminating the underlying network
|
||||
// connection. Setting the timeout to 0 will cause close to
|
||||
// wait forever, which is the default.
|
||||
func (s *Connection) SetCloseTimeout(timeout time.Duration) {
|
||||
s.closeTimeout = timeout
|
||||
}
|
||||
|
||||
// SetIdleTimeout sets the amount of time the connection may sit idle before
|
||||
// it is forcefully terminated.
|
||||
func (s *Connection) SetIdleTimeout(timeout time.Duration) {
|
||||
s.framer.setIdleTimeout(timeout)
|
||||
}
|
||||
|
||||
func (s *Connection) sendHeaders(headers http.Header, stream *Stream, fin bool) error {
|
||||
var flags spdy.ControlFlags
|
||||
if fin {
|
||||
flags = spdy.ControlFlagFin
|
||||
}
|
||||
|
||||
headerFrame := &spdy.HeadersFrame{
|
||||
StreamId: stream.streamId,
|
||||
Headers: headers,
|
||||
CFHeader: spdy.ControlFrameHeader{Flags: flags},
|
||||
}
|
||||
|
||||
return s.framer.WriteFrame(headerFrame)
|
||||
}
|
||||
|
||||
func (s *Connection) sendReply(headers http.Header, stream *Stream, fin bool) error {
|
||||
var flags spdy.ControlFlags
|
||||
if fin {
|
||||
flags = spdy.ControlFlagFin
|
||||
}
|
||||
|
||||
replyFrame := &spdy.SynReplyFrame{
|
||||
StreamId: stream.streamId,
|
||||
Headers: headers,
|
||||
CFHeader: spdy.ControlFrameHeader{Flags: flags},
|
||||
}
|
||||
|
||||
return s.framer.WriteFrame(replyFrame)
|
||||
}
|
||||
|
||||
func (s *Connection) sendResetFrame(status spdy.RstStreamStatus, streamId spdy.StreamId) error {
|
||||
resetFrame := &spdy.RstStreamFrame{
|
||||
StreamId: streamId,
|
||||
Status: status,
|
||||
}
|
||||
|
||||
return s.framer.WriteFrame(resetFrame)
|
||||
}
|
||||
|
||||
func (s *Connection) sendReset(status spdy.RstStreamStatus, stream *Stream) error {
|
||||
return s.sendResetFrame(status, stream.streamId)
|
||||
}
|
||||
|
||||
func (s *Connection) sendStream(stream *Stream, fin bool) error {
|
||||
var flags spdy.ControlFlags
|
||||
if fin {
|
||||
flags = spdy.ControlFlagFin
|
||||
stream.finished = true
|
||||
}
|
||||
|
||||
var parentId spdy.StreamId
|
||||
if stream.parent != nil {
|
||||
parentId = stream.parent.streamId
|
||||
}
|
||||
|
||||
streamFrame := &spdy.SynStreamFrame{
|
||||
StreamId: spdy.StreamId(stream.streamId),
|
||||
AssociatedToStreamId: spdy.StreamId(parentId),
|
||||
Headers: stream.headers,
|
||||
CFHeader: spdy.ControlFrameHeader{Flags: flags},
|
||||
}
|
||||
|
||||
return s.framer.WriteFrame(streamFrame)
|
||||
}
|
||||
|
||||
// getNextStreamId returns the next sequential id
|
||||
// every call should produce a unique value or an error
|
||||
func (s *Connection) getNextStreamId() spdy.StreamId {
|
||||
sid := s.nextStreamId
|
||||
if sid > 0x7fffffff {
|
||||
return 0
|
||||
}
|
||||
s.nextStreamId = s.nextStreamId + 2
|
||||
return sid
|
||||
}
|
||||
|
||||
// PeekNextStreamId returns the next sequential id and keeps the next id untouched
|
||||
func (s *Connection) PeekNextStreamId() spdy.StreamId {
|
||||
sid := s.nextStreamId
|
||||
return sid
|
||||
}
|
||||
|
||||
func (s *Connection) validateStreamId(rid spdy.StreamId) error {
|
||||
if rid > 0x7fffffff || rid < s.receivedStreamId {
|
||||
return ErrInvalidStreamId
|
||||
}
|
||||
s.receivedStreamId = rid + 2
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Connection) addStream(stream *Stream) {
|
||||
s.streamCond.L.Lock()
|
||||
s.streams[stream.streamId] = stream
|
||||
debugMessage("(%p) (%p) Stream added, broadcasting: %d", s, stream, stream.streamId)
|
||||
s.streamCond.Broadcast()
|
||||
s.streamCond.L.Unlock()
|
||||
}
|
||||
|
||||
func (s *Connection) removeStream(stream *Stream) {
|
||||
s.streamCond.L.Lock()
|
||||
delete(s.streams, stream.streamId)
|
||||
debugMessage("(%p) (%p) Stream removed, broadcasting: %d", s, stream, stream.streamId)
|
||||
s.streamCond.Broadcast()
|
||||
s.streamCond.L.Unlock()
|
||||
}
|
||||
|
||||
func (s *Connection) getStream(streamId spdy.StreamId) (stream *Stream, ok bool) {
|
||||
s.streamLock.RLock()
|
||||
stream, ok = s.streams[streamId]
|
||||
s.streamLock.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// FindStream looks up the given stream id and either waits for the
|
||||
// stream to be found or returns nil if the stream id is no longer
|
||||
// valid.
|
||||
func (s *Connection) FindStream(streamId uint32) *Stream {
|
||||
var stream *Stream
|
||||
var ok bool
|
||||
s.streamCond.L.Lock()
|
||||
stream, ok = s.streams[spdy.StreamId(streamId)]
|
||||
debugMessage("(%p) Found stream %d? %t", s, spdy.StreamId(streamId), ok)
|
||||
for !ok && streamId >= uint32(s.receivedStreamId) {
|
||||
s.streamCond.Wait()
|
||||
stream, ok = s.streams[spdy.StreamId(streamId)]
|
||||
}
|
||||
s.streamCond.L.Unlock()
|
||||
return stream
|
||||
}
|
||||
|
||||
func (s *Connection) CloseChan() <-chan bool {
|
||||
return s.closeChan
|
||||
}
|
||||
38
vendor/github.com/docker/spdystream/handlers.go
generated
vendored
Normal file
38
vendor/github.com/docker/spdystream/handlers.go
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package spdystream
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// MirrorStreamHandler mirrors all streams.
|
||||
func MirrorStreamHandler(stream *Stream) {
|
||||
replyErr := stream.SendReply(http.Header{}, false)
|
||||
if replyErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
io.Copy(stream, stream)
|
||||
stream.Close()
|
||||
}()
|
||||
go func() {
|
||||
for {
|
||||
header, receiveErr := stream.ReceiveHeader()
|
||||
if receiveErr != nil {
|
||||
return
|
||||
}
|
||||
sendErr := stream.SendHeader(header, false)
|
||||
if sendErr != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// NoopStreamHandler does nothing when stream connects, most
|
||||
// likely used with RejectAuthHandler which will not allow any
|
||||
// streams to make it to the stream handler.
|
||||
func NoOpStreamHandler(stream *Stream) {
|
||||
stream.SendReply(http.Header{}, false)
|
||||
}
|
||||
98
vendor/github.com/docker/spdystream/priority.go
generated
vendored
Normal file
98
vendor/github.com/docker/spdystream/priority.go
generated
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package spdystream
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/spdystream/spdy"
|
||||
)
|
||||
|
||||
type prioritizedFrame struct {
|
||||
frame spdy.Frame
|
||||
priority uint8
|
||||
insertId uint64
|
||||
}
|
||||
|
||||
type frameQueue []*prioritizedFrame
|
||||
|
||||
func (fq frameQueue) Len() int {
|
||||
return len(fq)
|
||||
}
|
||||
|
||||
func (fq frameQueue) Less(i, j int) bool {
|
||||
if fq[i].priority == fq[j].priority {
|
||||
return fq[i].insertId < fq[j].insertId
|
||||
}
|
||||
return fq[i].priority < fq[j].priority
|
||||
}
|
||||
|
||||
func (fq frameQueue) Swap(i, j int) {
|
||||
fq[i], fq[j] = fq[j], fq[i]
|
||||
}
|
||||
|
||||
func (fq *frameQueue) Push(x interface{}) {
|
||||
*fq = append(*fq, x.(*prioritizedFrame))
|
||||
}
|
||||
|
||||
func (fq *frameQueue) Pop() interface{} {
|
||||
old := *fq
|
||||
n := len(old)
|
||||
*fq = old[0 : n-1]
|
||||
return old[n-1]
|
||||
}
|
||||
|
||||
type PriorityFrameQueue struct {
|
||||
queue *frameQueue
|
||||
c *sync.Cond
|
||||
size int
|
||||
nextInsertId uint64
|
||||
drain bool
|
||||
}
|
||||
|
||||
func NewPriorityFrameQueue(size int) *PriorityFrameQueue {
|
||||
queue := make(frameQueue, 0, size)
|
||||
heap.Init(&queue)
|
||||
|
||||
return &PriorityFrameQueue{
|
||||
queue: &queue,
|
||||
size: size,
|
||||
c: sync.NewCond(&sync.Mutex{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (q *PriorityFrameQueue) Push(frame spdy.Frame, priority uint8) {
|
||||
q.c.L.Lock()
|
||||
defer q.c.L.Unlock()
|
||||
for q.queue.Len() >= q.size {
|
||||
q.c.Wait()
|
||||
}
|
||||
pFrame := &prioritizedFrame{
|
||||
frame: frame,
|
||||
priority: priority,
|
||||
insertId: q.nextInsertId,
|
||||
}
|
||||
q.nextInsertId = q.nextInsertId + 1
|
||||
heap.Push(q.queue, pFrame)
|
||||
q.c.Signal()
|
||||
}
|
||||
|
||||
func (q *PriorityFrameQueue) Pop() spdy.Frame {
|
||||
q.c.L.Lock()
|
||||
defer q.c.L.Unlock()
|
||||
for q.queue.Len() == 0 {
|
||||
if q.drain {
|
||||
return nil
|
||||
}
|
||||
q.c.Wait()
|
||||
}
|
||||
frame := heap.Pop(q.queue).(*prioritizedFrame).frame
|
||||
q.c.Signal()
|
||||
return frame
|
||||
}
|
||||
|
||||
func (q *PriorityFrameQueue) Drain() {
|
||||
q.c.L.Lock()
|
||||
defer q.c.L.Unlock()
|
||||
q.drain = true
|
||||
q.c.Broadcast()
|
||||
}
|
||||
108
vendor/github.com/docker/spdystream/priority_test.go
generated
vendored
Normal file
108
vendor/github.com/docker/spdystream/priority_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
package spdystream
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/spdystream/spdy"
|
||||
)
|
||||
|
||||
func TestPriorityQueueOrdering(t *testing.T) {
|
||||
queue := NewPriorityFrameQueue(150)
|
||||
data1 := &spdy.DataFrame{}
|
||||
data2 := &spdy.DataFrame{}
|
||||
data3 := &spdy.DataFrame{}
|
||||
data4 := &spdy.DataFrame{}
|
||||
queue.Push(data1, 2)
|
||||
queue.Push(data2, 1)
|
||||
queue.Push(data3, 1)
|
||||
queue.Push(data4, 0)
|
||||
|
||||
if queue.Pop() != data4 {
|
||||
t.Fatalf("Wrong order, expected data4 first")
|
||||
}
|
||||
if queue.Pop() != data2 {
|
||||
t.Fatalf("Wrong order, expected data2 second")
|
||||
}
|
||||
if queue.Pop() != data3 {
|
||||
t.Fatalf("Wrong order, expected data3 third")
|
||||
}
|
||||
if queue.Pop() != data1 {
|
||||
t.Fatalf("Wrong order, expected data1 fourth")
|
||||
}
|
||||
|
||||
// Insert 50 Medium priority frames
|
||||
for i := spdy.StreamId(50); i < 100; i++ {
|
||||
queue.Push(&spdy.DataFrame{StreamId: i}, 1)
|
||||
}
|
||||
// Insert 50 low priority frames
|
||||
for i := spdy.StreamId(100); i < 150; i++ {
|
||||
queue.Push(&spdy.DataFrame{StreamId: i}, 2)
|
||||
}
|
||||
// Insert 50 high priority frames
|
||||
for i := spdy.StreamId(0); i < 50; i++ {
|
||||
queue.Push(&spdy.DataFrame{StreamId: i}, 0)
|
||||
}
|
||||
|
||||
for i := spdy.StreamId(0); i < 150; i++ {
|
||||
frame := queue.Pop()
|
||||
if frame.(*spdy.DataFrame).StreamId != i {
|
||||
t.Fatalf("Wrong frame\nActual: %d\nExpecting: %d", frame.(*spdy.DataFrame).StreamId, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorityQueueSync(t *testing.T) {
|
||||
queue := NewPriorityFrameQueue(150)
|
||||
var wg sync.WaitGroup
|
||||
insertRange := func(start, stop spdy.StreamId, priority uint8) {
|
||||
for i := start; i < stop; i++ {
|
||||
queue.Push(&spdy.DataFrame{StreamId: i}, priority)
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
wg.Add(3)
|
||||
go insertRange(spdy.StreamId(100), spdy.StreamId(150), 2)
|
||||
go insertRange(spdy.StreamId(0), spdy.StreamId(50), 0)
|
||||
go insertRange(spdy.StreamId(50), spdy.StreamId(100), 1)
|
||||
|
||||
wg.Wait()
|
||||
for i := spdy.StreamId(0); i < 150; i++ {
|
||||
frame := queue.Pop()
|
||||
if frame.(*spdy.DataFrame).StreamId != i {
|
||||
t.Fatalf("Wrong frame\nActual: %d\nExpecting: %d", frame.(*spdy.DataFrame).StreamId, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorityQueueBlocking(t *testing.T) {
|
||||
queue := NewPriorityFrameQueue(15)
|
||||
for i := 0; i < 15; i++ {
|
||||
queue.Push(&spdy.DataFrame{}, 2)
|
||||
}
|
||||
doneChan := make(chan bool)
|
||||
go func() {
|
||||
queue.Push(&spdy.DataFrame{}, 2)
|
||||
close(doneChan)
|
||||
}()
|
||||
select {
|
||||
case <-doneChan:
|
||||
t.Fatalf("Push succeeded, expected to block")
|
||||
case <-time.After(time.Millisecond):
|
||||
break
|
||||
}
|
||||
|
||||
queue.Pop()
|
||||
|
||||
select {
|
||||
case <-doneChan:
|
||||
break
|
||||
case <-time.After(time.Millisecond):
|
||||
t.Fatalf("Push should have succeeded, but timeout reached")
|
||||
}
|
||||
|
||||
for i := 0; i < 15; i++ {
|
||||
queue.Pop()
|
||||
}
|
||||
}
|
||||
187
vendor/github.com/docker/spdystream/spdy/dictionary.go
generated
vendored
Normal file
187
vendor/github.com/docker/spdystream/spdy/dictionary.go
generated
vendored
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package spdy
|
||||
|
||||
// headerDictionary is the dictionary sent to the zlib compressor/decompressor.
|
||||
var headerDictionary = []byte{
|
||||
0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68,
|
||||
0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70,
|
||||
0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70,
|
||||
0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65,
|
||||
0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05,
|
||||
0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00,
|
||||
0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00,
|
||||
0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70,
|
||||
0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,
|
||||
0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63,
|
||||
0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f,
|
||||
0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f,
|
||||
0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c,
|
||||
0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00,
|
||||
0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70,
|
||||
0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73,
|
||||
0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00,
|
||||
0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77,
|
||||
0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68,
|
||||
0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63,
|
||||
0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72,
|
||||
0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f,
|
||||
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65,
|
||||
0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f,
|
||||
0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d,
|
||||
0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65,
|
||||
0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67,
|
||||
0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f,
|
||||
0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f,
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00,
|
||||
0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
|
||||
0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00,
|
||||
0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
|
||||
0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00,
|
||||
0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
|
||||
0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00,
|
||||
0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00,
|
||||
0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00,
|
||||
0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74,
|
||||
0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69,
|
||||
0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66,
|
||||
0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68,
|
||||
0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69,
|
||||
0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00,
|
||||
0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f,
|
||||
0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73,
|
||||
0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d,
|
||||
0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d,
|
||||
0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00,
|
||||
0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67,
|
||||
0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d,
|
||||
0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69,
|
||||
0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65,
|
||||
0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74,
|
||||
0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65,
|
||||
0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00,
|
||||
0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72,
|
||||
0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00,
|
||||
0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00,
|
||||
0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79,
|
||||
0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74,
|
||||
0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00,
|
||||
0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61,
|
||||
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05,
|
||||
0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00,
|
||||
0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72,
|
||||
0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72,
|
||||
0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00,
|
||||
0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65,
|
||||
0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00,
|
||||
0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c,
|
||||
0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72,
|
||||
0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65,
|
||||
0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00,
|
||||
0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61,
|
||||
0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73,
|
||||
0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79,
|
||||
0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00,
|
||||
0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69,
|
||||
0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77,
|
||||
0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e,
|
||||
0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00,
|
||||
0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64,
|
||||
0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00,
|
||||
0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
|
||||
0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30,
|
||||
0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76,
|
||||
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00,
|
||||
0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31,
|
||||
0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72,
|
||||
0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62,
|
||||
0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73,
|
||||
0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69,
|
||||
0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65,
|
||||
0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00,
|
||||
0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69,
|
||||
0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32,
|
||||
0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35,
|
||||
0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30,
|
||||
0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33,
|
||||
0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37,
|
||||
0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30,
|
||||
0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34,
|
||||
0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31,
|
||||
0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31,
|
||||
0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34,
|
||||
0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34,
|
||||
0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e,
|
||||
0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f,
|
||||
0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65,
|
||||
0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20,
|
||||
0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65,
|
||||
0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f,
|
||||
0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d,
|
||||
0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34,
|
||||
0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30,
|
||||
0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68,
|
||||
0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30,
|
||||
0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64,
|
||||
0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e,
|
||||
0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64,
|
||||
0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65,
|
||||
0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72,
|
||||
0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f,
|
||||
0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74,
|
||||
0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65,
|
||||
0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20,
|
||||
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20,
|
||||
0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61,
|
||||
0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46,
|
||||
0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41,
|
||||
0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a,
|
||||
0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41,
|
||||
0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20,
|
||||
0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20,
|
||||
0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30,
|
||||
0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e,
|
||||
0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57,
|
||||
0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c,
|
||||
0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61,
|
||||
0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20,
|
||||
0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b,
|
||||
0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f,
|
||||
0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61,
|
||||
0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69,
|
||||
0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67,
|
||||
0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67,
|
||||
0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69,
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78,
|
||||
0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69,
|
||||
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78,
|
||||
0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c,
|
||||
0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c,
|
||||
0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74,
|
||||
0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c,
|
||||
0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74,
|
||||
0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65,
|
||||
0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65,
|
||||
0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64,
|
||||
0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,
|
||||
0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63,
|
||||
0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69,
|
||||
0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d,
|
||||
0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a,
|
||||
0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e,
|
||||
}
|
||||
348
vendor/github.com/docker/spdystream/spdy/read.go
generated
vendored
Normal file
348
vendor/github.com/docker/spdystream/spdy/read.go
generated
vendored
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package spdy
|
||||
|
||||
import (
|
||||
"compress/zlib"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (frame *SynStreamFrame) read(h ControlFrameHeader, f *Framer) error {
|
||||
return f.readSynStreamFrame(h, frame)
|
||||
}
|
||||
|
||||
func (frame *SynReplyFrame) read(h ControlFrameHeader, f *Framer) error {
|
||||
return f.readSynReplyFrame(h, frame)
|
||||
}
|
||||
|
||||
func (frame *RstStreamFrame) read(h ControlFrameHeader, f *Framer) error {
|
||||
frame.CFHeader = h
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
if frame.Status == 0 {
|
||||
return &Error{InvalidControlFrame, frame.StreamId}
|
||||
}
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (frame *SettingsFrame) read(h ControlFrameHeader, f *Framer) error {
|
||||
frame.CFHeader = h
|
||||
var numSettings uint32
|
||||
if err := binary.Read(f.r, binary.BigEndian, &numSettings); err != nil {
|
||||
return err
|
||||
}
|
||||
frame.FlagIdValues = make([]SettingsFlagIdValue, numSettings)
|
||||
for i := uint32(0); i < numSettings; i++ {
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.FlagIdValues[i].Id); err != nil {
|
||||
return err
|
||||
}
|
||||
frame.FlagIdValues[i].Flag = SettingsFlag((frame.FlagIdValues[i].Id & 0xff000000) >> 24)
|
||||
frame.FlagIdValues[i].Id &= 0xffffff
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.FlagIdValues[i].Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (frame *PingFrame) read(h ControlFrameHeader, f *Framer) error {
|
||||
frame.CFHeader = h
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
if frame.Id == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
if frame.CFHeader.Flags != 0 {
|
||||
return &Error{InvalidControlFrame, StreamId(frame.Id)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (frame *GoAwayFrame) read(h ControlFrameHeader, f *Framer) error {
|
||||
frame.CFHeader = h
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.LastGoodStreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
if frame.CFHeader.Flags != 0 {
|
||||
return &Error{InvalidControlFrame, frame.LastGoodStreamId}
|
||||
}
|
||||
if frame.CFHeader.length != 8 {
|
||||
return &Error{InvalidControlFrame, frame.LastGoodStreamId}
|
||||
}
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (frame *HeadersFrame) read(h ControlFrameHeader, f *Framer) error {
|
||||
return f.readHeadersFrame(h, frame)
|
||||
}
|
||||
|
||||
func (frame *WindowUpdateFrame) read(h ControlFrameHeader, f *Framer) error {
|
||||
frame.CFHeader = h
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
if frame.CFHeader.Flags != 0 {
|
||||
return &Error{InvalidControlFrame, frame.StreamId}
|
||||
}
|
||||
if frame.CFHeader.length != 8 {
|
||||
return &Error{InvalidControlFrame, frame.StreamId}
|
||||
}
|
||||
if err := binary.Read(f.r, binary.BigEndian, &frame.DeltaWindowSize); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newControlFrame(frameType ControlFrameType) (controlFrame, error) {
|
||||
ctor, ok := cframeCtor[frameType]
|
||||
if !ok {
|
||||
return nil, &Error{Err: InvalidControlFrame}
|
||||
}
|
||||
return ctor(), nil
|
||||
}
|
||||
|
||||
var cframeCtor = map[ControlFrameType]func() controlFrame{
|
||||
TypeSynStream: func() controlFrame { return new(SynStreamFrame) },
|
||||
TypeSynReply: func() controlFrame { return new(SynReplyFrame) },
|
||||
TypeRstStream: func() controlFrame { return new(RstStreamFrame) },
|
||||
TypeSettings: func() controlFrame { return new(SettingsFrame) },
|
||||
TypePing: func() controlFrame { return new(PingFrame) },
|
||||
TypeGoAway: func() controlFrame { return new(GoAwayFrame) },
|
||||
TypeHeaders: func() controlFrame { return new(HeadersFrame) },
|
||||
TypeWindowUpdate: func() controlFrame { return new(WindowUpdateFrame) },
|
||||
}
|
||||
|
||||
func (f *Framer) uncorkHeaderDecompressor(payloadSize int64) error {
|
||||
if f.headerDecompressor != nil {
|
||||
f.headerReader.N = payloadSize
|
||||
return nil
|
||||
}
|
||||
f.headerReader = io.LimitedReader{R: f.r, N: payloadSize}
|
||||
decompressor, err := zlib.NewReaderDict(&f.headerReader, []byte(headerDictionary))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.headerDecompressor = decompressor
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadFrame reads SPDY encoded data and returns a decompressed Frame.
|
||||
func (f *Framer) ReadFrame() (Frame, error) {
|
||||
var firstWord uint32
|
||||
if err := binary.Read(f.r, binary.BigEndian, &firstWord); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if firstWord&0x80000000 != 0 {
|
||||
frameType := ControlFrameType(firstWord & 0xffff)
|
||||
version := uint16(firstWord >> 16 & 0x7fff)
|
||||
return f.parseControlFrame(version, frameType)
|
||||
}
|
||||
return f.parseDataFrame(StreamId(firstWord & 0x7fffffff))
|
||||
}
|
||||
|
||||
func (f *Framer) parseControlFrame(version uint16, frameType ControlFrameType) (Frame, error) {
|
||||
var length uint32
|
||||
if err := binary.Read(f.r, binary.BigEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
flags := ControlFlags((length & 0xff000000) >> 24)
|
||||
length &= 0xffffff
|
||||
header := ControlFrameHeader{version, frameType, flags, length}
|
||||
cframe, err := newControlFrame(frameType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = cframe.read(header, f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cframe, nil
|
||||
}
|
||||
|
||||
func parseHeaderValueBlock(r io.Reader, streamId StreamId) (http.Header, error) {
|
||||
var numHeaders uint32
|
||||
if err := binary.Read(r, binary.BigEndian, &numHeaders); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var e error
|
||||
h := make(http.Header, int(numHeaders))
|
||||
for i := 0; i < int(numHeaders); i++ {
|
||||
var length uint32
|
||||
if err := binary.Read(r, binary.BigEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nameBytes := make([]byte, length)
|
||||
if _, err := io.ReadFull(r, nameBytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := string(nameBytes)
|
||||
if name != strings.ToLower(name) {
|
||||
e = &Error{UnlowercasedHeaderName, streamId}
|
||||
name = strings.ToLower(name)
|
||||
}
|
||||
if h[name] != nil {
|
||||
e = &Error{DuplicateHeaders, streamId}
|
||||
}
|
||||
if err := binary.Read(r, binary.BigEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value := make([]byte, length)
|
||||
if _, err := io.ReadFull(r, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
valueList := strings.Split(string(value), headerValueSeparator)
|
||||
for _, v := range valueList {
|
||||
h.Add(name, v)
|
||||
}
|
||||
}
|
||||
if e != nil {
|
||||
return h, e
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (f *Framer) readSynStreamFrame(h ControlFrameHeader, frame *SynStreamFrame) error {
|
||||
frame.CFHeader = h
|
||||
var err error
|
||||
if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = binary.Read(f.r, binary.BigEndian, &frame.AssociatedToStreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = binary.Read(f.r, binary.BigEndian, &frame.Priority); err != nil {
|
||||
return err
|
||||
}
|
||||
frame.Priority >>= 5
|
||||
if err = binary.Read(f.r, binary.BigEndian, &frame.Slot); err != nil {
|
||||
return err
|
||||
}
|
||||
reader := f.r
|
||||
if !f.headerCompressionDisabled {
|
||||
err := f.uncorkHeaderDecompressor(int64(h.length - 10))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reader = f.headerDecompressor
|
||||
}
|
||||
frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
|
||||
if !f.headerCompressionDisabled && (err == io.EOF && f.headerReader.N == 0 || f.headerReader.N != 0) {
|
||||
err = &Error{WrongCompressedPayloadSize, 0}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for h := range frame.Headers {
|
||||
if invalidReqHeaders[h] {
|
||||
return &Error{InvalidHeaderPresent, frame.StreamId}
|
||||
}
|
||||
}
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Framer) readSynReplyFrame(h ControlFrameHeader, frame *SynReplyFrame) error {
|
||||
frame.CFHeader = h
|
||||
var err error
|
||||
if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
reader := f.r
|
||||
if !f.headerCompressionDisabled {
|
||||
err := f.uncorkHeaderDecompressor(int64(h.length - 4))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reader = f.headerDecompressor
|
||||
}
|
||||
frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
|
||||
if !f.headerCompressionDisabled && (err == io.EOF && f.headerReader.N == 0 || f.headerReader.N != 0) {
|
||||
err = &Error{WrongCompressedPayloadSize, 0}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for h := range frame.Headers {
|
||||
if invalidRespHeaders[h] {
|
||||
return &Error{InvalidHeaderPresent, frame.StreamId}
|
||||
}
|
||||
}
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Framer) readHeadersFrame(h ControlFrameHeader, frame *HeadersFrame) error {
|
||||
frame.CFHeader = h
|
||||
var err error
|
||||
if err = binary.Read(f.r, binary.BigEndian, &frame.StreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
reader := f.r
|
||||
if !f.headerCompressionDisabled {
|
||||
err := f.uncorkHeaderDecompressor(int64(h.length - 4))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reader = f.headerDecompressor
|
||||
}
|
||||
frame.Headers, err = parseHeaderValueBlock(reader, frame.StreamId)
|
||||
if !f.headerCompressionDisabled && (err == io.EOF && f.headerReader.N == 0 || f.headerReader.N != 0) {
|
||||
err = &Error{WrongCompressedPayloadSize, 0}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var invalidHeaders map[string]bool
|
||||
if frame.StreamId%2 == 0 {
|
||||
invalidHeaders = invalidReqHeaders
|
||||
} else {
|
||||
invalidHeaders = invalidRespHeaders
|
||||
}
|
||||
for h := range frame.Headers {
|
||||
if invalidHeaders[h] {
|
||||
return &Error{InvalidHeaderPresent, frame.StreamId}
|
||||
}
|
||||
}
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Framer) parseDataFrame(streamId StreamId) (*DataFrame, error) {
|
||||
var length uint32
|
||||
if err := binary.Read(f.r, binary.BigEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var frame DataFrame
|
||||
frame.StreamId = streamId
|
||||
frame.Flags = DataFlags(length >> 24)
|
||||
length &= 0xffffff
|
||||
frame.Data = make([]byte, length)
|
||||
if _, err := io.ReadFull(f.r, frame.Data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if frame.StreamId == 0 {
|
||||
return nil, &Error{ZeroStreamId, 0}
|
||||
}
|
||||
return &frame, nil
|
||||
}
|
||||
644
vendor/github.com/docker/spdystream/spdy/spdy_test.go
generated
vendored
Normal file
644
vendor/github.com/docker/spdystream/spdy/spdy_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,644 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package spdy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var HeadersFixture = http.Header{
|
||||
"Url": []string{"http://www.google.com/"},
|
||||
"Method": []string{"get"},
|
||||
"Version": []string{"http/1.1"},
|
||||
}
|
||||
|
||||
func TestHeaderParsing(t *testing.T) {
|
||||
var headerValueBlockBuf bytes.Buffer
|
||||
writeHeaderValueBlock(&headerValueBlockBuf, HeadersFixture)
|
||||
const bogusStreamId = 1
|
||||
newHeaders, err := parseHeaderValueBlock(&headerValueBlockBuf, bogusStreamId)
|
||||
if err != nil {
|
||||
t.Fatal("parseHeaderValueBlock:", err)
|
||||
}
|
||||
if !reflect.DeepEqual(HeadersFixture, newHeaders) {
|
||||
t.Fatal("got: ", newHeaders, "\nwant: ", HeadersFixture)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateParseSynStreamFrameCompressionDisable(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
// Fixture framer for no compression test.
|
||||
framer := &Framer{
|
||||
headerCompressionDisabled: true,
|
||||
w: buffer,
|
||||
headerBuf: new(bytes.Buffer),
|
||||
r: buffer,
|
||||
}
|
||||
synStreamFrame := SynStreamFrame{
|
||||
CFHeader: ControlFrameHeader{
|
||||
version: Version,
|
||||
frameType: TypeSynStream,
|
||||
},
|
||||
StreamId: 2,
|
||||
Headers: HeadersFixture,
|
||||
}
|
||||
if err := framer.WriteFrame(&synStreamFrame); err != nil {
|
||||
t.Fatal("WriteFrame without compression:", err)
|
||||
}
|
||||
frame, err := framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatal("ReadFrame without compression:", err)
|
||||
}
|
||||
parsedSynStreamFrame, ok := frame.(*SynStreamFrame)
|
||||
if !ok {
|
||||
t.Fatal("Parsed incorrect frame type:", frame)
|
||||
}
|
||||
if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
|
||||
t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateParseSynStreamFrameCompressionEnable(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
framer, err := NewFramer(buffer, buffer)
|
||||
synStreamFrame := SynStreamFrame{
|
||||
CFHeader: ControlFrameHeader{
|
||||
version: Version,
|
||||
frameType: TypeSynStream,
|
||||
},
|
||||
StreamId: 2,
|
||||
Headers: HeadersFixture,
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create new framer:", err)
|
||||
}
|
||||
if err := framer.WriteFrame(&synStreamFrame); err != nil {
|
||||
t.Fatal("WriteFrame with compression:", err)
|
||||
}
|
||||
frame, err := framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatal("ReadFrame with compression:", err)
|
||||
}
|
||||
parsedSynStreamFrame, ok := frame.(*SynStreamFrame)
|
||||
if !ok {
|
||||
t.Fatal("Parsed incorrect frame type:", frame)
|
||||
}
|
||||
if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
|
||||
t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateParseSynReplyFrameCompressionDisable(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
framer := &Framer{
|
||||
headerCompressionDisabled: true,
|
||||
w: buffer,
|
||||
headerBuf: new(bytes.Buffer),
|
||||
r: buffer,
|
||||
}
|
||||
synReplyFrame := SynReplyFrame{
|
||||
CFHeader: ControlFrameHeader{
|
||||
version: Version,
|
||||
frameType: TypeSynReply,
|
||||
},
|
||||
StreamId: 2,
|
||||
Headers: HeadersFixture,
|
||||
}
|
||||
if err := framer.WriteFrame(&synReplyFrame); err != nil {
|
||||
t.Fatal("WriteFrame without compression:", err)
|
||||
}
|
||||
frame, err := framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatal("ReadFrame without compression:", err)
|
||||
}
|
||||
parsedSynReplyFrame, ok := frame.(*SynReplyFrame)
|
||||
if !ok {
|
||||
t.Fatal("Parsed incorrect frame type:", frame)
|
||||
}
|
||||
if !reflect.DeepEqual(synReplyFrame, *parsedSynReplyFrame) {
|
||||
t.Fatal("got: ", *parsedSynReplyFrame, "\nwant: ", synReplyFrame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateParseSynReplyFrameCompressionEnable(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
framer, err := NewFramer(buffer, buffer)
|
||||
synReplyFrame := SynReplyFrame{
|
||||
CFHeader: ControlFrameHeader{
|
||||
version: Version,
|
||||
frameType: TypeSynReply,
|
||||
},
|
||||
StreamId: 2,
|
||||
Headers: HeadersFixture,
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create new framer:", err)
|
||||
}
|
||||
if err := framer.WriteFrame(&synReplyFrame); err != nil {
|
||||
t.Fatal("WriteFrame with compression:", err)
|
||||
}
|
||||
frame, err := framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatal("ReadFrame with compression:", err)
|
||||
}
|
||||
parsedSynReplyFrame, ok := frame.(*SynReplyFrame)
|
||||
if !ok {
|
||||
t.Fatal("Parsed incorrect frame type:", frame)
|
||||
}
|
||||
if !reflect.DeepEqual(synReplyFrame, *parsedSynReplyFrame) {
|
||||
t.Fatal("got: ", *parsedSynReplyFrame, "\nwant: ", synReplyFrame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateParseRstStream(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
framer, err := NewFramer(buffer, buffer)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create new framer:", err)
|
||||
}
|
||||
rstStreamFrame := RstStreamFrame{
|
||||
CFHeader: ControlFrameHeader{
|
||||
version: Version,
|
||||
frameType: TypeRstStream,
|
||||
},
|
||||
StreamId: 1,
|
||||
Status: InvalidStream,
|
||||
}
|
||||
if err := framer.WriteFrame(&rstStreamFrame); err != nil {
|
||||
t.Fatal("WriteFrame:", err)
|
||||
}
|
||||
frame, err := framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatal("ReadFrame:", err)
|
||||
}
|
||||
parsedRstStreamFrame, ok := frame.(*RstStreamFrame)
|
||||
if !ok {
|
||||
t.Fatal("Parsed incorrect frame type:", frame)
|
||||
}
|
||||
if !reflect.DeepEqual(rstStreamFrame, *parsedRstStreamFrame) {
|
||||
t.Fatal("got: ", *parsedRstStreamFrame, "\nwant: ", rstStreamFrame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateParseSettings(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
framer, err := NewFramer(buffer, buffer)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create new framer:", err)
|
||||
}
|
||||
settingsFrame := SettingsFrame{
|
||||
CFHeader: ControlFrameHeader{
|
||||
version: Version,
|
||||
frameType: TypeSettings,
|
||||
},
|
||||
FlagIdValues: []SettingsFlagIdValue{
|
||||
{FlagSettingsPersistValue, SettingsCurrentCwnd, 10},
|
||||
{FlagSettingsPersisted, SettingsUploadBandwidth, 1},
|
||||
},
|
||||
}
|
||||
if err := framer.WriteFrame(&settingsFrame); err != nil {
|
||||
t.Fatal("WriteFrame:", err)
|
||||
}
|
||||
frame, err := framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatal("ReadFrame:", err)
|
||||
}
|
||||
parsedSettingsFrame, ok := frame.(*SettingsFrame)
|
||||
if !ok {
|
||||
t.Fatal("Parsed incorrect frame type:", frame)
|
||||
}
|
||||
if !reflect.DeepEqual(settingsFrame, *parsedSettingsFrame) {
|
||||
t.Fatal("got: ", *parsedSettingsFrame, "\nwant: ", settingsFrame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateParsePing(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
framer, err := NewFramer(buffer, buffer)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create new framer:", err)
|
||||
}
|
||||
pingFrame := PingFrame{
|
||||
CFHeader: ControlFrameHeader{
|
||||
version: Version,
|
||||
frameType: TypePing,
|
||||
},
|
||||
Id: 31337,
|
||||
}
|
||||
if err := framer.WriteFrame(&pingFrame); err != nil {
|
||||
t.Fatal("WriteFrame:", err)
|
||||
}
|
||||
if pingFrame.CFHeader.Flags != 0 {
|
||||
t.Fatal("Incorrect frame type:", pingFrame)
|
||||
}
|
||||
frame, err := framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatal("ReadFrame:", err)
|
||||
}
|
||||
parsedPingFrame, ok := frame.(*PingFrame)
|
||||
if !ok {
|
||||
t.Fatal("Parsed incorrect frame type:", frame)
|
||||
}
|
||||
if parsedPingFrame.CFHeader.Flags != 0 {
|
||||
t.Fatal("Parsed incorrect frame type:", parsedPingFrame)
|
||||
}
|
||||
if !reflect.DeepEqual(pingFrame, *parsedPingFrame) {
|
||||
t.Fatal("got: ", *parsedPingFrame, "\nwant: ", pingFrame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateParseGoAway(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
framer, err := NewFramer(buffer, buffer)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create new framer:", err)
|
||||
}
|
||||
goAwayFrame := GoAwayFrame{
|
||||
CFHeader: ControlFrameHeader{
|
||||
version: Version,
|
||||
frameType: TypeGoAway,
|
||||
},
|
||||
LastGoodStreamId: 31337,
|
||||
Status: 1,
|
||||
}
|
||||
if err := framer.WriteFrame(&goAwayFrame); err != nil {
|
||||
t.Fatal("WriteFrame:", err)
|
||||
}
|
||||
if goAwayFrame.CFHeader.Flags != 0 {
|
||||
t.Fatal("Incorrect frame type:", goAwayFrame)
|
||||
}
|
||||
if goAwayFrame.CFHeader.length != 8 {
|
||||
t.Fatal("Incorrect frame type:", goAwayFrame)
|
||||
}
|
||||
frame, err := framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatal("ReadFrame:", err)
|
||||
}
|
||||
parsedGoAwayFrame, ok := frame.(*GoAwayFrame)
|
||||
if !ok {
|
||||
t.Fatal("Parsed incorrect frame type:", frame)
|
||||
}
|
||||
if parsedGoAwayFrame.CFHeader.Flags != 0 {
|
||||
t.Fatal("Incorrect frame type:", parsedGoAwayFrame)
|
||||
}
|
||||
if parsedGoAwayFrame.CFHeader.length != 8 {
|
||||
t.Fatal("Incorrect frame type:", parsedGoAwayFrame)
|
||||
}
|
||||
if !reflect.DeepEqual(goAwayFrame, *parsedGoAwayFrame) {
|
||||
t.Fatal("got: ", *parsedGoAwayFrame, "\nwant: ", goAwayFrame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateParseHeadersFrame(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
framer := &Framer{
|
||||
headerCompressionDisabled: true,
|
||||
w: buffer,
|
||||
headerBuf: new(bytes.Buffer),
|
||||
r: buffer,
|
||||
}
|
||||
headersFrame := HeadersFrame{
|
||||
CFHeader: ControlFrameHeader{
|
||||
version: Version,
|
||||
frameType: TypeHeaders,
|
||||
},
|
||||
StreamId: 2,
|
||||
}
|
||||
headersFrame.Headers = HeadersFixture
|
||||
if err := framer.WriteFrame(&headersFrame); err != nil {
|
||||
t.Fatal("WriteFrame without compression:", err)
|
||||
}
|
||||
frame, err := framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatal("ReadFrame without compression:", err)
|
||||
}
|
||||
parsedHeadersFrame, ok := frame.(*HeadersFrame)
|
||||
if !ok {
|
||||
t.Fatal("Parsed incorrect frame type:", frame)
|
||||
}
|
||||
if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
|
||||
t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateParseHeadersFrameCompressionEnable(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
headersFrame := HeadersFrame{
|
||||
CFHeader: ControlFrameHeader{
|
||||
version: Version,
|
||||
frameType: TypeHeaders,
|
||||
},
|
||||
StreamId: 2,
|
||||
}
|
||||
headersFrame.Headers = HeadersFixture
|
||||
|
||||
framer, err := NewFramer(buffer, buffer)
|
||||
if err := framer.WriteFrame(&headersFrame); err != nil {
|
||||
t.Fatal("WriteFrame with compression:", err)
|
||||
}
|
||||
frame, err := framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatal("ReadFrame with compression:", err)
|
||||
}
|
||||
parsedHeadersFrame, ok := frame.(*HeadersFrame)
|
||||
if !ok {
|
||||
t.Fatal("Parsed incorrect frame type:", frame)
|
||||
}
|
||||
if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
|
||||
t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateParseWindowUpdateFrame(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
framer, err := NewFramer(buffer, buffer)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create new framer:", err)
|
||||
}
|
||||
windowUpdateFrame := WindowUpdateFrame{
|
||||
CFHeader: ControlFrameHeader{
|
||||
version: Version,
|
||||
frameType: TypeWindowUpdate,
|
||||
},
|
||||
StreamId: 31337,
|
||||
DeltaWindowSize: 1,
|
||||
}
|
||||
if err := framer.WriteFrame(&windowUpdateFrame); err != nil {
|
||||
t.Fatal("WriteFrame:", err)
|
||||
}
|
||||
if windowUpdateFrame.CFHeader.Flags != 0 {
|
||||
t.Fatal("Incorrect frame type:", windowUpdateFrame)
|
||||
}
|
||||
if windowUpdateFrame.CFHeader.length != 8 {
|
||||
t.Fatal("Incorrect frame type:", windowUpdateFrame)
|
||||
}
|
||||
frame, err := framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatal("ReadFrame:", err)
|
||||
}
|
||||
parsedWindowUpdateFrame, ok := frame.(*WindowUpdateFrame)
|
||||
if !ok {
|
||||
t.Fatal("Parsed incorrect frame type:", frame)
|
||||
}
|
||||
if parsedWindowUpdateFrame.CFHeader.Flags != 0 {
|
||||
t.Fatal("Incorrect frame type:", parsedWindowUpdateFrame)
|
||||
}
|
||||
if parsedWindowUpdateFrame.CFHeader.length != 8 {
|
||||
t.Fatal("Incorrect frame type:", parsedWindowUpdateFrame)
|
||||
}
|
||||
if !reflect.DeepEqual(windowUpdateFrame, *parsedWindowUpdateFrame) {
|
||||
t.Fatal("got: ", *parsedWindowUpdateFrame, "\nwant: ", windowUpdateFrame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateParseDataFrame(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
framer, err := NewFramer(buffer, buffer)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create new framer:", err)
|
||||
}
|
||||
dataFrame := DataFrame{
|
||||
StreamId: 1,
|
||||
Data: []byte{'h', 'e', 'l', 'l', 'o'},
|
||||
}
|
||||
if err := framer.WriteFrame(&dataFrame); err != nil {
|
||||
t.Fatal("WriteFrame:", err)
|
||||
}
|
||||
frame, err := framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatal("ReadFrame:", err)
|
||||
}
|
||||
parsedDataFrame, ok := frame.(*DataFrame)
|
||||
if !ok {
|
||||
t.Fatal("Parsed incorrect frame type:", frame)
|
||||
}
|
||||
if !reflect.DeepEqual(dataFrame, *parsedDataFrame) {
|
||||
t.Fatal("got: ", *parsedDataFrame, "\nwant: ", dataFrame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompressionContextAcrossFrames(t *testing.T) {
|
||||
buffer := new(bytes.Buffer)
|
||||
framer, err := NewFramer(buffer, buffer)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create new framer:", err)
|
||||
}
|
||||
headersFrame := HeadersFrame{
|
||||
CFHeader: ControlFrameHeader{
|
||||
version: Version,
|
||||
frameType: TypeHeaders,
|
||||
},
|
||||
StreamId: 2,
|
||||
Headers: HeadersFixture,
|
||||
}
|
||||
if err := framer.WriteFrame(&headersFrame); err != nil {
|
||||
t.Fatal("WriteFrame (HEADERS):", err)
|
||||
}
|
||||
synStreamFrame := SynStreamFrame{
|
||||
ControlFrameHeader{
|
||||
Version,
|
||||
TypeSynStream,
|
||||
0, // Flags
|
||||
0, // length
|
||||
},
|
||||
2, // StreamId
|
||||
0, // AssociatedTOStreamID
|
||||
0, // Priority
|
||||
1, // Slot
|
||||
nil, // Headers
|
||||
}
|
||||
synStreamFrame.Headers = HeadersFixture
|
||||
|
||||
if err := framer.WriteFrame(&synStreamFrame); err != nil {
|
||||
t.Fatal("WriteFrame (SYN_STREAM):", err)
|
||||
}
|
||||
frame, err := framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatal("ReadFrame (HEADERS):", err, buffer.Bytes())
|
||||
}
|
||||
parsedHeadersFrame, ok := frame.(*HeadersFrame)
|
||||
if !ok {
|
||||
t.Fatalf("expected HeadersFrame; got %T %v", frame, frame)
|
||||
}
|
||||
if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
|
||||
t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
|
||||
}
|
||||
frame, err = framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatal("ReadFrame (SYN_STREAM):", err, buffer.Bytes())
|
||||
}
|
||||
parsedSynStreamFrame, ok := frame.(*SynStreamFrame)
|
||||
if !ok {
|
||||
t.Fatalf("expected SynStreamFrame; got %T %v", frame, frame)
|
||||
}
|
||||
if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
|
||||
t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleSPDYFrames(t *testing.T) {
|
||||
// Initialize the framers.
|
||||
pr1, pw1 := io.Pipe()
|
||||
pr2, pw2 := io.Pipe()
|
||||
writer, err := NewFramer(pw1, pr2)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create writer:", err)
|
||||
}
|
||||
reader, err := NewFramer(pw2, pr1)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create reader:", err)
|
||||
}
|
||||
|
||||
// Set up the frames we're actually transferring.
|
||||
headersFrame := HeadersFrame{
|
||||
CFHeader: ControlFrameHeader{
|
||||
version: Version,
|
||||
frameType: TypeHeaders,
|
||||
},
|
||||
StreamId: 2,
|
||||
Headers: HeadersFixture,
|
||||
}
|
||||
synStreamFrame := SynStreamFrame{
|
||||
CFHeader: ControlFrameHeader{
|
||||
version: Version,
|
||||
frameType: TypeSynStream,
|
||||
},
|
||||
StreamId: 2,
|
||||
Headers: HeadersFixture,
|
||||
}
|
||||
|
||||
// Start the goroutines to write the frames.
|
||||
go func() {
|
||||
if err := writer.WriteFrame(&headersFrame); err != nil {
|
||||
t.Fatal("WriteFrame (HEADERS): ", err)
|
||||
}
|
||||
if err := writer.WriteFrame(&synStreamFrame); err != nil {
|
||||
t.Fatal("WriteFrame (SYN_STREAM): ", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Read the frames and verify they look as expected.
|
||||
frame, err := reader.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatal("ReadFrame (HEADERS): ", err)
|
||||
}
|
||||
parsedHeadersFrame, ok := frame.(*HeadersFrame)
|
||||
if !ok {
|
||||
t.Fatal("Parsed incorrect frame type:", frame)
|
||||
}
|
||||
if !reflect.DeepEqual(headersFrame, *parsedHeadersFrame) {
|
||||
t.Fatal("got: ", *parsedHeadersFrame, "\nwant: ", headersFrame)
|
||||
}
|
||||
frame, err = reader.ReadFrame()
|
||||
if err != nil {
|
||||
t.Fatal("ReadFrame (SYN_STREAM):", err)
|
||||
}
|
||||
parsedSynStreamFrame, ok := frame.(*SynStreamFrame)
|
||||
if !ok {
|
||||
t.Fatal("Parsed incorrect frame type.")
|
||||
}
|
||||
if !reflect.DeepEqual(synStreamFrame, *parsedSynStreamFrame) {
|
||||
t.Fatal("got: ", *parsedSynStreamFrame, "\nwant: ", synStreamFrame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadMalformedZlibHeader(t *testing.T) {
|
||||
// These were constructed by corrupting the first byte of the zlib
|
||||
// header after writing.
|
||||
malformedStructs := map[string]string{
|
||||
"SynStreamFrame": "gAIAAQAAABgAAAACAAAAAAAAF/nfolGyYmAAAAAA//8=",
|
||||
"SynReplyFrame": "gAIAAgAAABQAAAACAAAX+d+iUbJiYAAAAAD//w==",
|
||||
"HeadersFrame": "gAIACAAAABQAAAACAAAX+d+iUbJiYAAAAAD//w==",
|
||||
}
|
||||
for name, bad := range malformedStructs {
|
||||
b, err := base64.StdEncoding.DecodeString(bad)
|
||||
if err != nil {
|
||||
t.Errorf("Unable to decode base64 encoded frame %s: %v", name, err)
|
||||
}
|
||||
buf := bytes.NewBuffer(b)
|
||||
reader, err := NewFramer(buf, buf)
|
||||
if err != nil {
|
||||
t.Fatalf("NewFramer: %v", err)
|
||||
}
|
||||
_, err = reader.ReadFrame()
|
||||
if err != zlib.ErrHeader {
|
||||
t.Errorf("Frame %s, expected: %#v, actual: %#v", name, zlib.ErrHeader, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: these tests are too weak for updating SPDY spec. Fix me.
|
||||
|
||||
type zeroStream struct {
|
||||
frame Frame
|
||||
encoded string
|
||||
}
|
||||
|
||||
var streamIdZeroFrames = map[string]zeroStream{
|
||||
"SynStreamFrame": {
|
||||
&SynStreamFrame{StreamId: 0},
|
||||
"gAIAAQAAABgAAAAAAAAAAAAAePnfolGyYmAAAAAA//8=",
|
||||
},
|
||||
"SynReplyFrame": {
|
||||
&SynReplyFrame{StreamId: 0},
|
||||
"gAIAAgAAABQAAAAAAAB4+d+iUbJiYAAAAAD//w==",
|
||||
},
|
||||
"RstStreamFrame": {
|
||||
&RstStreamFrame{StreamId: 0},
|
||||
"gAIAAwAAAAgAAAAAAAAAAA==",
|
||||
},
|
||||
"HeadersFrame": {
|
||||
&HeadersFrame{StreamId: 0},
|
||||
"gAIACAAAABQAAAAAAAB4+d+iUbJiYAAAAAD//w==",
|
||||
},
|
||||
"DataFrame": {
|
||||
&DataFrame{StreamId: 0},
|
||||
"AAAAAAAAAAA=",
|
||||
},
|
||||
"PingFrame": {
|
||||
&PingFrame{Id: 0},
|
||||
"gAIABgAAAAQAAAAA",
|
||||
},
|
||||
}
|
||||
|
||||
func TestNoZeroStreamId(t *testing.T) {
|
||||
t.Log("skipping") // TODO: update to work with SPDY3
|
||||
return
|
||||
|
||||
for name, f := range streamIdZeroFrames {
|
||||
b, err := base64.StdEncoding.DecodeString(f.encoded)
|
||||
if err != nil {
|
||||
t.Errorf("Unable to decode base64 encoded frame %s: %v", f, err)
|
||||
continue
|
||||
}
|
||||
framer, err := NewFramer(ioutil.Discard, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
t.Fatalf("NewFramer: %v", err)
|
||||
}
|
||||
err = framer.WriteFrame(f.frame)
|
||||
checkZeroStreamId(t, name, "WriteFrame", err)
|
||||
|
||||
_, err = framer.ReadFrame()
|
||||
checkZeroStreamId(t, name, "ReadFrame", err)
|
||||
}
|
||||
}
|
||||
|
||||
func checkZeroStreamId(t *testing.T, frame string, method string, err error) {
|
||||
if err == nil {
|
||||
t.Errorf("%s ZeroStreamId, no error on %s", method, frame)
|
||||
return
|
||||
}
|
||||
eerr, ok := err.(*Error)
|
||||
if !ok || eerr.Err != ZeroStreamId {
|
||||
t.Errorf("%s ZeroStreamId, incorrect error %#v, frame %s", method, eerr, frame)
|
||||
}
|
||||
}
|
||||
275
vendor/github.com/docker/spdystream/spdy/types.go
generated
vendored
Normal file
275
vendor/github.com/docker/spdystream/spdy/types.go
generated
vendored
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package spdy implements the SPDY protocol (currently SPDY/3), described in
|
||||
// http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3.
|
||||
package spdy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Version is the protocol version number that this package implements.
|
||||
const Version = 3
|
||||
|
||||
// ControlFrameType stores the type field in a control frame header.
|
||||
type ControlFrameType uint16
|
||||
|
||||
const (
|
||||
TypeSynStream ControlFrameType = 0x0001
|
||||
TypeSynReply = 0x0002
|
||||
TypeRstStream = 0x0003
|
||||
TypeSettings = 0x0004
|
||||
TypePing = 0x0006
|
||||
TypeGoAway = 0x0007
|
||||
TypeHeaders = 0x0008
|
||||
TypeWindowUpdate = 0x0009
|
||||
)
|
||||
|
||||
// ControlFlags are the flags that can be set on a control frame.
|
||||
type ControlFlags uint8
|
||||
|
||||
const (
|
||||
ControlFlagFin ControlFlags = 0x01
|
||||
ControlFlagUnidirectional = 0x02
|
||||
ControlFlagSettingsClearSettings = 0x01
|
||||
)
|
||||
|
||||
// DataFlags are the flags that can be set on a data frame.
|
||||
type DataFlags uint8
|
||||
|
||||
const (
|
||||
DataFlagFin DataFlags = 0x01
|
||||
)
|
||||
|
||||
// MaxDataLength is the maximum number of bytes that can be stored in one frame.
|
||||
const MaxDataLength = 1<<24 - 1
|
||||
|
||||
// headerValueSepator separates multiple header values.
|
||||
const headerValueSeparator = "\x00"
|
||||
|
||||
// Frame is a single SPDY frame in its unpacked in-memory representation. Use
|
||||
// Framer to read and write it.
|
||||
type Frame interface {
|
||||
write(f *Framer) error
|
||||
}
|
||||
|
||||
// ControlFrameHeader contains all the fields in a control frame header,
|
||||
// in its unpacked in-memory representation.
|
||||
type ControlFrameHeader struct {
|
||||
// Note, high bit is the "Control" bit.
|
||||
version uint16 // spdy version number
|
||||
frameType ControlFrameType
|
||||
Flags ControlFlags
|
||||
length uint32 // length of data field
|
||||
}
|
||||
|
||||
type controlFrame interface {
|
||||
Frame
|
||||
read(h ControlFrameHeader, f *Framer) error
|
||||
}
|
||||
|
||||
// StreamId represents a 31-bit value identifying the stream.
|
||||
type StreamId uint32
|
||||
|
||||
// SynStreamFrame is the unpacked, in-memory representation of a SYN_STREAM
|
||||
// frame.
|
||||
type SynStreamFrame struct {
|
||||
CFHeader ControlFrameHeader
|
||||
StreamId StreamId
|
||||
AssociatedToStreamId StreamId // stream id for a stream which this stream is associated to
|
||||
Priority uint8 // priority of this frame (3-bit)
|
||||
Slot uint8 // index in the server's credential vector of the client certificate
|
||||
Headers http.Header
|
||||
}
|
||||
|
||||
// SynReplyFrame is the unpacked, in-memory representation of a SYN_REPLY frame.
|
||||
type SynReplyFrame struct {
|
||||
CFHeader ControlFrameHeader
|
||||
StreamId StreamId
|
||||
Headers http.Header
|
||||
}
|
||||
|
||||
// RstStreamStatus represents the status that led to a RST_STREAM.
|
||||
type RstStreamStatus uint32
|
||||
|
||||
const (
|
||||
ProtocolError RstStreamStatus = iota + 1
|
||||
InvalidStream
|
||||
RefusedStream
|
||||
UnsupportedVersion
|
||||
Cancel
|
||||
InternalError
|
||||
FlowControlError
|
||||
StreamInUse
|
||||
StreamAlreadyClosed
|
||||
InvalidCredentials
|
||||
FrameTooLarge
|
||||
)
|
||||
|
||||
// RstStreamFrame is the unpacked, in-memory representation of a RST_STREAM
|
||||
// frame.
|
||||
type RstStreamFrame struct {
|
||||
CFHeader ControlFrameHeader
|
||||
StreamId StreamId
|
||||
Status RstStreamStatus
|
||||
}
|
||||
|
||||
// SettingsFlag represents a flag in a SETTINGS frame.
|
||||
type SettingsFlag uint8
|
||||
|
||||
const (
|
||||
FlagSettingsPersistValue SettingsFlag = 0x1
|
||||
FlagSettingsPersisted = 0x2
|
||||
)
|
||||
|
||||
// SettingsFlag represents the id of an id/value pair in a SETTINGS frame.
|
||||
type SettingsId uint32
|
||||
|
||||
const (
|
||||
SettingsUploadBandwidth SettingsId = iota + 1
|
||||
SettingsDownloadBandwidth
|
||||
SettingsRoundTripTime
|
||||
SettingsMaxConcurrentStreams
|
||||
SettingsCurrentCwnd
|
||||
SettingsDownloadRetransRate
|
||||
SettingsInitialWindowSize
|
||||
SettingsClientCretificateVectorSize
|
||||
)
|
||||
|
||||
// SettingsFlagIdValue is the unpacked, in-memory representation of the
|
||||
// combined flag/id/value for a setting in a SETTINGS frame.
|
||||
type SettingsFlagIdValue struct {
|
||||
Flag SettingsFlag
|
||||
Id SettingsId
|
||||
Value uint32
|
||||
}
|
||||
|
||||
// SettingsFrame is the unpacked, in-memory representation of a SPDY
|
||||
// SETTINGS frame.
|
||||
type SettingsFrame struct {
|
||||
CFHeader ControlFrameHeader
|
||||
FlagIdValues []SettingsFlagIdValue
|
||||
}
|
||||
|
||||
// PingFrame is the unpacked, in-memory representation of a PING frame.
|
||||
type PingFrame struct {
|
||||
CFHeader ControlFrameHeader
|
||||
Id uint32 // unique id for this ping, from server is even, from client is odd.
|
||||
}
|
||||
|
||||
// GoAwayStatus represents the status in a GoAwayFrame.
|
||||
type GoAwayStatus uint32
|
||||
|
||||
const (
|
||||
GoAwayOK GoAwayStatus = iota
|
||||
GoAwayProtocolError
|
||||
GoAwayInternalError
|
||||
)
|
||||
|
||||
// GoAwayFrame is the unpacked, in-memory representation of a GOAWAY frame.
|
||||
type GoAwayFrame struct {
|
||||
CFHeader ControlFrameHeader
|
||||
LastGoodStreamId StreamId // last stream id which was accepted by sender
|
||||
Status GoAwayStatus
|
||||
}
|
||||
|
||||
// HeadersFrame is the unpacked, in-memory representation of a HEADERS frame.
|
||||
type HeadersFrame struct {
|
||||
CFHeader ControlFrameHeader
|
||||
StreamId StreamId
|
||||
Headers http.Header
|
||||
}
|
||||
|
||||
// WindowUpdateFrame is the unpacked, in-memory representation of a
|
||||
// WINDOW_UPDATE frame.
|
||||
type WindowUpdateFrame struct {
|
||||
CFHeader ControlFrameHeader
|
||||
StreamId StreamId
|
||||
DeltaWindowSize uint32 // additional number of bytes to existing window size
|
||||
}
|
||||
|
||||
// TODO: Implement credential frame and related methods.
|
||||
|
||||
// DataFrame is the unpacked, in-memory representation of a DATA frame.
|
||||
type DataFrame struct {
|
||||
// Note, high bit is the "Control" bit. Should be 0 for data frames.
|
||||
StreamId StreamId
|
||||
Flags DataFlags
|
||||
Data []byte // payload data of this frame
|
||||
}
|
||||
|
||||
// A SPDY specific error.
|
||||
type ErrorCode string
|
||||
|
||||
const (
|
||||
UnlowercasedHeaderName ErrorCode = "header was not lowercased"
|
||||
DuplicateHeaders = "multiple headers with same name"
|
||||
WrongCompressedPayloadSize = "compressed payload size was incorrect"
|
||||
UnknownFrameType = "unknown frame type"
|
||||
InvalidControlFrame = "invalid control frame"
|
||||
InvalidDataFrame = "invalid data frame"
|
||||
InvalidHeaderPresent = "frame contained invalid header"
|
||||
ZeroStreamId = "stream id zero is disallowed"
|
||||
)
|
||||
|
||||
// Error contains both the type of error and additional values. StreamId is 0
|
||||
// if Error is not associated with a stream.
|
||||
type Error struct {
|
||||
Err ErrorCode
|
||||
StreamId StreamId
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return string(e.Err)
|
||||
}
|
||||
|
||||
var invalidReqHeaders = map[string]bool{
|
||||
"Connection": true,
|
||||
"Host": true,
|
||||
"Keep-Alive": true,
|
||||
"Proxy-Connection": true,
|
||||
"Transfer-Encoding": true,
|
||||
}
|
||||
|
||||
var invalidRespHeaders = map[string]bool{
|
||||
"Connection": true,
|
||||
"Keep-Alive": true,
|
||||
"Proxy-Connection": true,
|
||||
"Transfer-Encoding": true,
|
||||
}
|
||||
|
||||
// Framer handles serializing/deserializing SPDY frames, including compressing/
|
||||
// decompressing payloads.
|
||||
type Framer struct {
|
||||
headerCompressionDisabled bool
|
||||
w io.Writer
|
||||
headerBuf *bytes.Buffer
|
||||
headerCompressor *zlib.Writer
|
||||
r io.Reader
|
||||
headerReader io.LimitedReader
|
||||
headerDecompressor io.ReadCloser
|
||||
}
|
||||
|
||||
// NewFramer allocates a new Framer for a given SPDY connection, represented by
|
||||
// a io.Writer and io.Reader. Note that Framer will read and write individual fields
|
||||
// from/to the Reader and Writer, so the caller should pass in an appropriately
|
||||
// buffered implementation to optimize performance.
|
||||
func NewFramer(w io.Writer, r io.Reader) (*Framer, error) {
|
||||
compressBuf := new(bytes.Buffer)
|
||||
compressor, err := zlib.NewWriterLevelDict(compressBuf, zlib.BestCompression, []byte(headerDictionary))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
framer := &Framer{
|
||||
w: w,
|
||||
headerBuf: compressBuf,
|
||||
headerCompressor: compressor,
|
||||
r: r,
|
||||
}
|
||||
return framer, nil
|
||||
}
|
||||
318
vendor/github.com/docker/spdystream/spdy/write.go
generated
vendored
Normal file
318
vendor/github.com/docker/spdystream/spdy/write.go
generated
vendored
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package spdy
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (frame *SynStreamFrame) write(f *Framer) error {
|
||||
return f.writeSynStreamFrame(frame)
|
||||
}
|
||||
|
||||
func (frame *SynReplyFrame) write(f *Framer) error {
|
||||
return f.writeSynReplyFrame(frame)
|
||||
}
|
||||
|
||||
func (frame *RstStreamFrame) write(f *Framer) (err error) {
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
frame.CFHeader.version = Version
|
||||
frame.CFHeader.frameType = TypeRstStream
|
||||
frame.CFHeader.Flags = 0
|
||||
frame.CFHeader.length = 8
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
|
||||
return
|
||||
}
|
||||
if frame.Status == 0 {
|
||||
return &Error{InvalidControlFrame, frame.StreamId}
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.Status); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (frame *SettingsFrame) write(f *Framer) (err error) {
|
||||
frame.CFHeader.version = Version
|
||||
frame.CFHeader.frameType = TypeSettings
|
||||
frame.CFHeader.length = uint32(len(frame.FlagIdValues)*8 + 4)
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, uint32(len(frame.FlagIdValues))); err != nil {
|
||||
return
|
||||
}
|
||||
for _, flagIdValue := range frame.FlagIdValues {
|
||||
flagId := uint32(flagIdValue.Flag)<<24 | uint32(flagIdValue.Id)
|
||||
if err = binary.Write(f.w, binary.BigEndian, flagId); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, flagIdValue.Value); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (frame *PingFrame) write(f *Framer) (err error) {
|
||||
if frame.Id == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
frame.CFHeader.version = Version
|
||||
frame.CFHeader.frameType = TypePing
|
||||
frame.CFHeader.Flags = 0
|
||||
frame.CFHeader.length = 4
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.Id); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (frame *GoAwayFrame) write(f *Framer) (err error) {
|
||||
frame.CFHeader.version = Version
|
||||
frame.CFHeader.frameType = TypeGoAway
|
||||
frame.CFHeader.Flags = 0
|
||||
frame.CFHeader.length = 8
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.LastGoodStreamId); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.Status); err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (frame *HeadersFrame) write(f *Framer) error {
|
||||
return f.writeHeadersFrame(frame)
|
||||
}
|
||||
|
||||
func (frame *WindowUpdateFrame) write(f *Framer) (err error) {
|
||||
frame.CFHeader.version = Version
|
||||
frame.CFHeader.frameType = TypeWindowUpdate
|
||||
frame.CFHeader.Flags = 0
|
||||
frame.CFHeader.length = 8
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.DeltaWindowSize); err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (frame *DataFrame) write(f *Framer) error {
|
||||
return f.writeDataFrame(frame)
|
||||
}
|
||||
|
||||
// WriteFrame writes a frame.
|
||||
func (f *Framer) WriteFrame(frame Frame) error {
|
||||
return frame.write(f)
|
||||
}
|
||||
|
||||
func writeControlFrameHeader(w io.Writer, h ControlFrameHeader) error {
|
||||
if err := binary.Write(w, binary.BigEndian, 0x8000|h.version); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := binary.Write(w, binary.BigEndian, h.frameType); err != nil {
|
||||
return err
|
||||
}
|
||||
flagsAndLength := uint32(h.Flags)<<24 | h.length
|
||||
if err := binary.Write(w, binary.BigEndian, flagsAndLength); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeHeaderValueBlock(w io.Writer, h http.Header) (n int, err error) {
|
||||
n = 0
|
||||
if err = binary.Write(w, binary.BigEndian, uint32(len(h))); err != nil {
|
||||
return
|
||||
}
|
||||
n += 2
|
||||
for name, values := range h {
|
||||
if err = binary.Write(w, binary.BigEndian, uint32(len(name))); err != nil {
|
||||
return
|
||||
}
|
||||
n += 2
|
||||
name = strings.ToLower(name)
|
||||
if _, err = io.WriteString(w, name); err != nil {
|
||||
return
|
||||
}
|
||||
n += len(name)
|
||||
v := strings.Join(values, headerValueSeparator)
|
||||
if err = binary.Write(w, binary.BigEndian, uint32(len(v))); err != nil {
|
||||
return
|
||||
}
|
||||
n += 2
|
||||
if _, err = io.WriteString(w, v); err != nil {
|
||||
return
|
||||
}
|
||||
n += len(v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Framer) writeSynStreamFrame(frame *SynStreamFrame) (err error) {
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
// Marshal the headers.
|
||||
var writer io.Writer = f.headerBuf
|
||||
if !f.headerCompressionDisabled {
|
||||
writer = f.headerCompressor
|
||||
}
|
||||
if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil {
|
||||
return
|
||||
}
|
||||
if !f.headerCompressionDisabled {
|
||||
f.headerCompressor.Flush()
|
||||
}
|
||||
|
||||
// Set ControlFrameHeader.
|
||||
frame.CFHeader.version = Version
|
||||
frame.CFHeader.frameType = TypeSynStream
|
||||
frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 10)
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.AssociatedToStreamId); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.Priority<<5); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.Slot); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
f.headerBuf.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Framer) writeSynReplyFrame(frame *SynReplyFrame) (err error) {
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
// Marshal the headers.
|
||||
var writer io.Writer = f.headerBuf
|
||||
if !f.headerCompressionDisabled {
|
||||
writer = f.headerCompressor
|
||||
}
|
||||
if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil {
|
||||
return
|
||||
}
|
||||
if !f.headerCompressionDisabled {
|
||||
f.headerCompressor.Flush()
|
||||
}
|
||||
|
||||
// Set ControlFrameHeader.
|
||||
frame.CFHeader.version = Version
|
||||
frame.CFHeader.frameType = TypeSynReply
|
||||
frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 4)
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil {
|
||||
return
|
||||
}
|
||||
f.headerBuf.Reset()
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Framer) writeHeadersFrame(frame *HeadersFrame) (err error) {
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
// Marshal the headers.
|
||||
var writer io.Writer = f.headerBuf
|
||||
if !f.headerCompressionDisabled {
|
||||
writer = f.headerCompressor
|
||||
}
|
||||
if _, err = writeHeaderValueBlock(writer, frame.Headers); err != nil {
|
||||
return
|
||||
}
|
||||
if !f.headerCompressionDisabled {
|
||||
f.headerCompressor.Flush()
|
||||
}
|
||||
|
||||
// Set ControlFrameHeader.
|
||||
frame.CFHeader.version = Version
|
||||
frame.CFHeader.frameType = TypeHeaders
|
||||
frame.CFHeader.length = uint32(len(f.headerBuf.Bytes()) + 4)
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = writeControlFrameHeader(f.w, frame.CFHeader); err != nil {
|
||||
return
|
||||
}
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = f.w.Write(f.headerBuf.Bytes()); err != nil {
|
||||
return
|
||||
}
|
||||
f.headerBuf.Reset()
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Framer) writeDataFrame(frame *DataFrame) (err error) {
|
||||
if frame.StreamId == 0 {
|
||||
return &Error{ZeroStreamId, 0}
|
||||
}
|
||||
if frame.StreamId&0x80000000 != 0 || len(frame.Data) > MaxDataLength {
|
||||
return &Error{InvalidDataFrame, frame.StreamId}
|
||||
}
|
||||
|
||||
// Serialize frame to Writer.
|
||||
if err = binary.Write(f.w, binary.BigEndian, frame.StreamId); err != nil {
|
||||
return
|
||||
}
|
||||
flagsAndLength := uint32(frame.Flags)<<24 | uint32(len(frame.Data))
|
||||
if err = binary.Write(f.w, binary.BigEndian, flagsAndLength); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = f.w.Write(frame.Data); err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
113
vendor/github.com/docker/spdystream/spdy_bench_test.go
generated
vendored
Normal file
113
vendor/github.com/docker/spdystream/spdy_bench_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
package spdystream
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func configureServer() (io.Closer, string, *sync.WaitGroup) {
|
||||
authenticated = true
|
||||
wg := &sync.WaitGroup{}
|
||||
server, listen, serverErr := runServer(wg)
|
||||
|
||||
if serverErr != nil {
|
||||
panic(serverErr)
|
||||
}
|
||||
|
||||
return server, listen, wg
|
||||
}
|
||||
|
||||
func BenchmarkDial10000(b *testing.B) {
|
||||
server, addr, wg := configureServer()
|
||||
|
||||
defer func() {
|
||||
server.Close()
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
conn, dialErr := net.Dial("tcp", addr)
|
||||
if dialErr != nil {
|
||||
panic(fmt.Sprintf("Error dialing server: %s", dialErr))
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDialWithSPDYStream10000(b *testing.B) {
|
||||
server, addr, wg := configureServer()
|
||||
|
||||
defer func() {
|
||||
server.Close()
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
conn, dialErr := net.Dial("tcp", addr)
|
||||
if dialErr != nil {
|
||||
b.Fatalf("Error dialing server: %s", dialErr)
|
||||
}
|
||||
|
||||
spdyConn, spdyErr := NewConnection(conn, false)
|
||||
if spdyErr != nil {
|
||||
b.Fatalf("Error creating spdy connection: %s", spdyErr)
|
||||
}
|
||||
go spdyConn.Serve(NoOpStreamHandler)
|
||||
|
||||
closeErr := spdyConn.Close()
|
||||
if closeErr != nil {
|
||||
b.Fatalf("Error closing connection: %s, closeErr")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkStreamWithDataAndSize(size uint64, b *testing.B) {
|
||||
server, addr, wg := configureServer()
|
||||
|
||||
defer func() {
|
||||
server.Close()
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
conn, dialErr := net.Dial("tcp", addr)
|
||||
if dialErr != nil {
|
||||
b.Fatalf("Error dialing server: %s", dialErr)
|
||||
}
|
||||
|
||||
spdyConn, spdyErr := NewConnection(conn, false)
|
||||
if spdyErr != nil {
|
||||
b.Fatalf("Error creating spdy connection: %s", spdyErr)
|
||||
}
|
||||
|
||||
go spdyConn.Serve(MirrorStreamHandler)
|
||||
|
||||
stream, err := spdyConn.CreateStream(http.Header{}, nil, false)
|
||||
|
||||
writer := make([]byte, size)
|
||||
|
||||
stream.Write(writer)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
reader := make([]byte, size)
|
||||
stream.Read(reader)
|
||||
|
||||
stream.Close()
|
||||
|
||||
closeErr := spdyConn.Close()
|
||||
if closeErr != nil {
|
||||
b.Fatalf("Error closing connection: %s, closeErr")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStreamWith1Byte10000(b *testing.B) { benchmarkStreamWithDataAndSize(1, b) }
|
||||
func BenchmarkStreamWith1KiloByte10000(b *testing.B) { benchmarkStreamWithDataAndSize(1024, b) }
|
||||
func BenchmarkStreamWith1Megabyte10000(b *testing.B) { benchmarkStreamWithDataAndSize(1024*1024, b) }
|
||||
1171
vendor/github.com/docker/spdystream/spdy_test.go
generated
vendored
Normal file
1171
vendor/github.com/docker/spdystream/spdy_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
327
vendor/github.com/docker/spdystream/stream.go
generated
vendored
Normal file
327
vendor/github.com/docker/spdystream/stream.go
generated
vendored
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
package spdystream
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/spdystream/spdy"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnreadPartialData = errors.New("unread partial data")
|
||||
)
|
||||
|
||||
type Stream struct {
|
||||
streamId spdy.StreamId
|
||||
parent *Stream
|
||||
conn *Connection
|
||||
startChan chan error
|
||||
|
||||
dataLock sync.RWMutex
|
||||
dataChan chan []byte
|
||||
unread []byte
|
||||
|
||||
priority uint8
|
||||
headers http.Header
|
||||
headerChan chan http.Header
|
||||
finishLock sync.Mutex
|
||||
finished bool
|
||||
replyCond *sync.Cond
|
||||
replied bool
|
||||
closeLock sync.Mutex
|
||||
closeChan chan bool
|
||||
}
|
||||
|
||||
// WriteData writes data to stream, sending a dataframe per call
|
||||
func (s *Stream) WriteData(data []byte, fin bool) error {
|
||||
s.waitWriteReply()
|
||||
var flags spdy.DataFlags
|
||||
|
||||
if fin {
|
||||
flags = spdy.DataFlagFin
|
||||
s.finishLock.Lock()
|
||||
if s.finished {
|
||||
s.finishLock.Unlock()
|
||||
return ErrWriteClosedStream
|
||||
}
|
||||
s.finished = true
|
||||
s.finishLock.Unlock()
|
||||
}
|
||||
|
||||
dataFrame := &spdy.DataFrame{
|
||||
StreamId: s.streamId,
|
||||
Flags: flags,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
debugMessage("(%p) (%d) Writing data frame", s, s.streamId)
|
||||
return s.conn.framer.WriteFrame(dataFrame)
|
||||
}
|
||||
|
||||
// Write writes bytes to a stream, calling write data for each call.
|
||||
func (s *Stream) Write(data []byte) (n int, err error) {
|
||||
err = s.WriteData(data, false)
|
||||
if err == nil {
|
||||
n = len(data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Read reads bytes from a stream, a single read will never get more
|
||||
// than what is sent on a single data frame, but a multiple calls to
|
||||
// read may get data from the same data frame.
|
||||
func (s *Stream) Read(p []byte) (n int, err error) {
|
||||
if s.unread == nil {
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
return 0, io.EOF
|
||||
case read, ok := <-s.dataChan:
|
||||
if !ok {
|
||||
return 0, io.EOF
|
||||
}
|
||||
s.unread = read
|
||||
}
|
||||
}
|
||||
n = copy(p, s.unread)
|
||||
if n < len(s.unread) {
|
||||
s.unread = s.unread[n:]
|
||||
} else {
|
||||
s.unread = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ReadData reads an entire data frame and returns the byte array
|
||||
// from the data frame. If there is unread data from the result
|
||||
// of a Read call, this function will return an ErrUnreadPartialData.
|
||||
func (s *Stream) ReadData() ([]byte, error) {
|
||||
debugMessage("(%p) Reading data from %d", s, s.streamId)
|
||||
if s.unread != nil {
|
||||
return nil, ErrUnreadPartialData
|
||||
}
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
return nil, io.EOF
|
||||
case read, ok := <-s.dataChan:
|
||||
if !ok {
|
||||
return nil, io.EOF
|
||||
}
|
||||
return read, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) waitWriteReply() {
|
||||
if s.replyCond != nil {
|
||||
s.replyCond.L.Lock()
|
||||
for !s.replied {
|
||||
s.replyCond.Wait()
|
||||
}
|
||||
s.replyCond.L.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Wait waits for the stream to receive a reply.
|
||||
func (s *Stream) Wait() error {
|
||||
return s.WaitTimeout(time.Duration(0))
|
||||
}
|
||||
|
||||
// WaitTimeout waits for the stream to receive a reply or for timeout.
|
||||
// When the timeout is reached, ErrTimeout will be returned.
|
||||
func (s *Stream) WaitTimeout(timeout time.Duration) error {
|
||||
var timeoutChan <-chan time.Time
|
||||
if timeout > time.Duration(0) {
|
||||
timeoutChan = time.After(timeout)
|
||||
}
|
||||
|
||||
select {
|
||||
case err := <-s.startChan:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
case <-timeoutChan:
|
||||
return ErrTimeout
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the stream by sending an empty data frame with the
|
||||
// finish flag set, indicating this side is finished with the stream.
|
||||
func (s *Stream) Close() error {
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
// Stream is now fully closed
|
||||
s.conn.removeStream(s)
|
||||
default:
|
||||
break
|
||||
}
|
||||
return s.WriteData([]byte{}, true)
|
||||
}
|
||||
|
||||
// Reset sends a reset frame, putting the stream into the fully closed state.
|
||||
func (s *Stream) Reset() error {
|
||||
s.conn.removeStream(s)
|
||||
return s.resetStream()
|
||||
}
|
||||
|
||||
func (s *Stream) resetStream() error {
|
||||
// Always call closeRemoteChannels, even if s.finished is already true.
|
||||
// This makes it so that stream.Close() followed by stream.Reset() allows
|
||||
// stream.Read() to unblock.
|
||||
s.closeRemoteChannels()
|
||||
|
||||
s.finishLock.Lock()
|
||||
if s.finished {
|
||||
s.finishLock.Unlock()
|
||||
return nil
|
||||
}
|
||||
s.finished = true
|
||||
s.finishLock.Unlock()
|
||||
|
||||
resetFrame := &spdy.RstStreamFrame{
|
||||
StreamId: s.streamId,
|
||||
Status: spdy.Cancel,
|
||||
}
|
||||
return s.conn.framer.WriteFrame(resetFrame)
|
||||
}
|
||||
|
||||
// CreateSubStream creates a stream using the current as the parent
|
||||
func (s *Stream) CreateSubStream(headers http.Header, fin bool) (*Stream, error) {
|
||||
return s.conn.CreateStream(headers, s, fin)
|
||||
}
|
||||
|
||||
// SetPriority sets the stream priority, does not affect the
|
||||
// remote priority of this stream after Open has been called.
|
||||
// Valid values are 0 through 7, 0 being the highest priority
|
||||
// and 7 the lowest.
|
||||
func (s *Stream) SetPriority(priority uint8) {
|
||||
s.priority = priority
|
||||
}
|
||||
|
||||
// SendHeader sends a header frame across the stream
|
||||
func (s *Stream) SendHeader(headers http.Header, fin bool) error {
|
||||
return s.conn.sendHeaders(headers, s, fin)
|
||||
}
|
||||
|
||||
// SendReply sends a reply on a stream, only valid to be called once
|
||||
// when handling a new stream
|
||||
func (s *Stream) SendReply(headers http.Header, fin bool) error {
|
||||
if s.replyCond == nil {
|
||||
return errors.New("cannot reply on initiated stream")
|
||||
}
|
||||
s.replyCond.L.Lock()
|
||||
defer s.replyCond.L.Unlock()
|
||||
if s.replied {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := s.conn.sendReply(headers, s, fin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.replied = true
|
||||
s.replyCond.Broadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Refuse sends a reset frame with the status refuse, only
|
||||
// valid to be called once when handling a new stream. This
|
||||
// may be used to indicate that a stream is not allowed
|
||||
// when http status codes are not being used.
|
||||
func (s *Stream) Refuse() error {
|
||||
if s.replied {
|
||||
return nil
|
||||
}
|
||||
s.replied = true
|
||||
return s.conn.sendReset(spdy.RefusedStream, s)
|
||||
}
|
||||
|
||||
// Cancel sends a reset frame with the status canceled. This
|
||||
// can be used at any time by the creator of the Stream to
|
||||
// indicate the stream is no longer needed.
|
||||
func (s *Stream) Cancel() error {
|
||||
return s.conn.sendReset(spdy.Cancel, s)
|
||||
}
|
||||
|
||||
// ReceiveHeader receives a header sent on the other side
|
||||
// of the stream. This function will block until a header
|
||||
// is received or stream is closed.
|
||||
func (s *Stream) ReceiveHeader() (http.Header, error) {
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
break
|
||||
case header, ok := <-s.headerChan:
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("header chan closed")
|
||||
}
|
||||
return header, nil
|
||||
}
|
||||
return nil, fmt.Errorf("stream closed")
|
||||
}
|
||||
|
||||
// Parent returns the parent stream
|
||||
func (s *Stream) Parent() *Stream {
|
||||
return s.parent
|
||||
}
|
||||
|
||||
// Headers returns the headers used to create the stream
|
||||
func (s *Stream) Headers() http.Header {
|
||||
return s.headers
|
||||
}
|
||||
|
||||
// String returns the string version of stream using the
|
||||
// streamId to uniquely identify the stream
|
||||
func (s *Stream) String() string {
|
||||
return fmt.Sprintf("stream:%d", s.streamId)
|
||||
}
|
||||
|
||||
// Identifier returns a 32 bit identifier for the stream
|
||||
func (s *Stream) Identifier() uint32 {
|
||||
return uint32(s.streamId)
|
||||
}
|
||||
|
||||
// IsFinished returns whether the stream has finished
|
||||
// sending data
|
||||
func (s *Stream) IsFinished() bool {
|
||||
return s.finished
|
||||
}
|
||||
|
||||
// Implement net.Conn interface
|
||||
|
||||
func (s *Stream) LocalAddr() net.Addr {
|
||||
return s.conn.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (s *Stream) RemoteAddr() net.Addr {
|
||||
return s.conn.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
// TODO set per stream values instead of connection-wide
|
||||
|
||||
func (s *Stream) SetDeadline(t time.Time) error {
|
||||
return s.conn.conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (s *Stream) SetReadDeadline(t time.Time) error {
|
||||
return s.conn.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (s *Stream) SetWriteDeadline(t time.Time) error {
|
||||
return s.conn.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (s *Stream) closeRemoteChannels() {
|
||||
s.closeLock.Lock()
|
||||
defer s.closeLock.Unlock()
|
||||
select {
|
||||
case <-s.closeChan:
|
||||
default:
|
||||
close(s.closeChan)
|
||||
}
|
||||
}
|
||||
16
vendor/github.com/docker/spdystream/utils.go
generated
vendored
Normal file
16
vendor/github.com/docker/spdystream/utils.go
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package spdystream
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
DEBUG = os.Getenv("DEBUG")
|
||||
)
|
||||
|
||||
func debugMessage(fmt string, args ...interface{}) {
|
||||
if DEBUG != "" {
|
||||
log.Printf(fmt, args...)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue