mirror of https://github.com/docker/docs.git
Merge branch master into bump_v1.4.0
Docker-DCO-1.1-Signed-off-by: Jessica Frazelle <jess@docker.com> (github: jfrazelle)
This commit is contained in:
commit
debf60b466
|
@ -4,6 +4,7 @@
|
||||||
.vagrant*
|
.vagrant*
|
||||||
bin
|
bin
|
||||||
docker/docker
|
docker/docker
|
||||||
|
*.exe
|
||||||
.*.swp
|
.*.swp
|
||||||
a.out
|
a.out
|
||||||
*.orig
|
*.orig
|
||||||
|
|
30
.mailmap
30
.mailmap
|
@ -1,8 +1,10 @@
|
||||||
# Generate AUTHORS: hack/generate-authors.sh
|
# Generate AUTHORS: project/generate-authors.sh
|
||||||
|
|
||||||
# Tip for finding duplicates (besides scanning the output of AUTHORS for name
|
# Tip for finding duplicates (besides scanning the output of AUTHORS for name
|
||||||
# duplicates that aren't also email duplicates): scan the output of:
|
# duplicates that aren't also email duplicates): scan the output of:
|
||||||
# git log --format='%aE - %aN' | sort -uf
|
# git log --format='%aE - %aN' | sort -uf
|
||||||
|
#
|
||||||
|
# For explanation on this file format: man git-shortlog
|
||||||
|
|
||||||
<charles.hooper@dotcloud.com> <chooper@plumata.com>
|
<charles.hooper@dotcloud.com> <chooper@plumata.com>
|
||||||
<daniel.mizyrycki@dotcloud.com> <daniel@dotcloud.com>
|
<daniel.mizyrycki@dotcloud.com> <daniel@dotcloud.com>
|
||||||
|
@ -29,6 +31,7 @@ Andy Smith <github@anarkystic.com>
|
||||||
<victor.vieux@docker.com> <dev@vvieux.com>
|
<victor.vieux@docker.com> <dev@vvieux.com>
|
||||||
<victor.vieux@docker.com> <victor@docker.com>
|
<victor.vieux@docker.com> <victor@docker.com>
|
||||||
<victor.vieux@docker.com> <vieux@docker.com>
|
<victor.vieux@docker.com> <vieux@docker.com>
|
||||||
|
<victor.vieux@docker.com> <victorvieux@gmail.com>
|
||||||
<dominik@honnef.co> <dominikh@fork-bomb.org>
|
<dominik@honnef.co> <dominikh@fork-bomb.org>
|
||||||
<ehanchrow@ine.com> <eric.hanchrow@gmail.com>
|
<ehanchrow@ine.com> <eric.hanchrow@gmail.com>
|
||||||
Walter Stanish <walter@pratyeka.org>
|
Walter Stanish <walter@pratyeka.org>
|
||||||
|
@ -54,6 +57,7 @@ Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||||
<proppy@google.com> <proppy@aminche.com>
|
<proppy@google.com> <proppy@aminche.com>
|
||||||
<michael@docker.com> <michael@crosbymichael.com>
|
<michael@docker.com> <michael@crosbymichael.com>
|
||||||
<michael@docker.com> <crosby.michael@gmail.com>
|
<michael@docker.com> <crosby.michael@gmail.com>
|
||||||
|
<michael@docker.com> <crosbymichael@gmail.com>
|
||||||
<github@developersupport.net> <github@metaliveblog.com>
|
<github@developersupport.net> <github@metaliveblog.com>
|
||||||
<brandon@ifup.org> <brandon@ifup.co>
|
<brandon@ifup.org> <brandon@ifup.co>
|
||||||
<dano@spotify.com> <daniel.norberg@gmail.com>
|
<dano@spotify.com> <daniel.norberg@gmail.com>
|
||||||
|
@ -63,10 +67,13 @@ Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||||
<sjoerd-github@linuxonly.nl> <sjoerd@byte.nl>
|
<sjoerd-github@linuxonly.nl> <sjoerd@byte.nl>
|
||||||
<solomon@docker.com> <solomon.hykes@dotcloud.com>
|
<solomon@docker.com> <solomon.hykes@dotcloud.com>
|
||||||
<solomon@docker.com> <solomon@dotcloud.com>
|
<solomon@docker.com> <solomon@dotcloud.com>
|
||||||
|
<solomon@docker.com> <s@docker.com>
|
||||||
Sven Dowideit <SvenDowideit@home.org.au>
|
Sven Dowideit <SvenDowideit@home.org.au>
|
||||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@fosiki.com>
|
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@fosiki.com>
|
||||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@docker.com>
|
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@docker.com>
|
||||||
Sven Dowideit <SvenDowideit@home.org.au> <¨SvenDowideit@home.org.au¨>
|
Sven Dowideit <SvenDowideit@home.org.au> <¨SvenDowideit@home.org.au¨>
|
||||||
|
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@users.noreply.github.com>
|
||||||
|
Sven Dowideit <SvenDowideit@home.org.au> <sven@t440s.home.gateway>
|
||||||
unclejack <unclejacksons@gmail.com> <unclejack@users.noreply.github.com>
|
unclejack <unclejacksons@gmail.com> <unclejack@users.noreply.github.com>
|
||||||
<alexl@redhat.com> <alexander.larsson@gmail.com>
|
<alexl@redhat.com> <alexander.larsson@gmail.com>
|
||||||
Alexandr Morozov <lk4d4math@gmail.com>
|
Alexandr Morozov <lk4d4math@gmail.com>
|
||||||
|
@ -97,3 +104,24 @@ Matthew Heon <mheon@redhat.com> <mheon@mheonlaptop.redhat.com>
|
||||||
<andrew.weiss@outlook.com> <andrew.weiss@microsoft.com>
|
<andrew.weiss@outlook.com> <andrew.weiss@microsoft.com>
|
||||||
Francisco Carriedo <fcarriedo@gmail.com>
|
Francisco Carriedo <fcarriedo@gmail.com>
|
||||||
<julienbordellier@gmail.com> <git@julienbordellier.com>
|
<julienbordellier@gmail.com> <git@julienbordellier.com>
|
||||||
|
<ahmetb@microsoft.com> <ahmetalpbalkan@gmail.com>
|
||||||
|
<lk4d4@docker.com> <lk4d4math@gmail.com>
|
||||||
|
<arnaud.porterie@docker.com> <icecrime@gmail.com>
|
||||||
|
<baloo@gandi.net> <superbaloo+registrations.github@superbaloo.net>
|
||||||
|
Brian Goff <cpuguy83@gmail.com>
|
||||||
|
<cpuguy83@gmail.com> <bgoff@cpuguy83-mbp.home>
|
||||||
|
<ewindisch@docker.com> <eric@windisch.us>
|
||||||
|
<frank.rosquin+github@gmail.com> <frank.rosquin@gmail.com>
|
||||||
|
Hollie Teal <hollie@docker.com>
|
||||||
|
<hollie@docker.com> <hollie.teal@docker.com>
|
||||||
|
<hollie@docker.com> <hollietealok@users.noreply.github.com>
|
||||||
|
<huu@prismskylabs.com> <whoshuu@gmail.com>
|
||||||
|
Jessica Frazelle <jess@docker.com> Jessie Frazelle <jfrazelle@users.noreply.github.com>
|
||||||
|
<jess@docker.com> <jfrazelle@users.noreply.github.com>
|
||||||
|
<konrad.wilhelm.kleine@gmail.com> <kwk@users.noreply.github.com>
|
||||||
|
<tintypemolly@gmail.com> <tintypemolly@Ohui-MacBook-Pro.local>
|
||||||
|
<estesp@linux.vnet.ibm.com> <estesp@gmail.com>
|
||||||
|
<github@gone.nl> <thaJeztah@users.noreply.github.com>
|
||||||
|
Thomas LEVEIL <thomasleveil@gmail.com> Thomas LÉVEIL <thomasleveil@users.noreply.github.com>
|
||||||
|
<oi@truffles.me.uk> <timruffles@googlemail.com>
|
||||||
|
<Vincent.Bernat@exoscale.ch> <bernat@luffy.cx>
|
||||||
|
|
39
.travis.yml
39
.travis.yml
|
@ -1,39 +0,0 @@
|
||||||
# Note: right now we don't use go-specific features of travis.
|
|
||||||
# Later we might automate "go test" etc. (or do it inside a docker container...?)
|
|
||||||
|
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
# This should match the version in the Dockerfile.
|
|
||||||
- 1.3.1
|
|
||||||
# Test against older versions too, just for a little extra retrocompat.
|
|
||||||
- 1.2
|
|
||||||
|
|
||||||
# Let us have pretty experimental Docker-based Travis workers.
|
|
||||||
# (These spin up much faster than the VM-based ones.)
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
# Disable the normal go build.
|
|
||||||
install:
|
|
||||||
- export DOCKER_BUILDTAGS='exclude_graphdriver_btrfs exclude_graphdriver_devicemapper' # btrfs and devicemapper fail to compile thanks to a couple missing headers (which we can't install thanks to "sudo: false")
|
|
||||||
- export AUTO_GOPATH=1
|
|
||||||
# some of Docker's unit tests don't work inside Travis (yet!), so we purge those test files for now
|
|
||||||
- rm -f daemon/graphdriver/btrfs/*_test.go # fails to compile (missing header)
|
|
||||||
- rm -f daemon/graphdriver/devmapper/*_test.go # fails to compile (missing header)
|
|
||||||
- rm -f daemon/execdriver/lxc/*_test.go # fails to run (missing "lxc-start")
|
|
||||||
- rm -f daemon/graphdriver/aufs/*_test.go # fails to run ("backing file system is unsupported for this graph driver")
|
|
||||||
- rm -f daemon/graphdriver/vfs/*_test.go # fails to run (not root, which these tests assume "/var/tmp/... no owned by uid 0")
|
|
||||||
- rm -f daemon/networkdriver/bridge/*_test.go # fails to run ("Failed to initialize network driver")
|
|
||||||
- rm -f graph/*_test.go # fails to run ("mkdir /tmp/docker-test.../vfs/dir/foo/etc/postgres: permission denied")
|
|
||||||
- rm -f pkg/mount/*_test.go # fails to run ("permission denied")
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- env | sort
|
|
||||||
|
|
||||||
script:
|
|
||||||
- hack/make.sh validate-dco
|
|
||||||
- hack/make.sh validate-gofmt
|
|
||||||
- DOCKER_CLIENTONLY=1 ./hack/make.sh dynbinary
|
|
||||||
- ./hack/make.sh dynbinary dyntest-unit
|
|
||||||
|
|
||||||
# vim:set sw=2 ts=2:
|
|
239
AUTHORS
239
AUTHORS
|
@ -1,69 +1,87 @@
|
||||||
# This file lists all individuals having contributed content to the repository.
|
# This file lists all individuals having contributed content to the repository.
|
||||||
# For how it is generated, see `hack/generate-authors.sh`.
|
# For how it is generated, see `project/generate-authors.sh`.
|
||||||
|
|
||||||
Aanand Prasad <aanand.prasad@gmail.com>
|
Aanand Prasad <aanand.prasad@gmail.com>
|
||||||
Aaron Feng <aaron.feng@gmail.com>
|
Aaron Feng <aaron.feng@gmail.com>
|
||||||
Aaron Huslage <huslage@gmail.com>
|
Aaron Huslage <huslage@gmail.com>
|
||||||
Abel Muiño <amuino@gmail.com>
|
Abel Muiño <amuino@gmail.com>
|
||||||
|
Abhinav Ajgaonkar <abhinav316@gmail.com>
|
||||||
|
Abin Shahab <ashahab@altiscale.com>
|
||||||
Adam Miller <admiller@redhat.com>
|
Adam Miller <admiller@redhat.com>
|
||||||
Adam Singer <financeCoding@gmail.com>
|
Adam Singer <financeCoding@gmail.com>
|
||||||
Aditya <aditya@netroy.in>
|
Aditya <aditya@netroy.in>
|
||||||
Adrian Mouat <adrian.mouat@gmail.com>
|
Adrian Mouat <adrian.mouat@gmail.com>
|
||||||
Adrien Folie <folie.adrien@gmail.com>
|
Adrien Folie <folie.adrien@gmail.com>
|
||||||
|
Ahmet Alp Balkan <ahmetb@microsoft.com>
|
||||||
AJ Bowen <aj@gandi.net>
|
AJ Bowen <aj@gandi.net>
|
||||||
Al Tobey <al@ooyala.com>
|
|
||||||
alambike <alambike@gmail.com>
|
alambike <alambike@gmail.com>
|
||||||
|
Alan Thompson <cloojure@gmail.com>
|
||||||
|
Albert Callarisa <shark234@gmail.com>
|
||||||
Albert Zhang <zhgwenming@gmail.com>
|
Albert Zhang <zhgwenming@gmail.com>
|
||||||
Aleksa Sarai <cyphar@cyphar.com>
|
Aleksa Sarai <cyphar@cyphar.com>
|
||||||
Alex Gaynor <alex.gaynor@gmail.com>
|
|
||||||
Alex Warhawk <ax.warhawk@gmail.com>
|
|
||||||
Alexander Larsson <alexl@redhat.com>
|
Alexander Larsson <alexl@redhat.com>
|
||||||
Alexander Shopov <ash@kambanaria.org>
|
Alexander Shopov <ash@kambanaria.org>
|
||||||
Alexandr Morozov <lk4d4math@gmail.com>
|
Alexandr Morozov <lk4d4@docker.com>
|
||||||
Alexey Kotlyarov <alexey@infoxchange.net.au>
|
Alexey Kotlyarov <alexey@infoxchange.net.au>
|
||||||
Alexey Shamrin <shamrin@gmail.com>
|
Alexey Shamrin <shamrin@gmail.com>
|
||||||
|
Alex Gaynor <alex.gaynor@gmail.com>
|
||||||
Alexis THOMAS <fr.alexisthomas@gmail.com>
|
Alexis THOMAS <fr.alexisthomas@gmail.com>
|
||||||
|
Alex Warhawk <ax.warhawk@gmail.com>
|
||||||
almoehi <almoehi@users.noreply.github.com>
|
almoehi <almoehi@users.noreply.github.com>
|
||||||
|
Al Tobey <al@ooyala.com>
|
||||||
|
Álvaro Lázaro <alvaro.lazaro.g@gmail.com>
|
||||||
amangoel <amangoel@gmail.com>
|
amangoel <amangoel@gmail.com>
|
||||||
|
Amit Bakshi <ambakshi@gmail.com>
|
||||||
AnandkumarPatel <anandkumarpatel@gmail.com>
|
AnandkumarPatel <anandkumarpatel@gmail.com>
|
||||||
Andre Dublin <81dublin@gmail.com>
|
Anand Patil <anand.prabhakar.patil@gmail.com>
|
||||||
Andrea Luzzardi <aluzzardi@gmail.com>
|
Andrea Luzzardi <aluzzardi@gmail.com>
|
||||||
Andrea Turli <andrea.turli@gmail.com>
|
Andreas Köhler <andi5.py@gmx.net>
|
||||||
Andreas Savvides <andreas@editd.com>
|
Andreas Savvides <andreas@editd.com>
|
||||||
Andreas Tiefenthaler <at@an-ti.eu>
|
Andreas Tiefenthaler <at@an-ti.eu>
|
||||||
|
Andrea Turli <andrea.turli@gmail.com>
|
||||||
|
Andre Dublin <81dublin@gmail.com>
|
||||||
Andrew Duckworth <grillopress@gmail.com>
|
Andrew Duckworth <grillopress@gmail.com>
|
||||||
Andrew France <andrew@avito.co.uk>
|
Andrew France <andrew@avito.co.uk>
|
||||||
Andrew Macgregor <andrew.macgregor@agworld.com.au>
|
Andrew Macgregor <andrew.macgregor@agworld.com.au>
|
||||||
Andrew Munsell <andrew@wizardapps.net>
|
Andrew Munsell <andrew@wizardapps.net>
|
||||||
|
Andrews Medina <andrewsmedina@gmail.com>
|
||||||
Andrew Weiss <andrew.weiss@outlook.com>
|
Andrew Weiss <andrew.weiss@outlook.com>
|
||||||
Andrew Williams <williams.andrew@gmail.com>
|
Andrew Williams <williams.andrew@gmail.com>
|
||||||
Andrews Medina <andrewsmedina@gmail.com>
|
Andrey Petrov <andrey.petrov@shazow.net>
|
||||||
|
Andrey Stolbovsky <andrey.stolbovsky@gmail.com>
|
||||||
Andy Chambers <anchambers@paypal.com>
|
Andy Chambers <anchambers@paypal.com>
|
||||||
andy diller <dillera@gmail.com>
|
andy diller <dillera@gmail.com>
|
||||||
Andy Goldstein <agoldste@redhat.com>
|
Andy Goldstein <agoldste@redhat.com>
|
||||||
Andy Kipp <andy@rstudio.com>
|
Andy Kipp <andy@rstudio.com>
|
||||||
Andy Rothfusz <github@developersupport.net>
|
Andy Rothfusz <github@developersupport.net>
|
||||||
Andy Smith <github@anarkystic.com>
|
Andy Smith <github@anarkystic.com>
|
||||||
|
Andy Wilson <wilson.andrew.j+github@gmail.com>
|
||||||
Anthony Bishopric <git@anthonybishopric.com>
|
Anthony Bishopric <git@anthonybishopric.com>
|
||||||
Anton Löfgren <anton.lofgren@gmail.com>
|
Anton Löfgren <anton.lofgren@gmail.com>
|
||||||
Anton Nikitin <anton.k.nikitin@gmail.com>
|
Anton Nikitin <anton.k.nikitin@gmail.com>
|
||||||
Antony Messerli <amesserl@rackspace.com>
|
Antony Messerli <amesserl@rackspace.com>
|
||||||
apocas <petermdias@gmail.com>
|
apocas <petermdias@gmail.com>
|
||||||
Arnaud Porterie <icecrime@gmail.com>
|
ArikaChen <eaglesora@gmail.com>
|
||||||
|
Arnaud Porterie <arnaud.porterie@docker.com>
|
||||||
|
Arthur Gautier <baloo@gandi.net>
|
||||||
Asbjørn Enge <asbjorn@hanafjedle.net>
|
Asbjørn Enge <asbjorn@hanafjedle.net>
|
||||||
|
averagehuman <averagehuman@users.noreply.github.com>
|
||||||
|
Avi Miller <avi.miller@oracle.com>
|
||||||
Barnaby Gray <barnaby@pickle.me.uk>
|
Barnaby Gray <barnaby@pickle.me.uk>
|
||||||
Barry Allard <barry.allard@gmail.com>
|
Barry Allard <barry.allard@gmail.com>
|
||||||
Bartłomiej Piotrowski <b@bpiotrowski.pl>
|
Bartłomiej Piotrowski <b@bpiotrowski.pl>
|
||||||
bdevloed <boris.de.vloed@gmail.com>
|
bdevloed <boris.de.vloed@gmail.com>
|
||||||
Ben Firshman <ben@firshman.co.uk>
|
Ben Firshman <ben@firshman.co.uk>
|
||||||
|
Benjamin Atkin <ben@benatkin.com>
|
||||||
|
Benoit Chesneau <bchesneau@gmail.com>
|
||||||
Ben Sargent <ben@brokendigits.com>
|
Ben Sargent <ben@brokendigits.com>
|
||||||
Ben Toews <mastahyeti@gmail.com>
|
Ben Toews <mastahyeti@gmail.com>
|
||||||
Ben Wiklund <ben@daisyowl.com>
|
Ben Wiklund <ben@daisyowl.com>
|
||||||
Benjamin Atkin <ben@benatkin.com>
|
|
||||||
Benoit Chesneau <bchesneau@gmail.com>
|
|
||||||
Bernerd Schaefer <bj.schaefer@gmail.com>
|
Bernerd Schaefer <bj.schaefer@gmail.com>
|
||||||
|
Bert Goethals <bert@bertg.be>
|
||||||
Bhiraj Butala <abhiraj.butala@gmail.com>
|
Bhiraj Butala <abhiraj.butala@gmail.com>
|
||||||
bin liu <liubin0329@users.noreply.github.com>
|
bin liu <liubin0329@users.noreply.github.com>
|
||||||
|
Blake Geno <blakegeno@gmail.com>
|
||||||
Bouke Haarsma <bouke@webatoom.nl>
|
Bouke Haarsma <bouke@webatoom.nl>
|
||||||
Boyd Hemphill <boyd@feedmagnet.com>
|
Boyd Hemphill <boyd@feedmagnet.com>
|
||||||
Brandon Liu <bdon@bdon.org>
|
Brandon Liu <bdon@bdon.org>
|
||||||
|
@ -80,10 +98,13 @@ Brian Shumate <brian@couchbase.com>
|
||||||
Brice Jaglin <bjaglin@teads.tv>
|
Brice Jaglin <bjaglin@teads.tv>
|
||||||
Briehan Lombaard <briehan.lombaard@gmail.com>
|
Briehan Lombaard <briehan.lombaard@gmail.com>
|
||||||
Bruno Bigras <bigras.bruno@gmail.com>
|
Bruno Bigras <bigras.bruno@gmail.com>
|
||||||
|
Bruno Binet <bruno.binet@gmail.com>
|
||||||
Bruno Renié <brutasse@gmail.com>
|
Bruno Renié <brutasse@gmail.com>
|
||||||
Bryan Bess <squarejaw@bsbess.com>
|
Bryan Bess <squarejaw@bsbess.com>
|
||||||
Bryan Matsuo <bryan.matsuo@gmail.com>
|
Bryan Matsuo <bryan.matsuo@gmail.com>
|
||||||
Bryan Murphy <bmurphy1976@gmail.com>
|
Bryan Murphy <bmurphy1976@gmail.com>
|
||||||
|
Burke Libbey <burke@libbey.me>
|
||||||
|
Byung Kang <byung.kang.ctr@amrdec.army.mil>
|
||||||
Caleb Spare <cespare@gmail.com>
|
Caleb Spare <cespare@gmail.com>
|
||||||
Calen Pennington <cale@edx.org>
|
Calen Pennington <cale@edx.org>
|
||||||
Cameron Boehmer <cameron.boehmer@gmail.com>
|
Cameron Boehmer <cameron.boehmer@gmail.com>
|
||||||
|
@ -95,56 +116,68 @@ Charlie Lewis <charliel@lab41.org>
|
||||||
Chewey <prosto-chewey@users.noreply.github.com>
|
Chewey <prosto-chewey@users.noreply.github.com>
|
||||||
Chia-liang Kao <clkao@clkao.org>
|
Chia-liang Kao <clkao@clkao.org>
|
||||||
Chris Alfonso <calfonso@redhat.com>
|
Chris Alfonso <calfonso@redhat.com>
|
||||||
|
Chris Armstrong <chris@opdemand.com>
|
||||||
|
chrismckinnel <chris.mckinnel@tangentlabs.co.uk>
|
||||||
Chris Snow <chsnow123@gmail.com>
|
Chris Snow <chsnow123@gmail.com>
|
||||||
Chris St. Pierre <chris.a.st.pierre@gmail.com>
|
Chris St. Pierre <chris.a.st.pierre@gmail.com>
|
||||||
chrismckinnel <chris.mckinnel@tangentlabs.co.uk>
|
|
||||||
Christian Berendt <berendt@b1-systems.de>
|
Christian Berendt <berendt@b1-systems.de>
|
||||||
ChristoperBiscardi <biscarch@sketcht.com>
|
ChristoperBiscardi <biscarch@sketcht.com>
|
||||||
Christophe Troestler <christophe.Troestler@umons.ac.be>
|
|
||||||
Christopher Currie <codemonkey+github@gmail.com>
|
Christopher Currie <codemonkey+github@gmail.com>
|
||||||
Christopher Rigor <crigor@gmail.com>
|
Christopher Rigor <crigor@gmail.com>
|
||||||
|
Christophe Troestler <christophe.Troestler@umons.ac.be>
|
||||||
Ciro S. Costa <ciro.costa@usp.br>
|
Ciro S. Costa <ciro.costa@usp.br>
|
||||||
Clayton Coleman <ccoleman@redhat.com>
|
Clayton Coleman <ccoleman@redhat.com>
|
||||||
Colin Dunklau <colin.dunklau@gmail.com>
|
Colin Dunklau <colin.dunklau@gmail.com>
|
||||||
Colin Rice <colin@daedrum.net>
|
Colin Rice <colin@daedrum.net>
|
||||||
Colin Walters <walters@verbum.org>
|
Colin Walters <walters@verbum.org>
|
||||||
Cory Forsyth <cory.forsyth@gmail.com>
|
Cory Forsyth <cory.forsyth@gmail.com>
|
||||||
cpuguy83 <cpuguy83@gmail.com>
|
|
||||||
cressie176 <github@stephen-cresswell.net>
|
cressie176 <github@stephen-cresswell.net>
|
||||||
Cruceru Calin-Cristian <crucerucalincristian@gmail.com>
|
Cruceru Calin-Cristian <crucerucalincristian@gmail.com>
|
||||||
Daan van Berkel <daan.v.berkel.1980@gmail.com>
|
Daan van Berkel <daan.v.berkel.1980@gmail.com>
|
||||||
|
Daehyeok.Mun <daehyeok@gmail.com>
|
||||||
Dafydd Crosby <dtcrsby@gmail.com>
|
Dafydd Crosby <dtcrsby@gmail.com>
|
||||||
Dan Buch <d.buch@modcloth.com>
|
Dan Buch <d.buch@modcloth.com>
|
||||||
|
Dan Cotora <dan@bluevision.ro>
|
||||||
|
Dan Griffin <dgriffin@peer1.com>
|
||||||
Dan Hirsch <thequux@upstandinghackers.com>
|
Dan Hirsch <thequux@upstandinghackers.com>
|
||||||
Dan Keder <dan.keder@gmail.com>
|
Daniel, Dao Quang Minh <dqminh89@gmail.com>
|
||||||
Dan McPherson <dmcphers@redhat.com>
|
|
||||||
Dan Stine <sw@stinemail.com>
|
|
||||||
Dan Walsh <dwalsh@redhat.com>
|
|
||||||
Dan Williams <me@deedubs.com>
|
|
||||||
Daniel Exner <dex@dragonslave.de>
|
Daniel Exner <dex@dragonslave.de>
|
||||||
|
Daniel Farrell <dfarrell@redhat.com>
|
||||||
Daniel Garcia <daniel@danielgarcia.info>
|
Daniel Garcia <daniel@danielgarcia.info>
|
||||||
Daniel Gasienica <daniel@gasienica.ch>
|
Daniel Gasienica <daniel@gasienica.ch>
|
||||||
|
Daniel Menet <membership@sontags.ch>
|
||||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
|
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
|
||||||
Daniel Norberg <dano@spotify.com>
|
Daniel Norberg <dano@spotify.com>
|
||||||
Daniel Nordberg <dnordberg@gmail.com>
|
Daniel Nordberg <dnordberg@gmail.com>
|
||||||
Daniel Robinson <gottagetmac@gmail.com>
|
Daniel Robinson <gottagetmac@gmail.com>
|
||||||
Daniel Von Fange <daniel@leancoder.com>
|
Daniel Von Fange <daniel@leancoder.com>
|
||||||
Daniel YC Lin <dlin.tw@gmail.com>
|
Daniel YC Lin <dlin.tw@gmail.com>
|
||||||
Daniel, Dao Quang Minh <dqminh89@gmail.com>
|
Dan Keder <dan.keder@gmail.com>
|
||||||
|
Dan McPherson <dmcphers@redhat.com>
|
||||||
Danny Berger <dpb587@gmail.com>
|
Danny Berger <dpb587@gmail.com>
|
||||||
Danny Yates <danny@codeaholics.org>
|
Danny Yates <danny@codeaholics.org>
|
||||||
|
Dan Stine <sw@stinemail.com>
|
||||||
|
Dan Walsh <dwalsh@redhat.com>
|
||||||
|
Dan Williams <me@deedubs.com>
|
||||||
Darren Coxall <darren@darrencoxall.com>
|
Darren Coxall <darren@darrencoxall.com>
|
||||||
Darren Shepherd <darren.s.shepherd@gmail.com>
|
Darren Shepherd <darren.s.shepherd@gmail.com>
|
||||||
David Anderson <dave@natulte.net>
|
David Anderson <dave@natulte.net>
|
||||||
David Calavera <david.calavera@gmail.com>
|
David Calavera <david.calavera@gmail.com>
|
||||||
David Corking <dmc-source@dcorking.com>
|
David Corking <dmc-source@dcorking.com>
|
||||||
|
Davide Ceretti <davide.ceretti@hogarthww.com>
|
||||||
David Gageot <david@gageot.net>
|
David Gageot <david@gageot.net>
|
||||||
|
David Gebler <davidgebler@gmail.com>
|
||||||
David Mcanulty <github@hellspark.com>
|
David Mcanulty <github@hellspark.com>
|
||||||
|
David Pelaez <pelaez89@gmail.com>
|
||||||
David Röthlisberger <david@rothlis.net>
|
David Röthlisberger <david@rothlis.net>
|
||||||
David Sissitka <me@dsissitka.com>
|
David Sissitka <me@dsissitka.com>
|
||||||
|
Dawn Chen <dawnchen@google.com>
|
||||||
|
decadent <decadent@users.noreply.github.com>
|
||||||
Deni Bertovic <deni@kset.org>
|
Deni Bertovic <deni@kset.org>
|
||||||
Derek <crq@kernel.org>
|
Derek <crq@kernel.org>
|
||||||
|
Derek McGowan <derek@mcgstyle.net>
|
||||||
Deric Crago <deric.crago@gmail.com>
|
Deric Crago <deric.crago@gmail.com>
|
||||||
|
Deshi Xiao <dxiao@redhat.com>
|
||||||
Dinesh Subhraveti <dineshs@altiscale.com>
|
Dinesh Subhraveti <dineshs@altiscale.com>
|
||||||
Djibril Koné <kone.djibril@gmail.com>
|
Djibril Koné <kone.djibril@gmail.com>
|
||||||
dkumor <daniel@dkumor.com>
|
dkumor <daniel@dkumor.com>
|
||||||
|
@ -154,11 +187,13 @@ Dominik Honnef <dominik@honnef.co>
|
||||||
Don Spaulding <donspauldingii@gmail.com>
|
Don Spaulding <donspauldingii@gmail.com>
|
||||||
Doug Davis <dug@us.ibm.com>
|
Doug Davis <dug@us.ibm.com>
|
||||||
doug tangren <d.tangren@gmail.com>
|
doug tangren <d.tangren@gmail.com>
|
||||||
Dr Nic Williams <drnicwilliams@gmail.com>
|
dragon788 <dragon788@users.noreply.github.com>
|
||||||
Dražen Lučanin <kermit666@gmail.com>
|
Dražen Lučanin <kermit666@gmail.com>
|
||||||
|
Dr Nic Williams <drnicwilliams@gmail.com>
|
||||||
Dustin Sallings <dustin@spy.net>
|
Dustin Sallings <dustin@spy.net>
|
||||||
Edmund Wagner <edmund-wagner@web.de>
|
Edmund Wagner <edmund-wagner@web.de>
|
||||||
Eiichi Tsukata <devel@etsukata.com>
|
Eiichi Tsukata <devel@etsukata.com>
|
||||||
|
Eike Herzbach <eike@herzbach.net>
|
||||||
Eivind Uggedal <eivind@uggedal.com>
|
Eivind Uggedal <eivind@uggedal.com>
|
||||||
Elias Probst <mail@eliasprobst.eu>
|
Elias Probst <mail@eliasprobst.eu>
|
||||||
Emil Hernvall <emil@quench.at>
|
Emil Hernvall <emil@quench.at>
|
||||||
|
@ -166,17 +201,19 @@ Emily Rose <emily@contactvibe.com>
|
||||||
Eric Hanchrow <ehanchrow@ine.com>
|
Eric Hanchrow <ehanchrow@ine.com>
|
||||||
Eric Lee <thenorthsecedes@gmail.com>
|
Eric Lee <thenorthsecedes@gmail.com>
|
||||||
Eric Myhre <hash@exultant.us>
|
Eric Myhre <hash@exultant.us>
|
||||||
Eric Windisch <eric@windisch.us>
|
Eric Paris <eparis@redhat.com>
|
||||||
Eric Windisch <ewindisch@docker.com>
|
Eric Windisch <ewindisch@docker.com>
|
||||||
Erik Hollensbe <github@hollensbe.org>
|
Erik Hollensbe <github@hollensbe.org>
|
||||||
Erik Inge Bolsø <knan@redpill-linpro.com>
|
Erik Inge Bolsø <knan@redpill-linpro.com>
|
||||||
|
Erik Kristensen <erik@erikkristensen.com>
|
||||||
Erno Hopearuoho <erno.hopearuoho@gmail.com>
|
Erno Hopearuoho <erno.hopearuoho@gmail.com>
|
||||||
|
Eugene Yakubovich <eugene.yakubovich@coreos.com>
|
||||||
eugenkrizo <eugen.krizo@gmail.com>
|
eugenkrizo <eugen.krizo@gmail.com>
|
||||||
|
evanderkoogh <info@erronis.nl>
|
||||||
Evan Hazlett <ejhazlett@gmail.com>
|
Evan Hazlett <ejhazlett@gmail.com>
|
||||||
Evan Krall <krall@yelp.com>
|
Evan Krall <krall@yelp.com>
|
||||||
Evan Phoenix <evan@fallingsnow.net>
|
Evan Phoenix <evan@fallingsnow.net>
|
||||||
Evan Wies <evan@neomantra.net>
|
Evan Wies <evan@neomantra.net>
|
||||||
evanderkoogh <info@erronis.nl>
|
|
||||||
Eystein Måløy Stenberg <eystein.maloy.stenberg@cfengine.com>
|
Eystein Måløy Stenberg <eystein.maloy.stenberg@cfengine.com>
|
||||||
ezbercih <cem.ezberci@gmail.com>
|
ezbercih <cem.ezberci@gmail.com>
|
||||||
Fabio Falci <fabiofalci@gmail.com>
|
Fabio Falci <fabiofalci@gmail.com>
|
||||||
|
@ -186,49 +223,60 @@ Faiz Khan <faizkhan00@gmail.com>
|
||||||
Fareed Dudhia <fareeddudhia@googlemail.com>
|
Fareed Dudhia <fareeddudhia@googlemail.com>
|
||||||
Felix Rabe <felix@rabe.io>
|
Felix Rabe <felix@rabe.io>
|
||||||
Fernando <fermayo@gmail.com>
|
Fernando <fermayo@gmail.com>
|
||||||
|
Filipe Brandenburger <filbranden@google.com>
|
||||||
Flavio Castelli <fcastelli@suse.com>
|
Flavio Castelli <fcastelli@suse.com>
|
||||||
FLGMwt <ryan.stelly@live.com>
|
FLGMwt <ryan.stelly@live.com>
|
||||||
Francisco Carriedo <fcarriedo@gmail.com>
|
Francisco Carriedo <fcarriedo@gmail.com>
|
||||||
Francisco Souza <f@souza.cc>
|
Francisco Souza <f@souza.cc>
|
||||||
Frank Macreery <frank@macreery.com>
|
Frank Macreery <frank@macreery.com>
|
||||||
Fred Lifton <fred.lifton@docker.com>
|
Frank Rosquin <frank.rosquin+github@gmail.com>
|
||||||
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
||||||
Frederik Loeffert <frederik@zitrusmedia.de>
|
Frederik Loeffert <frederik@zitrusmedia.de>
|
||||||
|
Fred Lifton <fred.lifton@docker.com>
|
||||||
Freek Kalter <freek@kalteronline.org>
|
Freek Kalter <freek@kalteronline.org>
|
||||||
Gabe Rosenhouse <gabe@missionst.com>
|
Gabe Rosenhouse <gabe@missionst.com>
|
||||||
Gabor Nagy <mail@aigeruth.hu>
|
Gabor Nagy <mail@aigeruth.hu>
|
||||||
Gabriel Monroy <gabriel@opdemand.com>
|
Gabriel Monroy <gabriel@opdemand.com>
|
||||||
Galen Sampson <galen.sampson@gmail.com>
|
Galen Sampson <galen.sampson@gmail.com>
|
||||||
Gareth Rushgrove <gareth@morethanseven.net>
|
Gareth Rushgrove <gareth@morethanseven.net>
|
||||||
|
gautam, prasanna <prasannagautam@gmail.com>
|
||||||
Geoffrey Bachelet <grosfrais@gmail.com>
|
Geoffrey Bachelet <grosfrais@gmail.com>
|
||||||
|
George Xie <georgexsh@gmail.com>
|
||||||
Gereon Frey <gereon.frey@dynport.de>
|
Gereon Frey <gereon.frey@dynport.de>
|
||||||
German DZ <germ@ndz.com.ar>
|
German DZ <germ@ndz.com.ar>
|
||||||
Gert van Valkenhoef <g.h.m.van.valkenhoef@rug.nl>
|
Gert van Valkenhoef <g.h.m.van.valkenhoef@rug.nl>
|
||||||
Giuseppe Mazzotta <gdm85@users.noreply.github.com>
|
Giuseppe Mazzotta <gdm85@users.noreply.github.com>
|
||||||
Gleb Fotengauer-Malinovskiy <glebfm@altlinux.org>
|
Gleb Fotengauer-Malinovskiy <glebfm@altlinux.org>
|
||||||
|
Gleb M Borisov <borisov.gleb@gmail.com>
|
||||||
Glyn Normington <gnormington@gopivotal.com>
|
Glyn Normington <gnormington@gopivotal.com>
|
||||||
Goffert van Gool <goffert@phusion.nl>
|
Goffert van Gool <goffert@phusion.nl>
|
||||||
|
golubbe <ben.golub@dotcloud.com>
|
||||||
Graydon Hoare <graydon@pobox.com>
|
Graydon Hoare <graydon@pobox.com>
|
||||||
Greg Thornton <xdissent@me.com>
|
Greg Thornton <xdissent@me.com>
|
||||||
grunny <mwgrunny@gmail.com>
|
grunny <mwgrunny@gmail.com>
|
||||||
Guilherme Salgado <gsalgado@gmail.com>
|
Guilherme Salgado <gsalgado@gmail.com>
|
||||||
|
Guillaume Dufour <gdufour.prestataire@voyages-sncf.com>
|
||||||
Guillaume J. Charmes <guillaume.charmes@docker.com>
|
Guillaume J. Charmes <guillaume.charmes@docker.com>
|
||||||
Gurjeet Singh <gurjeet@singh.im>
|
Gurjeet Singh <gurjeet@singh.im>
|
||||||
Guruprasad <lgp171188@gmail.com>
|
Guruprasad <lgp171188@gmail.com>
|
||||||
|
Hans Rødtang <hansrodtang@gmail.com>
|
||||||
Harald Albers <github@albersweb.de>
|
Harald Albers <github@albersweb.de>
|
||||||
Harley Laue <losinggeneration@gmail.com>
|
Harley Laue <losinggeneration@gmail.com>
|
||||||
Hector Castro <hectcastro@gmail.com>
|
Hector Castro <hectcastro@gmail.com>
|
||||||
Henning Sprang <henning.sprang@gmail.com>
|
Henning Sprang <henning.sprang@gmail.com>
|
||||||
Hobofan <goisser94@gmail.com>
|
Hobofan <goisser94@gmail.com>
|
||||||
Hollie Teal <hollie.teal@docker.com>
|
Hollie Teal <hollie@docker.com>
|
||||||
Hollie Teal <hollietealok@users.noreply.github.com>
|
Huayi Zhang <irachex@gmail.com>
|
||||||
hollietealok <hollie@docker.com>
|
Hugo Duncan <hugo@hugoduncan.org>
|
||||||
Hunter Blanks <hunter@twilio.com>
|
Hunter Blanks <hunter@twilio.com>
|
||||||
|
Hu Tao <hutao@cn.fujitsu.com>
|
||||||
|
Huu Nguyen <huu@prismskylabs.com>
|
||||||
hyeongkyu.lee <hyeongkyu.lee@navercorp.com>
|
hyeongkyu.lee <hyeongkyu.lee@navercorp.com>
|
||||||
Ian Babrou <ibobrik@gmail.com>
|
Ian Babrou <ibobrik@gmail.com>
|
||||||
Ian Bull <irbull@gmail.com>
|
Ian Bull <irbull@gmail.com>
|
||||||
Ian Main <imain@redhat.com>
|
Ian Main <imain@redhat.com>
|
||||||
Ian Truslove <ian.truslove@gmail.com>
|
Ian Truslove <ian.truslove@gmail.com>
|
||||||
|
Igor Dolzhikov <bluesriverz@gmail.com>
|
||||||
ILYA Khlopotov <ilya.khlopotov@gmail.com>
|
ILYA Khlopotov <ilya.khlopotov@gmail.com>
|
||||||
inglesp <peter.inglesby@gmail.com>
|
inglesp <peter.inglesby@gmail.com>
|
||||||
Isaac Dupree <antispam@idupree.com>
|
Isaac Dupree <antispam@idupree.com>
|
||||||
|
@ -236,8 +284,8 @@ Isabel Jimenez <contact.isabeljimenez@gmail.com>
|
||||||
Isao Jonas <isao.jonas@gmail.com>
|
Isao Jonas <isao.jonas@gmail.com>
|
||||||
Ivan Fraixedes <ifcdev@gmail.com>
|
Ivan Fraixedes <ifcdev@gmail.com>
|
||||||
Jack Danger Canty <jackdanger@squareup.com>
|
Jack Danger Canty <jackdanger@squareup.com>
|
||||||
Jake Moshenko <jake@devtable.com>
|
|
||||||
jakedt <jake@devtable.com>
|
jakedt <jake@devtable.com>
|
||||||
|
Jake Moshenko <jake@devtable.com>
|
||||||
James Allen <jamesallen0108@gmail.com>
|
James Allen <jamesallen0108@gmail.com>
|
||||||
James Carr <james.r.carr@gmail.com>
|
James Carr <james.r.carr@gmail.com>
|
||||||
James DeFelice <james.defelice@ishisystems.com>
|
James DeFelice <james.defelice@ishisystems.com>
|
||||||
|
@ -245,6 +293,7 @@ James Harrison Fisher <jameshfisher@gmail.com>
|
||||||
James Kyle <james@jameskyle.org>
|
James Kyle <james@jameskyle.org>
|
||||||
James Mills <prologic@shortcircuit.net.au>
|
James Mills <prologic@shortcircuit.net.au>
|
||||||
James Turnbull <james@lovedthanlost.net>
|
James Turnbull <james@lovedthanlost.net>
|
||||||
|
Jan Keromnes <janx@linux.com>
|
||||||
Jan Pazdziora <jpazdziora@redhat.com>
|
Jan Pazdziora <jpazdziora@redhat.com>
|
||||||
Jan Toebes <jan@toebes.info>
|
Jan Toebes <jan@toebes.info>
|
||||||
Jaroslaw Zabiello <hipertracker@gmail.com>
|
Jaroslaw Zabiello <hipertracker@gmail.com>
|
||||||
|
@ -256,31 +305,35 @@ Jason McVetta <jason.mcvetta@gmail.com>
|
||||||
Jason Plum <jplum@devonit.com>
|
Jason Plum <jplum@devonit.com>
|
||||||
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
||||||
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||||
|
Jean-Paul Calderone <exarkun@twistedmatrix.com>
|
||||||
Jeff Lindsay <progrium@gmail.com>
|
Jeff Lindsay <progrium@gmail.com>
|
||||||
Jeff Welch <whatthejeff@gmail.com>
|
|
||||||
Jeffrey Bolle <jeffreybolle@gmail.com>
|
Jeffrey Bolle <jeffreybolle@gmail.com>
|
||||||
|
Jeff Welch <whatthejeff@gmail.com>
|
||||||
Jeremy Grosser <jeremy@synack.me>
|
Jeremy Grosser <jeremy@synack.me>
|
||||||
|
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
|
||||||
Jesse Dubay <jesse@thefortytwo.net>
|
Jesse Dubay <jesse@thefortytwo.net>
|
||||||
|
Jessica Frazelle <jess@docker.com>
|
||||||
Jezeniel Zapanta <jpzapanta22@gmail.com>
|
Jezeniel Zapanta <jpzapanta22@gmail.com>
|
||||||
Jilles Oldenbeuving <ojilles@gmail.com>
|
Jilles Oldenbeuving <ojilles@gmail.com>
|
||||||
Jim Alateras <jima@comware.com.au>
|
Jim Alateras <jima@comware.com.au>
|
||||||
Jim Perrin <jperrin@centos.org>
|
|
||||||
Jimmy Cuadra <jimmy@jimmycuadra.com>
|
Jimmy Cuadra <jimmy@jimmycuadra.com>
|
||||||
|
Jim Perrin <jperrin@centos.org>
|
||||||
Jiří Župka <jzupka@redhat.com>
|
Jiří Župka <jzupka@redhat.com>
|
||||||
Joe Beda <joe.github@bedafamily.com>
|
Joe Beda <joe.github@bedafamily.com>
|
||||||
|
Joe Ferguson <joe@infosiftr.com>
|
||||||
|
Joel Handwell <joelhandwell@gmail.com>
|
||||||
Joe Shaw <joe@joeshaw.org>
|
Joe Shaw <joe@joeshaw.org>
|
||||||
Joe Van Dyk <joe@tanga.com>
|
Joe Van Dyk <joe@tanga.com>
|
||||||
Joel Handwell <joelhandwell@gmail.com>
|
|
||||||
Joffrey F <joffrey@docker.com>
|
Joffrey F <joffrey@docker.com>
|
||||||
Johan Euphrosine <proppy@google.com>
|
Johan Euphrosine <proppy@google.com>
|
||||||
Johan Rydberg <johan.rydberg@gmail.com>
|
|
||||||
Johannes 'fish' Ziemke <github@freigeist.org>
|
Johannes 'fish' Ziemke <github@freigeist.org>
|
||||||
|
Johan Rydberg <johan.rydberg@gmail.com>
|
||||||
John Costa <john.costa@gmail.com>
|
John Costa <john.costa@gmail.com>
|
||||||
John Feminella <jxf@jxf.me>
|
John Feminella <jxf@jxf.me>
|
||||||
John Gardiner Myers <jgmyers@proofpoint.com>
|
John Gardiner Myers <jgmyers@proofpoint.com>
|
||||||
|
John Gossman <johngos@microsoft.com>
|
||||||
John OBrien III <jobrieniii@yahoo.com>
|
John OBrien III <jobrieniii@yahoo.com>
|
||||||
John Warwick <jwarwick@gmail.com>
|
John Warwick <jwarwick@gmail.com>
|
||||||
Jon Wedaman <jweede@gmail.com>
|
|
||||||
Jonas Pfenniger <jonas@pfenniger.name>
|
Jonas Pfenniger <jonas@pfenniger.name>
|
||||||
Jonathan Boulle <jonathanboulle@gmail.com>
|
Jonathan Boulle <jonathanboulle@gmail.com>
|
||||||
Jonathan Camp <jonathan@irondojo.com>
|
Jonathan Camp <jonathan@irondojo.com>
|
||||||
|
@ -288,22 +341,25 @@ Jonathan McCrohan <jmccrohan@gmail.com>
|
||||||
Jonathan Mueller <j.mueller@apoveda.ch>
|
Jonathan Mueller <j.mueller@apoveda.ch>
|
||||||
Jonathan Pares <jonathanpa@users.noreply.github.com>
|
Jonathan Pares <jonathanpa@users.noreply.github.com>
|
||||||
Jonathan Rudenberg <jonathan@titanous.com>
|
Jonathan Rudenberg <jonathan@titanous.com>
|
||||||
|
Jon Wedaman <jweede@gmail.com>
|
||||||
Joost Cassee <joost@cassee.net>
|
Joost Cassee <joost@cassee.net>
|
||||||
Jordan Arentsen <blissdev@gmail.com>
|
Jordan Arentsen <blissdev@gmail.com>
|
||||||
Jordan Sissel <jls@semicomplete.com>
|
Jordan Sissel <jls@semicomplete.com>
|
||||||
Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
|
Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
|
||||||
Joseph Hager <ajhager@gmail.com>
|
Joseph Hager <ajhager@gmail.com>
|
||||||
Josh <jokajak@gmail.com>
|
|
||||||
Josh Hawn <josh.hawn@docker.com>
|
Josh Hawn <josh.hawn@docker.com>
|
||||||
|
Josh <jokajak@gmail.com>
|
||||||
Josh Poimboeuf <jpoimboe@redhat.com>
|
Josh Poimboeuf <jpoimboe@redhat.com>
|
||||||
|
Josiah Kiehl <jkiehl@riotgames.com>
|
||||||
JP <jpellerin@leapfrogonline.com>
|
JP <jpellerin@leapfrogonline.com>
|
||||||
|
Julian Taylor <jtaylor.debian@googlemail.com>
|
||||||
Julien Barbier <write0@gmail.com>
|
Julien Barbier <write0@gmail.com>
|
||||||
Julien Bordellier <julienbordellier@gmail.com>
|
Julien Bordellier <julienbordellier@gmail.com>
|
||||||
Julien Dubois <julien.dubois@gmail.com>
|
Julien Dubois <julien.dubois@gmail.com>
|
||||||
Justin Force <justin.force@gmail.com>
|
Justin Force <justin.force@gmail.com>
|
||||||
Justin Plock <jplock@users.noreply.github.com>
|
Justin Plock <jplock@users.noreply.github.com>
|
||||||
Justin Simonelis <justin.p.simonelis@gmail.com>
|
Justin Simonelis <justin.p.simonelis@gmail.com>
|
||||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
|
Jyrki Puttonen <jyrkiput@gmail.com>
|
||||||
Karan Lyons <karan@karanlyons.com>
|
Karan Lyons <karan@karanlyons.com>
|
||||||
Karl Grzeszczak <karlgrz@gmail.com>
|
Karl Grzeszczak <karlgrz@gmail.com>
|
||||||
Kato Kazuyoshi <kato.kazuyoshi@gmail.com>
|
Kato Kazuyoshi <kato.kazuyoshi@gmail.com>
|
||||||
|
@ -311,57 +367,68 @@ Kawsar Saiyeed <kawsar.saiyeed@projiris.com>
|
||||||
Keli Hu <dev@keli.hu>
|
Keli Hu <dev@keli.hu>
|
||||||
Ken Cochrane <kencochrane@gmail.com>
|
Ken Cochrane <kencochrane@gmail.com>
|
||||||
Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com>
|
Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com>
|
||||||
Kevin "qwazerty" Houdebert <kevin.houdebert@gmail.com>
|
|
||||||
Kevin Clark <kevin.clark@gmail.com>
|
Kevin Clark <kevin.clark@gmail.com>
|
||||||
Kevin J. Lynagh <kevin@keminglabs.com>
|
Kevin J. Lynagh <kevin@keminglabs.com>
|
||||||
Kevin Menard <kevin@nirvdrum.com>
|
Kevin Menard <kevin@nirvdrum.com>
|
||||||
|
Kevin "qwazerty" Houdebert <kevin.houdebert@gmail.com>
|
||||||
Kevin Wallace <kevin@pentabarf.net>
|
Kevin Wallace <kevin@pentabarf.net>
|
||||||
Keyvan Fatehi <keyvanfatehi@gmail.com>
|
Keyvan Fatehi <keyvanfatehi@gmail.com>
|
||||||
kies <lleelm@gmail.com>
|
kies <lleelm@gmail.com>
|
||||||
Kim BKC Carlbacker <kim.carlbacker@gmail.com>
|
|
||||||
kim0 <email.ahmedkamal@googlemail.com>
|
kim0 <email.ahmedkamal@googlemail.com>
|
||||||
|
Kim BKC Carlbacker <kim.carlbacker@gmail.com>
|
||||||
Kimbro Staken <kstaken@kstaken.com>
|
Kimbro Staken <kstaken@kstaken.com>
|
||||||
Kiran Gangadharan <kiran.daredevil@gmail.com>
|
Kiran Gangadharan <kiran.daredevil@gmail.com>
|
||||||
knappe <tyler.knappe@gmail.com>
|
knappe <tyler.knappe@gmail.com>
|
||||||
Kohei Tsuruta <coheyxyz@gmail.com>
|
Kohei Tsuruta <coheyxyz@gmail.com>
|
||||||
|
Konrad Kleine <konrad.wilhelm.kleine@gmail.com>
|
||||||
Konstantin Pelykh <kpelykh@zettaset.com>
|
Konstantin Pelykh <kpelykh@zettaset.com>
|
||||||
|
krrg <krrgithub@gmail.com>
|
||||||
Kyle Conroy <kyle.j.conroy@gmail.com>
|
Kyle Conroy <kyle.j.conroy@gmail.com>
|
||||||
kyu <leehk1227@gmail.com>
|
kyu <leehk1227@gmail.com>
|
||||||
Lachlan Coote <lcoote@vmware.com>
|
Lachlan Coote <lcoote@vmware.com>
|
||||||
|
Lajos Papp <lajos.papp@sequenceiq.com>
|
||||||
|
Lakshan Perera <lakshan@laktek.com>
|
||||||
lalyos <lalyos@yahoo.com>
|
lalyos <lalyos@yahoo.com>
|
||||||
Lance Chen <cyen0312@gmail.com>
|
Lance Chen <cyen0312@gmail.com>
|
||||||
Lars R. Damerow <lars@pixar.com>
|
Lars R. Damerow <lars@pixar.com>
|
||||||
Laurie Voss <github@seldo.com>
|
Laurie Voss <github@seldo.com>
|
||||||
leeplay <hyeongkyu.lee@navercorp.com>
|
leeplay <hyeongkyu.lee@navercorp.com>
|
||||||
|
Lei Jitang <leijitang@huawei.com>
|
||||||
Len Weincier <len@cloudafrica.net>
|
Len Weincier <len@cloudafrica.net>
|
||||||
|
Leszek Kowalski <github@leszekkowalski.pl>
|
||||||
Levi Gross <levi@levigross.com>
|
Levi Gross <levi@levigross.com>
|
||||||
Lewis Peckover <lew+github@lew.io>
|
Lewis Peckover <lew+github@lew.io>
|
||||||
Liang-Chi Hsieh <viirya@gmail.com>
|
Liang-Chi Hsieh <viirya@gmail.com>
|
||||||
|
limsy <seongyeol37@gmail.com>
|
||||||
Lokesh Mandvekar <lsm5@fedoraproject.org>
|
Lokesh Mandvekar <lsm5@fedoraproject.org>
|
||||||
Louis Opter <kalessin@kalessin.fr>
|
Louis Opter <kalessin@kalessin.fr>
|
||||||
lukaspustina <lukas.pustina@centerdevice.com>
|
lukaspustina <lukas.pustina@centerdevice.com>
|
||||||
lukemarsden <luke@digital-crocus.com>
|
lukemarsden <luke@digital-crocus.com>
|
||||||
|
Madhu Venugopal <madhu@socketplane.io>
|
||||||
Mahesh Tiyyagura <tmahesh@gmail.com>
|
Mahesh Tiyyagura <tmahesh@gmail.com>
|
||||||
|
Malte Janduda <mail@janduda.net>
|
||||||
Manfred Zabarauskas <manfredas@zabarauskas.com>
|
Manfred Zabarauskas <manfredas@zabarauskas.com>
|
||||||
Manuel Meurer <manuel@krautcomputing.com>
|
Manuel Meurer <manuel@krautcomputing.com>
|
||||||
Manuel Woelker <github@manuel.woelker.org>
|
Manuel Woelker <github@manuel.woelker.org>
|
||||||
Marc Abramowitz <marc@marc-abramowitz.com>
|
Marc Abramowitz <marc@marc-abramowitz.com>
|
||||||
Marc Kuo <kuomarc2@gmail.com>
|
Marc Kuo <kuomarc2@gmail.com>
|
||||||
Marc Tamsky <mtamsky@gmail.com>
|
|
||||||
Marco Hennings <marco.hennings@freiheit.com>
|
Marco Hennings <marco.hennings@freiheit.com>
|
||||||
|
Marc Tamsky <mtamsky@gmail.com>
|
||||||
Marcus Farkas <toothlessgear@finitebox.com>
|
Marcus Farkas <toothlessgear@finitebox.com>
|
||||||
Marcus Ramberg <marcus@nordaaker.com>
|
|
||||||
marcuslinke <marcus.linke@gmx.de>
|
marcuslinke <marcus.linke@gmx.de>
|
||||||
|
Marcus Ramberg <marcus@nordaaker.com>
|
||||||
Marek Goldmann <marek.goldmann@gmail.com>
|
Marek Goldmann <marek.goldmann@gmail.com>
|
||||||
Marius Voila <marius.voila@gmail.com>
|
Marius Voila <marius.voila@gmail.com>
|
||||||
Mark Allen <mrallen1@yahoo.com>
|
Mark Allen <mrallen1@yahoo.com>
|
||||||
Mark McGranaghan <mmcgrana@gmail.com>
|
Mark McGranaghan <mmcgrana@gmail.com>
|
||||||
Marko Mikulicic <mmikulicic@gmail.com>
|
Marko Mikulicic <mmikulicic@gmail.com>
|
||||||
|
Marko Tibold <marko@tibold.nl>
|
||||||
Markus Fix <lispmeister@gmail.com>
|
Markus Fix <lispmeister@gmail.com>
|
||||||
Martijn van Oosterhout <kleptog@svana.org>
|
Martijn van Oosterhout <kleptog@svana.org>
|
||||||
Martin Redmond <martin@tinychat.com>
|
Martin Redmond <martin@tinychat.com>
|
||||||
Mason Malone <mason.malone@gmail.com>
|
Mason Malone <mason.malone@gmail.com>
|
||||||
Mateusz Sulima <sulima.mateusz@gmail.com>
|
Mateusz Sulima <sulima.mateusz@gmail.com>
|
||||||
|
Mathias Monnerville <mathias@monnerville.com>
|
||||||
Mathieu Le Marec - Pasquet <kiorky@cryptelium.net>
|
Mathieu Le Marec - Pasquet <kiorky@cryptelium.net>
|
||||||
Matt Apperson <me@mattapperson.com>
|
Matt Apperson <me@mattapperson.com>
|
||||||
Matt Bachmann <bachmann.matt@gmail.com>
|
Matt Bachmann <bachmann.matt@gmail.com>
|
||||||
|
@ -372,17 +439,24 @@ Matthias Klumpp <matthias@tenstral.net>
|
||||||
Matthias Kühnle <git.nivoc@neverbox.com>
|
Matthias Kühnle <git.nivoc@neverbox.com>
|
||||||
mattymo <raytrac3r@gmail.com>
|
mattymo <raytrac3r@gmail.com>
|
||||||
mattyw <mattyw@me.com>
|
mattyw <mattyw@me.com>
|
||||||
Max Shytikov <mshytikov@gmail.com>
|
|
||||||
Maxim Treskin <zerthurd@gmail.com>
|
|
||||||
Maxime Petazzoni <max@signalfuse.com>
|
Maxime Petazzoni <max@signalfuse.com>
|
||||||
|
Maxim Treskin <zerthurd@gmail.com>
|
||||||
|
Max Shytikov <mshytikov@gmail.com>
|
||||||
|
Médi-Rémi Hashim <medimatrix@users.noreply.github.com>
|
||||||
meejah <meejah@meejah.ca>
|
meejah <meejah@meejah.ca>
|
||||||
|
Mengdi Gao <usrgdd@gmail.com>
|
||||||
|
Mert Yazıcıoğlu <merty@users.noreply.github.com>
|
||||||
Michael Brown <michael@netdirect.ca>
|
Michael Brown <michael@netdirect.ca>
|
||||||
Michael Crosby <michael@docker.com>
|
Michael Crosby <michael@docker.com>
|
||||||
Michael Gorsuch <gorsuch@github.com>
|
Michael Gorsuch <gorsuch@github.com>
|
||||||
|
Michael Hudson-Doyle <michael.hudson@linaro.org>
|
||||||
Michael Neale <michael.neale@gmail.com>
|
Michael Neale <michael.neale@gmail.com>
|
||||||
Michael Prokop <github@michael-prokop.at>
|
|
||||||
Michael Stapelberg <michael+gh@stapelberg.de>
|
|
||||||
Michaël Pailloncy <mpapo.dev@gmail.com>
|
Michaël Pailloncy <mpapo.dev@gmail.com>
|
||||||
|
Michael Prokop <github@michael-prokop.at>
|
||||||
|
Michael Scharf <github@scharf.gr>
|
||||||
|
Michael Stapelberg <michael+gh@stapelberg.de>
|
||||||
|
Michael Thies <michaelthies78@gmail.com>
|
||||||
|
Michal Jemala <michal.jemala@gmail.com>
|
||||||
Michiel@unhosted <michiel@unhosted.org>
|
Michiel@unhosted <michiel@unhosted.org>
|
||||||
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
||||||
Mike Chelen <michael.chelen@gmail.com>
|
Mike Chelen <michael.chelen@gmail.com>
|
||||||
|
@ -395,32 +469,40 @@ Mohit Soni <mosoni@ebay.com>
|
||||||
Morgante Pell <morgante.pell@morgante.net>
|
Morgante Pell <morgante.pell@morgante.net>
|
||||||
Morten Siebuhr <sbhr@sbhr.dk>
|
Morten Siebuhr <sbhr@sbhr.dk>
|
||||||
Mrunal Patel <mrunalp@gmail.com>
|
Mrunal Patel <mrunalp@gmail.com>
|
||||||
|
mschurenko <matt.schurenko@gmail.com>
|
||||||
|
Mustafa Akın <mustafa91@gmail.com>
|
||||||
Nan Monnand Deng <monnand@gmail.com>
|
Nan Monnand Deng <monnand@gmail.com>
|
||||||
Naoki Orii <norii@cs.cmu.edu>
|
Naoki Orii <norii@cs.cmu.edu>
|
||||||
Nate Jones <nate@endot.org>
|
Nate Jones <nate@endot.org>
|
||||||
|
Nathan Hsieh <hsieh.nathan@gmail.com>
|
||||||
Nathan Kleyn <nathan@nathankleyn.com>
|
Nathan Kleyn <nathan@nathankleyn.com>
|
||||||
Nathan LeClaire <nathan.leclaire@docker.com>
|
Nathan LeClaire <nathan.leclaire@docker.com>
|
||||||
Nelson Chen <crazysim@gmail.com>
|
Nelson Chen <crazysim@gmail.com>
|
||||||
Niall O'Higgins <niallo@unworkable.org>
|
Niall O'Higgins <niallo@unworkable.org>
|
||||||
|
Nicholas E. Rabenau <nerab@gmx.at>
|
||||||
Nick Payne <nick@kurai.co.uk>
|
Nick Payne <nick@kurai.co.uk>
|
||||||
Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
|
Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
|
||||||
Nick Stinemates <nick@stinemates.org>
|
Nick Stinemates <nick@stinemates.org>
|
||||||
|
Nicolas De loof <nicolas.deloof@gmail.com>
|
||||||
Nicolas Dudebout <nicolas.dudebout@gatech.edu>
|
Nicolas Dudebout <nicolas.dudebout@gatech.edu>
|
||||||
|
Nicolas Goy <kuon@goyman.com>
|
||||||
Nicolas Kaiser <nikai@nikai.net>
|
Nicolas Kaiser <nikai@nikai.net>
|
||||||
NikolaMandic <mn080202@gmail.com>
|
NikolaMandic <mn080202@gmail.com>
|
||||||
noducks <onemannoducks@gmail.com>
|
noducks <onemannoducks@gmail.com>
|
||||||
Nolan Darilek <nolan@thewordnerd.info>
|
Nolan Darilek <nolan@thewordnerd.info>
|
||||||
O.S. Tezer <ostezer@gmail.com>
|
nzwsch <hi@nzwsch.com>
|
||||||
OddBloke <daniel@daniel-watkins.co.uk>
|
OddBloke <daniel@daniel-watkins.co.uk>
|
||||||
odk- <github@odkurzacz.org>
|
odk- <github@odkurzacz.org>
|
||||||
Oguz Bilgic <fisyonet@gmail.com>
|
Oguz Bilgic <fisyonet@gmail.com>
|
||||||
|
Oh Jinkyun <tintypemolly@gmail.com>
|
||||||
Ole Reifschneider <mail@ole-reifschneider.de>
|
Ole Reifschneider <mail@ole-reifschneider.de>
|
||||||
Olivier Gambier <dmp42@users.noreply.github.com>
|
Olivier Gambier <dmp42@users.noreply.github.com>
|
||||||
|
O.S. Tezer <ostezer@gmail.com>
|
||||||
pandrew <letters@paulnotcom.se>
|
pandrew <letters@paulnotcom.se>
|
||||||
Pascal Borreli <pascal@borreli.com>
|
Pascal Borreli <pascal@borreli.com>
|
||||||
|
Pascal Hartig <phartig@rdrei.net>
|
||||||
Patrick Hemmer <patrick.hemmer@gmail.com>
|
Patrick Hemmer <patrick.hemmer@gmail.com>
|
||||||
pattichen <craftsbear@gmail.com>
|
pattichen <craftsbear@gmail.com>
|
||||||
Paul <paul9869@gmail.com>
|
|
||||||
Paul Annesley <paul@annesley.cc>
|
Paul Annesley <paul@annesley.cc>
|
||||||
Paul Bowsher <pbowsher@globalpersonals.co.uk>
|
Paul Bowsher <pbowsher@globalpersonals.co.uk>
|
||||||
Paul Hammond <paul@paulhammond.org>
|
Paul Hammond <paul@paulhammond.org>
|
||||||
|
@ -428,25 +510,39 @@ Paul Jimenez <pj@place.org>
|
||||||
Paul Lietar <paul@lietar.net>
|
Paul Lietar <paul@lietar.net>
|
||||||
Paul Morie <pmorie@gmail.com>
|
Paul Morie <pmorie@gmail.com>
|
||||||
Paul Nasrat <pnasrat@gmail.com>
|
Paul Nasrat <pnasrat@gmail.com>
|
||||||
|
Paul <paul9869@gmail.com>
|
||||||
Paul Weaver <pauweave@cisco.com>
|
Paul Weaver <pauweave@cisco.com>
|
||||||
|
Pavlos Ratis <dastergon@gentoo.org>
|
||||||
Peter Bourgon <peter@bourgon.org>
|
Peter Bourgon <peter@bourgon.org>
|
||||||
Peter Braden <peterbraden@peterbraden.co.uk>
|
Peter Braden <peterbraden@peterbraden.co.uk>
|
||||||
|
Peter Ericson <pdericson@gmail.com>
|
||||||
|
Peter Salvatore <peter@psftw.com>
|
||||||
Peter Waller <p@pwaller.net>
|
Peter Waller <p@pwaller.net>
|
||||||
Phil <underscorephil@gmail.com>
|
Phil Estes <estesp@linux.vnet.ibm.com>
|
||||||
Phil Spitler <pspitler@gmail.com>
|
Philipp Weissensteiner <mail@philippweissensteiner.com>
|
||||||
Phillip Alexander <git@phillipalexander.io>
|
Phillip Alexander <git@phillipalexander.io>
|
||||||
|
Phil Spitler <pspitler@gmail.com>
|
||||||
|
Phil <underscorephil@gmail.com>
|
||||||
Piergiuliano Bossi <pgbossi@gmail.com>
|
Piergiuliano Bossi <pgbossi@gmail.com>
|
||||||
Pierre-Alain RIVIERE <pariviere@ippon.fr>
|
Pierre-Alain RIVIERE <pariviere@ippon.fr>
|
||||||
|
Pierre <py@poujade.org>
|
||||||
Piotr Bogdan <ppbogdan@gmail.com>
|
Piotr Bogdan <ppbogdan@gmail.com>
|
||||||
|
pixelistik <pixelistik@users.noreply.github.com>
|
||||||
|
Prasanna Gautam <prasannagautam@gmail.com>
|
||||||
|
Przemek Hejman <przemyslaw.hejman@gmail.com>
|
||||||
pysqz <randomq@126.com>
|
pysqz <randomq@126.com>
|
||||||
|
Qiang Huang <h.huangqiang@huawei.com>
|
||||||
Quentin Brossard <qbrossard@gmail.com>
|
Quentin Brossard <qbrossard@gmail.com>
|
||||||
r0n22 <cameron.regan@gmail.com>
|
r0n22 <cameron.regan@gmail.com>
|
||||||
Rafal Jeczalik <rjeczalik@gmail.com>
|
Rafal Jeczalik <rjeczalik@gmail.com>
|
||||||
|
Rafe Colton <rafael.colton@gmail.com>
|
||||||
Rajat Pandit <rp@rajatpandit.com>
|
Rajat Pandit <rp@rajatpandit.com>
|
||||||
Rajdeep Dua <dua_rajdeep@yahoo.com>
|
Rajdeep Dua <dua_rajdeep@yahoo.com>
|
||||||
Ralph Bean <rbean@redhat.com>
|
Ralph Bean <rbean@redhat.com>
|
||||||
Ramkumar Ramachandra <artagnon@gmail.com>
|
Ramkumar Ramachandra <artagnon@gmail.com>
|
||||||
Ramon van Alteren <ramon@vanalteren.nl>
|
Ramon van Alteren <ramon@vanalteren.nl>
|
||||||
|
Recursive Madman <recursive.madman@gmx.de>
|
||||||
|
Remi Rampin <remirampin@gmail.com>
|
||||||
Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
|
Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
|
||||||
rgstephens <greg@udon.org>
|
rgstephens <greg@udon.org>
|
||||||
Rhys Hiltner <rhys@twitch.tv>
|
Rhys Hiltner <rhys@twitch.tv>
|
||||||
|
@ -455,6 +551,7 @@ Richo Healey <richo@psych0tik.net>
|
||||||
Rick Bradley <rick@users.noreply.github.com>
|
Rick Bradley <rick@users.noreply.github.com>
|
||||||
Rick van de Loo <rickvandeloo@gmail.com>
|
Rick van de Loo <rickvandeloo@gmail.com>
|
||||||
Robert Bachmann <rb@robertbachmann.at>
|
Robert Bachmann <rb@robertbachmann.at>
|
||||||
|
Robert Bittle <guywithnose@gmail.com>
|
||||||
Robert Obryk <robryk@gmail.com>
|
Robert Obryk <robryk@gmail.com>
|
||||||
Roberto G. Hashioka <roberto.hashioka@docker.com>
|
Roberto G. Hashioka <roberto.hashioka@docker.com>
|
||||||
Robin Speekenbrink <robin@kingsquare.nl>
|
Robin Speekenbrink <robin@kingsquare.nl>
|
||||||
|
@ -470,25 +567,30 @@ Rovanion Luckey <rovanion.luckey@gmail.com>
|
||||||
Rudolph Gottesheim <r.gottesheim@loot.at>
|
Rudolph Gottesheim <r.gottesheim@loot.at>
|
||||||
Ryan Anderson <anderson.ryanc@gmail.com>
|
Ryan Anderson <anderson.ryanc@gmail.com>
|
||||||
Ryan Aslett <github@mixologic.com>
|
Ryan Aslett <github@mixologic.com>
|
||||||
|
Ryan Detzel <ryan.detzel@gmail.com>
|
||||||
Ryan Fowler <rwfowler@gmail.com>
|
Ryan Fowler <rwfowler@gmail.com>
|
||||||
Ryan O'Donnell <odonnellryanc@gmail.com>
|
Ryan O'Donnell <odonnellryanc@gmail.com>
|
||||||
Ryan Seto <ryanseto@yak.net>
|
Ryan Seto <ryanseto@yak.net>
|
||||||
Ryan Thomas <rthomas@atlassian.com>
|
Ryan Thomas <rthomas@atlassian.com>
|
||||||
s-ko <aleks@s-ko.net>
|
|
||||||
Sam Alba <sam.alba@gmail.com>
|
Sam Alba <sam.alba@gmail.com>
|
||||||
Sam Bailey <cyprix@cyprix.com.au>
|
Sam Bailey <cyprix@cyprix.com.au>
|
||||||
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
|
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
|
||||||
Sam Reis <sreis@atlassian.com>
|
Sam Reis <sreis@atlassian.com>
|
||||||
Sam Rijs <srijs@airpost.net>
|
Sam Rijs <srijs@airpost.net>
|
||||||
Samuel Andaya <samuel@andaya.net>
|
Samuel Andaya <samuel@andaya.net>
|
||||||
|
Samuel PHAN <samuel-phan@users.noreply.github.com>
|
||||||
satoru <satorulogic@gmail.com>
|
satoru <satorulogic@gmail.com>
|
||||||
Satoshi Amemiya <satoshi_amemiya@voyagegroup.com>
|
Satoshi Amemiya <satoshi_amemiya@voyagegroup.com>
|
||||||
Scott Bessler <scottbessler@gmail.com>
|
Scott Bessler <scottbessler@gmail.com>
|
||||||
Scott Collier <emailscottcollier@gmail.com>
|
Scott Collier <emailscottcollier@gmail.com>
|
||||||
|
Scott Johnston <scott@docker.com>
|
||||||
|
Scott Walls <sawalls@umich.edu>
|
||||||
Sean Cronin <seancron@gmail.com>
|
Sean Cronin <seancron@gmail.com>
|
||||||
Sean P. Kane <skane@newrelic.com>
|
Sean P. Kane <skane@newrelic.com>
|
||||||
Sebastiaan van Stijn <github@gone.nl>
|
Sebastiaan van Stijn <github@gone.nl>
|
||||||
Sebastiaan van Stijn <thaJeztah@users.noreply.github.com>
|
Sébastien Luttringer <seblu@seblu.net>
|
||||||
|
Sébastien <sebastien@yoozio.com>
|
||||||
|
Sébastien Stormacq <sebsto@users.noreply.github.com>
|
||||||
Senthil Kumar Selvaraj <senthil.thecoder@gmail.com>
|
Senthil Kumar Selvaraj <senthil.thecoder@gmail.com>
|
||||||
SeongJae Park <sj38.park@gmail.com>
|
SeongJae Park <sj38.park@gmail.com>
|
||||||
Shane Canon <scanon@lbl.gov>
|
Shane Canon <scanon@lbl.gov>
|
||||||
|
@ -496,28 +598,30 @@ shaunol <shaunol@gmail.com>
|
||||||
Shawn Landden <shawn@churchofgit.com>
|
Shawn Landden <shawn@churchofgit.com>
|
||||||
Shawn Siefkas <shawn.siefkas@meredith.com>
|
Shawn Siefkas <shawn.siefkas@meredith.com>
|
||||||
Shih-Yuan Lee <fourdollars@gmail.com>
|
Shih-Yuan Lee <fourdollars@gmail.com>
|
||||||
|
shuai-z <zs.broccoli@gmail.com>
|
||||||
Silas Sewell <silas@sewell.org>
|
Silas Sewell <silas@sewell.org>
|
||||||
Simon Taranto <simon.taranto@gmail.com>
|
Simon Taranto <simon.taranto@gmail.com>
|
||||||
Sindhu S <sindhus@live.in>
|
Sindhu S <sindhus@live.in>
|
||||||
Sjoerd Langkemper <sjoerd-github@linuxonly.nl>
|
Sjoerd Langkemper <sjoerd-github@linuxonly.nl>
|
||||||
|
s-ko <aleks@s-ko.net>
|
||||||
Solomon Hykes <solomon@docker.com>
|
Solomon Hykes <solomon@docker.com>
|
||||||
Song Gao <song@gao.io>
|
Song Gao <song@gao.io>
|
||||||
Soulou <leo@unbekandt.eu>
|
Soulou <leo@unbekandt.eu>
|
||||||
soulshake <amy@gandi.net>
|
soulshake <amy@gandi.net>
|
||||||
Sridatta Thatipamala <sthatipamala@gmail.com>
|
Sridatta Thatipamala <sthatipamala@gmail.com>
|
||||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||||
|
Srini Brahmaroutu <sbrahma@us.ibm.com>
|
||||||
Steeve Morin <steeve.morin@gmail.com>
|
Steeve Morin <steeve.morin@gmail.com>
|
||||||
Stefan Praszalowicz <stefan@greplin.com>
|
Stefan Praszalowicz <stefan@greplin.com>
|
||||||
Stephen Crosby <stevecrozz@gmail.com>
|
Stephen Crosby <stevecrozz@gmail.com>
|
||||||
Steven Burgess <steven.a.burgess@hotmail.com>
|
Steven Burgess <steven.a.burgess@hotmail.com>
|
||||||
|
Steven Merrill <steven.merrill@gmail.com>
|
||||||
sudosurootdev <sudosurootdev@gmail.com>
|
sudosurootdev <sudosurootdev@gmail.com>
|
||||||
Sven Dowideit <svendowideit@home.org.au>
|
Sven Dowideit <SvenDowideit@home.org.au>
|
||||||
Sylvain Bellemare <sylvain.bellemare@ezeep.com>
|
Sylvain Bellemare <sylvain.bellemare@ezeep.com>
|
||||||
Sébastien <sebastien@yoozio.com>
|
|
||||||
Sébastien Luttringer <seblu@seblu.net>
|
|
||||||
Sébastien Stormacq <sebsto@users.noreply.github.com>
|
|
||||||
tang0th <tang0th@gmx.com>
|
tang0th <tang0th@gmx.com>
|
||||||
Tatsuki Sugiura <sugi@nemui.org>
|
Tatsuki Sugiura <sugi@nemui.org>
|
||||||
|
Ted M. Young <tedyoung@gmail.com>
|
||||||
Tehmasp Chaudhri <tehmasp@gmail.com>
|
Tehmasp Chaudhri <tehmasp@gmail.com>
|
||||||
Thatcher Peskens <thatcher@docker.com>
|
Thatcher Peskens <thatcher@docker.com>
|
||||||
Thermionix <bond711@gmail.com>
|
Thermionix <bond711@gmail.com>
|
||||||
|
@ -526,25 +630,32 @@ Thomas Bikeev <thomas.bikeev@mac.com>
|
||||||
Thomas Frössman <thomasf@jossystem.se>
|
Thomas Frössman <thomasf@jossystem.se>
|
||||||
Thomas Hansen <thomas.hansen@gmail.com>
|
Thomas Hansen <thomas.hansen@gmail.com>
|
||||||
Thomas LEVEIL <thomasleveil@gmail.com>
|
Thomas LEVEIL <thomasleveil@gmail.com>
|
||||||
|
Thomas Orozco <thomas@orozco.fr>
|
||||||
Thomas Schroeter <thomas@cliqz.com>
|
Thomas Schroeter <thomas@cliqz.com>
|
||||||
Tianon Gravi <admwiggin@gmail.com>
|
Tianon Gravi <admwiggin@gmail.com>
|
||||||
Tibor Vass <teabee89@gmail.com>
|
Tibor Vass <teabee89@gmail.com>
|
||||||
Tim Bosse <taim@bosboot.org>
|
Tim Bosse <taim@bosboot.org>
|
||||||
Tim Ruffles <oi@truffles.me.uk>
|
Tim Hockin <thockin@google.com>
|
||||||
Tim Ruffles <timruffles@googlemail.com>
|
|
||||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
|
||||||
Timothy Hobbs <timothyhobbs@seznam.cz>
|
Timothy Hobbs <timothyhobbs@seznam.cz>
|
||||||
|
Tim Ruffles <oi@truffles.me.uk>
|
||||||
|
Tim Smith <timbot@google.com>
|
||||||
|
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||||
tjmehta <tj@init.me>
|
tjmehta <tj@init.me>
|
||||||
|
tjwebb123 <tjwebb123@users.noreply.github.com>
|
||||||
|
tobe <tobegit3hub@gmail.com>
|
||||||
Tobias Bieniek <Tobias.Bieniek@gmx.de>
|
Tobias Bieniek <Tobias.Bieniek@gmx.de>
|
||||||
Tobias Gesellchen <tobias@gesellix.de>
|
Tobias Gesellchen <tobias@gesellix.de>
|
||||||
Tobias Schmidt <ts@soundcloud.com>
|
Tobias Schmidt <ts@soundcloud.com>
|
||||||
Tobias Schwab <tobias.schwab@dynport.de>
|
Tobias Schwab <tobias.schwab@dynport.de>
|
||||||
Todd Lunter <tlunter@gmail.com>
|
Todd Lunter <tlunter@gmail.com>
|
||||||
|
Tomasz Lipinski <tlipinski@users.noreply.github.com>
|
||||||
Tom Fotherby <tom+github@peopleperhour.com>
|
Tom Fotherby <tom+github@peopleperhour.com>
|
||||||
Tom Hulihan <hulihan.tom159@gmail.com>
|
Tom Hulihan <hulihan.tom159@gmail.com>
|
||||||
Tom Maaswinkel <tom.maaswinkel@12wiki.eu>
|
Tom Maaswinkel <tom.maaswinkel@12wiki.eu>
|
||||||
Tommaso Visconti <tommaso.visconti@gmail.com>
|
Tommaso Visconti <tommaso.visconti@gmail.com>
|
||||||
|
Tonis Tiigi <tonistiigi@gmail.com>
|
||||||
Tony Daws <tony@daws.ca>
|
Tony Daws <tony@daws.ca>
|
||||||
|
Torstein Husebø <torstein@huseboe.net>
|
||||||
tpng <benny.tpng@gmail.com>
|
tpng <benny.tpng@gmail.com>
|
||||||
Travis Cline <travis.cline@gmail.com>
|
Travis Cline <travis.cline@gmail.com>
|
||||||
Trent Ogren <tedwardo2@gmail.com>
|
Trent Ogren <tedwardo2@gmail.com>
|
||||||
|
@ -560,33 +671,43 @@ Victor Vieux <victor.vieux@docker.com>
|
||||||
Viktor Vojnovski <viktor.vojnovski@amadeus.com>
|
Viktor Vojnovski <viktor.vojnovski@amadeus.com>
|
||||||
Vincent Batts <vbatts@redhat.com>
|
Vincent Batts <vbatts@redhat.com>
|
||||||
Vincent Bernat <bernat@luffy.cx>
|
Vincent Bernat <bernat@luffy.cx>
|
||||||
|
Vincent Bernat <Vincent.Bernat@exoscale.ch>
|
||||||
|
Vincent Giersch <vincent.giersch@ovh.net>
|
||||||
Vincent Mayers <vincent.mayers@inbloom.org>
|
Vincent Mayers <vincent.mayers@inbloom.org>
|
||||||
Vincent Woo <me@vincentwoo.com>
|
Vincent Woo <me@vincentwoo.com>
|
||||||
Vinod Kulkarni <vinod.kulkarni@gmail.com>
|
Vinod Kulkarni <vinod.kulkarni@gmail.com>
|
||||||
|
Vishal Doshi <vishal.doshi@gmail.com>
|
||||||
Vishnu Kannan <vishnuk@google.com>
|
Vishnu Kannan <vishnuk@google.com>
|
||||||
Vitor Monteiro <vmrmonteiro@gmail.com>
|
Vitor Monteiro <vmrmonteiro@gmail.com>
|
||||||
Vivek Agarwal <me@vivek.im>
|
Vivek Agarwal <me@vivek.im>
|
||||||
|
Vivek Dasgupta <vdasgupt@redhat.com>
|
||||||
|
Vivek Goyal <vgoyal@redhat.com>
|
||||||
Vladimir Bulyga <xx@ccxx.cc>
|
Vladimir Bulyga <xx@ccxx.cc>
|
||||||
Vladimir Kirillov <proger@wilab.org.ua>
|
Vladimir Kirillov <proger@wilab.org.ua>
|
||||||
Vladimir Rutsky <altsysrq@gmail.com>
|
Vladimir Rutsky <altsysrq@gmail.com>
|
||||||
|
Vojtech Vitek (V-Teq) <vvitek@redhat.com>
|
||||||
waitingkuo <waitingkuo0527@gmail.com>
|
waitingkuo <waitingkuo0527@gmail.com>
|
||||||
Walter Leibbrandt <github@wrl.co.za>
|
Walter Leibbrandt <github@wrl.co.za>
|
||||||
Walter Stanish <walter@pratyeka.org>
|
Walter Stanish <walter@pratyeka.org>
|
||||||
|
Ward Vandewege <ward@jhvc.com>
|
||||||
WarheadsSE <max@warheads.net>
|
WarheadsSE <max@warheads.net>
|
||||||
Wes Morgan <cap10morgan@gmail.com>
|
Wes Morgan <cap10morgan@gmail.com>
|
||||||
Will Dietz <w@wdtz.org>
|
Will Dietz <w@wdtz.org>
|
||||||
Will Rouesnel <w.rouesnel@gmail.com>
|
|
||||||
Will Weaver <monkey@buildingbananas.com>
|
|
||||||
William Delanoue <william.delanoue@gmail.com>
|
William Delanoue <william.delanoue@gmail.com>
|
||||||
William Henry <whenry@redhat.com>
|
William Henry <whenry@redhat.com>
|
||||||
William Riancho <wr.wllm@gmail.com>
|
William Riancho <wr.wllm@gmail.com>
|
||||||
William Thurston <thurstw@amazon.com>
|
William Thurston <thurstw@amazon.com>
|
||||||
|
Will Rouesnel <w.rouesnel@gmail.com>
|
||||||
|
Will Weaver <monkey@buildingbananas.com>
|
||||||
wyc <wayne@neverfear.org>
|
wyc <wayne@neverfear.org>
|
||||||
Xiuming Chen <cc@cxm.cc>
|
Xiuming Chen <cc@cxm.cc>
|
||||||
|
xuzhaokui <cynicholas@gmail.com>
|
||||||
Yang Bai <hamo.by@gmail.com>
|
Yang Bai <hamo.by@gmail.com>
|
||||||
Yasunori Mahata <nori@mahata.net>
|
Yasunori Mahata <nori@mahata.net>
|
||||||
|
Yohei Ueda <yohei@jp.ibm.com>
|
||||||
Yurii Rashkovskii <yrashk@gmail.com>
|
Yurii Rashkovskii <yrashk@gmail.com>
|
||||||
Zac Dover <zdover@redhat.com>
|
Zac Dover <zdover@redhat.com>
|
||||||
|
Zach Borboa <zachborboa@gmail.com>
|
||||||
Zain Memon <zain@inzain.net>
|
Zain Memon <zain@inzain.net>
|
||||||
Zaiste! <oh@zaiste.net>
|
Zaiste! <oh@zaiste.net>
|
||||||
Zane DeGraffenried <zane.deg@gmail.com>
|
Zane DeGraffenried <zane.deg@gmail.com>
|
||||||
|
@ -594,4 +715,4 @@ Zilin Du <zilin.du@gmail.com>
|
||||||
zimbatm <zimbatm@zimbatm.com>
|
zimbatm <zimbatm@zimbatm.com>
|
||||||
Zoltan Tombol <zoltan.tombol@gmail.com>
|
Zoltan Tombol <zoltan.tombol@gmail.com>
|
||||||
zqh <zqhxuyuan@gmail.com>
|
zqh <zqhxuyuan@gmail.com>
|
||||||
Álvaro Lázaro <alvaro.lazaro.g@gmail.com>
|
尹吉峰 <jifeng.yin@gmail.com>
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
#### Builder
|
#### Builder
|
||||||
- Fix escaping `$` for environment variables
|
- Fix escaping `$` for environment variables
|
||||||
- Fix issue with lowercase `onbuild` Dockerfile instruction
|
- Fix issue with lowercase `onbuild` Dockerfile instruction
|
||||||
- Restrict envrionment variable expansion to `ENV`, `ADD`, `COPY`, `WORKDIR`, `EXPOSE`, `VOLUME` and `USER`
|
- Restrict environment variable expansion to `ENV`, `ADD`, `COPY`, `WORKDIR`, `EXPOSE`, `VOLUME` and `USER`
|
||||||
|
|
||||||
## 1.3.0 (2014-10-14)
|
## 1.3.0 (2014-10-14)
|
||||||
|
|
||||||
|
|
|
@ -6,27 +6,36 @@ feels wrong or incomplete.
|
||||||
|
|
||||||
## Topics
|
## Topics
|
||||||
|
|
||||||
* [Security Reports](#security-reports)
|
* [Reporting Security Issues](#reporting-security-issues)
|
||||||
* [Design and Cleanup Proposals](#design-and-cleanup-proposals)
|
* [Design and Cleanup Proposals](#design-and-cleanup-proposals)
|
||||||
* [Reporting Issues](#reporting-issues)
|
* [Reporting Issues](#reporting-issues)
|
||||||
* [Build Environment](#build-environment)
|
* [Build Environment](#build-environment)
|
||||||
* [Contribution Guidelines](#contribution-guidelines)
|
* [Contribution Guidelines](#contribution-guidelines)
|
||||||
* [Community Guidelines](#docker-community-guidelines)
|
* [Community Guidelines](#docker-community-guidelines)
|
||||||
|
|
||||||
## Security Reports
|
## Reporting Security Issues
|
||||||
|
|
||||||
Please **DO NOT** file an issue for security related issues. Please send your
|
The Docker maintainers take security very seriously. If you discover a security issue,
|
||||||
reports to [security@docker.com](mailto:security@docker.com) instead.
|
please bring it to their attention right away!
|
||||||
|
|
||||||
|
Please send your report privately to [security@docker.com](mailto:security@docker.com),
|
||||||
|
please **DO NOT** file a public issue.
|
||||||
|
|
||||||
|
Security reports are greatly appreciated and we will publicly thank you for it. We also
|
||||||
|
like to send gifts - if you're into Docker shwag make sure to let us know :)
|
||||||
|
We currently do not offer a paid security bounty program, but are not ruling it out in
|
||||||
|
the future.
|
||||||
|
|
||||||
## Design and Cleanup Proposals
|
## Design and Cleanup Proposals
|
||||||
|
|
||||||
When considering a design proposal, we are looking for:
|
When considering a design proposal, we are looking for:
|
||||||
|
|
||||||
* A description of the problem this design proposal solves
|
* A description of the problem this design proposal solves
|
||||||
* An issue -- not a pull request -- that describes what you will take action on
|
* A pull request, not an issue, that modifies the documentation describing
|
||||||
|
the feature you are proposing, adding new documentation if necessary.
|
||||||
* Please prefix your issue with `Proposal:` in the title
|
* Please prefix your issue with `Proposal:` in the title
|
||||||
* Please review [the existing Proposals](https://github.com/dotcloud/docker/issues?direction=asc&labels=Proposal&page=1&sort=created&state=open)
|
* Please review [the existing Proposals](https://github.com/docker/docker/pulls?q=is%3Aopen+is%3Apr+label%3AProposal)
|
||||||
before reporting a new issue. You can always pair with someone if you both
|
before reporting a new one. You can always pair with someone if you both
|
||||||
have the same idea.
|
have the same idea.
|
||||||
|
|
||||||
When considering a cleanup task, we are looking for:
|
When considering a cleanup task, we are looking for:
|
||||||
|
@ -39,6 +48,10 @@ When considering a cleanup task, we are looking for:
|
||||||
|
|
||||||
## Reporting Issues
|
## Reporting Issues
|
||||||
|
|
||||||
|
A great way to contribute to the project is to send a detailed report when you
|
||||||
|
encounter an issue. We always appreciate a well-written, thorough bug report,
|
||||||
|
and will thank you for it!
|
||||||
|
|
||||||
When reporting [issues](https://github.com/docker/docker/issues) on
|
When reporting [issues](https://github.com/docker/docker/issues) on
|
||||||
GitHub please include your host OS (Ubuntu 12.04, Fedora 19, etc).
|
GitHub please include your host OS (Ubuntu 12.04, Fedora 19, etc).
|
||||||
Please include:
|
Please include:
|
||||||
|
@ -62,7 +75,7 @@ docs](http://docs.docker.com/contributing/devenvironment/).
|
||||||
### Pull requests are always welcome
|
### Pull requests are always welcome
|
||||||
|
|
||||||
We are always thrilled to receive pull requests, and do our best to
|
We are always thrilled to receive pull requests, and do our best to
|
||||||
process them as fast as possible. Not sure if that typo is worth a pull
|
process them as quickly as possible. Not sure if that typo is worth a pull
|
||||||
request? Do it! We will appreciate it.
|
request? Do it! We will appreciate it.
|
||||||
|
|
||||||
If your pull request is not accepted on the first try, don't be
|
If your pull request is not accepted on the first try, don't be
|
||||||
|
@ -159,7 +172,7 @@ component affected. For example, if a change affects `docs/` and `registry/`, it
|
||||||
needs an absolute majority from the maintainers of `docs/` AND, separately, an
|
needs an absolute majority from the maintainers of `docs/` AND, separately, an
|
||||||
absolute majority of the maintainers of `registry/`.
|
absolute majority of the maintainers of `registry/`.
|
||||||
|
|
||||||
For more details see [MAINTAINERS.md](hack/MAINTAINERS.md)
|
For more details see [MAINTAINERS.md](project/MAINTAINERS.md)
|
||||||
|
|
||||||
### Sign your work
|
### Sign your work
|
||||||
|
|
||||||
|
@ -310,7 +323,7 @@ do need a fair way to deal with people who are making our community suck.
|
||||||
will be addressed immediately and are not subject to 3 strikes or
|
will be addressed immediately and are not subject to 3 strikes or
|
||||||
forgiveness.
|
forgiveness.
|
||||||
|
|
||||||
* Contact james@docker.com to report abuse or appeal violations. In the case of
|
* Contact abuse@docker.com to report abuse or appeal violations. In the case of
|
||||||
appeals, we know that mistakes happen, and we'll work with you to come up with
|
appeals, we know that mistakes happen, and we'll work with you to come up with
|
||||||
a fair solution if there has been a misunderstanding.
|
a fair solution if there has been a misunderstanding.
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
# the case. Therefore, you don't have to disable it anymore.
|
# the case. Therefore, you don't have to disable it anymore.
|
||||||
#
|
#
|
||||||
|
|
||||||
docker-version 0.6.1
|
|
||||||
FROM ubuntu:14.04
|
FROM ubuntu:14.04
|
||||||
MAINTAINER Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
MAINTAINER Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||||
|
|
||||||
|
@ -69,7 +68,10 @@ RUN cd /usr/local/go/src && ./make.bash --no-clean 2>&1
|
||||||
ENV DOCKER_CROSSPLATFORMS \
|
ENV DOCKER_CROSSPLATFORMS \
|
||||||
linux/386 linux/arm \
|
linux/386 linux/arm \
|
||||||
darwin/amd64 darwin/386 \
|
darwin/amd64 darwin/386 \
|
||||||
freebsd/amd64 freebsd/386 freebsd/arm
|
freebsd/amd64 freebsd/386 freebsd/arm
|
||||||
|
# windows is experimental for now
|
||||||
|
# windows/amd64 windows/386
|
||||||
|
|
||||||
# (set an explicit GOARM of 5 for maximum compatibility)
|
# (set an explicit GOARM of 5 for maximum compatibility)
|
||||||
ENV GOARM 5
|
ENV GOARM 5
|
||||||
RUN cd /usr/local/go/src && bash -xc 'for platform in $DOCKER_CROSSPLATFORMS; do GOOS=${platform%/*} GOARCH=${platform##*/} ./make.bash --no-clean 2>&1; done'
|
RUN cd /usr/local/go/src && bash -xc 'for platform in $DOCKER_CROSSPLATFORMS; do GOOS=${platform%/*} GOARCH=${platform##*/} ./make.bash --no-clean 2>&1; done'
|
||||||
|
@ -104,7 +106,7 @@ RUN useradd --create-home --gid docker unprivilegeduser
|
||||||
|
|
||||||
VOLUME /var/lib/docker
|
VOLUME /var/lib/docker
|
||||||
WORKDIR /go/src/github.com/docker/docker
|
WORKDIR /go/src/github.com/docker/docker
|
||||||
ENV DOCKER_BUILDTAGS apparmor selinux
|
ENV DOCKER_BUILDTAGS apparmor selinux btrfs_noversion
|
||||||
|
|
||||||
# Wrap all commands in the "docker-in-docker" script to allow nested containers
|
# Wrap all commands in the "docker-in-docker" script to allow nested containers
|
||||||
ENTRYPOINT ["hack/dind"]
|
ENTRYPOINT ["hack/dind"]
|
||||||
|
|
31
Makefile
31
Makefile
|
@ -1,20 +1,39 @@
|
||||||
.PHONY: all binary build cross default docs docs-build docs-shell shell test test-unit test-integration test-integration-cli validate
|
.PHONY: all binary build cross default docs docs-build docs-shell shell test test-unit test-integration test-integration-cli validate
|
||||||
|
|
||||||
|
# env vars passed through directly to Docker's build scripts
|
||||||
|
# to allow things like `make DOCKER_CLIENTONLY=1 binary` easily
|
||||||
|
# `docs/sources/contributing/devenvironment.md ` and `project/PACKAGERS.md` have some limited documentation of some of these
|
||||||
|
DOCKER_ENVS := \
|
||||||
|
-e BUILDFLAGS \
|
||||||
|
-e DOCKER_CLIENTONLY \
|
||||||
|
-e DOCKER_EXECDRIVER \
|
||||||
|
-e DOCKER_GRAPHDRIVER \
|
||||||
|
-e TESTDIRS \
|
||||||
|
-e TESTFLAGS \
|
||||||
|
-e TIMEOUT
|
||||||
|
# note: we _cannot_ add "-e DOCKER_BUILDTAGS" here because even if it's unset in the shell, that would shadow the "ENV DOCKER_BUILDTAGS" set in our Dockerfile, which is very important for our official builds
|
||||||
|
|
||||||
# to allow `make BINDDIR=. shell` or `make BINDDIR= test`
|
# to allow `make BINDDIR=. shell` or `make BINDDIR= test`
|
||||||
# (default to no bind mount if DOCKER_HOST is set)
|
# (default to no bind mount if DOCKER_HOST is set)
|
||||||
BINDDIR := $(if $(DOCKER_HOST),,bundles)
|
BINDDIR := $(if $(DOCKER_HOST),,bundles)
|
||||||
|
DOCKER_MOUNT := $(if $(BINDDIR),-v "$(CURDIR)/$(BINDDIR):/go/src/github.com/docker/docker/$(BINDDIR)")
|
||||||
|
|
||||||
|
# to allow `make DOCSDIR=docs docs-shell` (to create a bind mount in docs)
|
||||||
|
DOCS_MOUNT := $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR))
|
||||||
|
|
||||||
# to allow `make DOCSPORT=9000 docs`
|
# to allow `make DOCSPORT=9000 docs`
|
||||||
DOCSPORT := 8000
|
DOCSPORT := 8000
|
||||||
|
|
||||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
||||||
GITCOMMIT := $(shell git rev-parse --short HEAD 2>/dev/null)
|
|
||||||
DOCKER_IMAGE := docker$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
DOCKER_IMAGE := docker$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||||
DOCKER_DOCS_IMAGE := docker-docs$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
DOCKER_DOCS_IMAGE := docker-docs$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||||
DOCKER_MOUNT := $(if $(BINDDIR),-v "$(CURDIR)/$(BINDDIR):/go/src/github.com/docker/docker/$(BINDDIR)")
|
|
||||||
|
|
||||||
DOCKER_RUN_DOCKER := docker run --rm -it --privileged -e TIMEOUT -e BUILDFLAGS -e TESTFLAGS -e TESTDIRS -e DOCKER_GRAPHDRIVER -e DOCKER_EXECDRIVER $(DOCKER_MOUNT) "$(DOCKER_IMAGE)"
|
DOCKER_RUN_DOCKER := docker run --rm -it --privileged $(DOCKER_ENVS) $(DOCKER_MOUNT) "$(DOCKER_IMAGE)"
|
||||||
# to allow `make DOCSDIR=docs docs-shell`
|
|
||||||
DOCKER_RUN_DOCS := docker run --rm -it $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR)) -e AWS_S3_BUCKET
|
DOCKER_RUN_DOCS := docker run --rm -it $(DOCS_MOUNT) -e AWS_S3_BUCKET
|
||||||
|
|
||||||
|
# for some docs workarounds (see below in "docs-build" target)
|
||||||
|
GITCOMMIT := $(shell git rev-parse --short HEAD 2>/dev/null)
|
||||||
|
|
||||||
default: binary
|
default: binary
|
||||||
|
|
||||||
|
@ -34,7 +53,7 @@ docs-shell: docs-build
|
||||||
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" bash
|
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" bash
|
||||||
|
|
||||||
docs-release: docs-build
|
docs-release: docs-build
|
||||||
$(DOCKER_RUN_DOCS) -e BUILD_ROOT "$(DOCKER_DOCS_IMAGE)" ./release.sh
|
$(DOCKER_RUN_DOCS) -e OPTIONS -e BUILD_ROOT "$(DOCKER_DOCS_IMAGE)" ./release.sh
|
||||||
|
|
||||||
test: build
|
test: build
|
||||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross test-unit test-integration test-integration-cli
|
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross test-unit test-integration test-integration-cli
|
||||||
|
|
13
README.md
13
README.md
|
@ -178,13 +178,14 @@ Contributing to Docker
|
||||||
======================
|
======================
|
||||||
|
|
||||||
[](https://godoc.org/github.com/docker/docker)
|
[](https://godoc.org/github.com/docker/docker)
|
||||||
[](https://travis-ci.org/docker/docker)
|
[](https://ci.dockerproject.com/github.com/docker/docker)
|
||||||
|
|
||||||
Want to hack on Docker? Awesome! There are instructions to get you
|
Want to hack on Docker? Awesome! There are instructions to get you
|
||||||
started [here](CONTRIBUTING.md).
|
started [here](CONTRIBUTING.md). If you'd like to contribute to the
|
||||||
|
documentation, please take a look at this [README.md](https://github.com/docker/docker/blob/master/docs/README.md).
|
||||||
|
|
||||||
They are probably not perfect, please let us know if anything feels
|
These instructions are probably not perfect, please let us know if anything
|
||||||
wrong or incomplete.
|
feels wrong or incomplete.
|
||||||
|
|
||||||
### Legal
|
### Legal
|
||||||
|
|
||||||
|
@ -201,5 +202,7 @@ For more information, please see http://www.bis.doc.gov
|
||||||
|
|
||||||
Licensing
|
Licensing
|
||||||
=========
|
=========
|
||||||
Docker is licensed under the Apache License, Version 2.0. See LICENSE for full license text.
|
Docker is licensed under the Apache License, Version 2.0. See
|
||||||
|
[LICENSE](https://github.com/docker/docker/blob/master/LICENSE) for the full
|
||||||
|
license text.
|
||||||
|
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
Victor Vieux <vieux@docker.com> (@vieux)
|
Victor Vieux <vieux@docker.com> (@vieux)
|
||||||
|
Jessie Frazelle <jess@docker.com> (@jfrazelle)
|
||||||
|
|
|
@ -3,12 +3,16 @@ package client
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
|
@ -34,6 +38,7 @@ type DockerCli struct {
|
||||||
isTerminalIn bool
|
isTerminalIn bool
|
||||||
// isTerminalOut describes if client's STDOUT is a TTY
|
// isTerminalOut describes if client's STDOUT is a TTY
|
||||||
isTerminalOut bool
|
isTerminalOut bool
|
||||||
|
transport *http.Transport
|
||||||
}
|
}
|
||||||
|
|
||||||
var funcMap = template.FuncMap{
|
var funcMap = template.FuncMap{
|
||||||
|
@ -71,11 +76,11 @@ func (cli *DockerCli) Cmd(args ...string) error {
|
||||||
method, exists := cli.getMethod(args[0])
|
method, exists := cli.getMethod(args[0])
|
||||||
if !exists {
|
if !exists {
|
||||||
fmt.Println("Error: Command not found:", args[0])
|
fmt.Println("Error: Command not found:", args[0])
|
||||||
return cli.CmdHelp(args[1:]...)
|
return cli.CmdHelp()
|
||||||
}
|
}
|
||||||
return method(args[1:]...)
|
return method(args[1:]...)
|
||||||
}
|
}
|
||||||
return cli.CmdHelp(args...)
|
return cli.CmdHelp()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet {
|
func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet {
|
||||||
|
@ -100,6 +105,16 @@ func (cli *DockerCli) LoadConfigFile() (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
|
||||||
|
// In order to attach to a container tty, input stream for the client must
|
||||||
|
// be a tty itself: redirecting or piping the client standard input is
|
||||||
|
// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
|
||||||
|
if ttyMode && attachStdin && !cli.isTerminalIn {
|
||||||
|
return errors.New("cannot enable tty mode on non tty input")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer, key libtrust.PrivateKey, proto, addr string, tlsConfig *tls.Config) *DockerCli {
|
func NewDockerCli(in io.ReadCloser, out, err io.Writer, key libtrust.PrivateKey, proto, addr string, tlsConfig *tls.Config) *DockerCli {
|
||||||
var (
|
var (
|
||||||
inFd uintptr
|
inFd uintptr
|
||||||
|
@ -131,6 +146,23 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, key libtrust.PrivateKey,
|
||||||
err = out
|
err = out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The transport is created here for reuse during the client session
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Why 32? See issue 8035
|
||||||
|
timeout := 32 * time.Second
|
||||||
|
if proto == "unix" {
|
||||||
|
// no need in compressing for local communications
|
||||||
|
tr.DisableCompression = true
|
||||||
|
tr.Dial = func(_, _ string) (net.Conn, error) {
|
||||||
|
return net.DialTimeout(proto, addr, timeout)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
|
||||||
|
}
|
||||||
|
|
||||||
return &DockerCli{
|
return &DockerCli{
|
||||||
proto: proto,
|
proto: proto,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
|
@ -144,5 +176,6 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, key libtrust.PrivateKey,
|
||||||
isTerminalOut: isTerminalOut,
|
isTerminalOut: isTerminalOut,
|
||||||
tlsConfig: tlsConfig,
|
tlsConfig: tlsConfig,
|
||||||
scheme: scheme,
|
scheme: scheme,
|
||||||
|
transport: tr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -17,11 +18,11 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/dockerversion"
|
"github.com/docker/docker/dockerversion"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
|
@ -29,7 +30,6 @@ import (
|
||||||
"github.com/docker/docker/nat"
|
"github.com/docker/docker/nat"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/pkg/parsers/filters"
|
"github.com/docker/docker/pkg/parsers/filters"
|
||||||
|
@ -38,6 +38,7 @@ import (
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
"github.com/docker/docker/pkg/timeutils"
|
"github.com/docker/docker/pkg/timeutils"
|
||||||
"github.com/docker/docker/pkg/units"
|
"github.com/docker/docker/pkg/units"
|
||||||
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
|
@ -47,6 +48,10 @@ const (
|
||||||
tarHeaderSize = 512
|
tarHeaderSize = 512
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
acceptedImageFilterTags = map[string]struct{}{"dangling": {}}
|
||||||
|
)
|
||||||
|
|
||||||
func (cli *DockerCli) CmdHelp(args ...string) error {
|
func (cli *DockerCli) CmdHelp(args ...string) error {
|
||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
method, exists := cli.getMethod(args[:2]...)
|
method, exists := cli.getMethod(args[:2]...)
|
||||||
|
@ -77,6 +82,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
|
noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
|
||||||
rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build")
|
rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build")
|
||||||
forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers, even after unsuccessful builds")
|
forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers, even after unsuccessful builds")
|
||||||
|
pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image")
|
||||||
if err := cmd.Parse(args); err != nil {
|
if err := cmd.Parse(args); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -110,13 +116,13 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
} else {
|
} else {
|
||||||
context = ioutil.NopCloser(buf)
|
context = ioutil.NopCloser(buf)
|
||||||
}
|
}
|
||||||
} else if utils.IsURL(cmd.Arg(0)) && (!utils.IsGIT(cmd.Arg(0)) || !hasGit) {
|
} else if urlutil.IsURL(cmd.Arg(0)) && (!urlutil.IsGitURL(cmd.Arg(0)) || !hasGit) {
|
||||||
isRemote = true
|
isRemote = true
|
||||||
} else {
|
} else {
|
||||||
root := cmd.Arg(0)
|
root := cmd.Arg(0)
|
||||||
if utils.IsGIT(root) {
|
if urlutil.IsGitURL(root) {
|
||||||
remoteURL := cmd.Arg(0)
|
remoteURL := cmd.Arg(0)
|
||||||
if !strings.HasPrefix(remoteURL, "git://") && !strings.HasPrefix(remoteURL, "git@") && !utils.IsURL(remoteURL) {
|
if !urlutil.IsGitTransport(remoteURL) {
|
||||||
remoteURL = "https://" + remoteURL
|
remoteURL = "https://" + remoteURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,6 +149,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
return fmt.Errorf("Error reading .dockerignore: '%s'", err)
|
return fmt.Errorf("Error reading .dockerignore: '%s'", err)
|
||||||
}
|
}
|
||||||
for _, pattern := range strings.Split(string(ignore), "\n") {
|
for _, pattern := range strings.Split(string(ignore), "\n") {
|
||||||
|
pattern = strings.TrimSpace(pattern)
|
||||||
|
if pattern == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pattern = filepath.Clean(pattern)
|
||||||
ok, err := filepath.Match(pattern, "Dockerfile")
|
ok, err := filepath.Match(pattern, "Dockerfile")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Bad .dockerignore pattern: '%s', error: %s", pattern, err)
|
return fmt.Errorf("Bad .dockerignore pattern: '%s', error: %s", pattern, err)
|
||||||
|
@ -169,7 +180,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
// FIXME: ProgressReader shouldn't be this annoying to use
|
// FIXME: ProgressReader shouldn't be this annoying to use
|
||||||
if context != nil {
|
if context != nil {
|
||||||
sf := utils.NewStreamFormatter(false)
|
sf := utils.NewStreamFormatter(false)
|
||||||
body = utils.ProgressReader(context, 0, cli.err, sf, true, "", "Sending build context to Docker daemon")
|
body = utils.ProgressReader(context, 0, cli.out, sf, true, "", "Sending build context to Docker daemon")
|
||||||
}
|
}
|
||||||
// Send the build context
|
// Send the build context
|
||||||
v := &url.Values{}
|
v := &url.Values{}
|
||||||
|
@ -208,6 +219,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
v.Set("forcerm", "1")
|
v.Set("forcerm", "1")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *pull {
|
||||||
|
v.Set("pull", "1")
|
||||||
|
}
|
||||||
cli.LoadConfigFile()
|
cli.LoadConfigFile()
|
||||||
|
|
||||||
headers := http.Header(make(map[string][]string))
|
headers := http.Header(make(map[string][]string))
|
||||||
|
@ -284,7 +298,10 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||||
// the password or email from the config file, so prompt them
|
// the password or email from the config file, so prompt them
|
||||||
if username != authconfig.Username {
|
if username != authconfig.Username {
|
||||||
if password == "" {
|
if password == "" {
|
||||||
oldState, _ := term.SaveState(cli.inFd)
|
oldState, err := term.SaveState(cli.inFd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
fmt.Fprintf(cli.out, "Password: ")
|
fmt.Fprintf(cli.out, "Password: ")
|
||||||
term.DisableEcho(cli.inFd, oldState)
|
term.DisableEcho(cli.inFd, oldState)
|
||||||
|
|
||||||
|
@ -467,33 +484,69 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||||
}
|
}
|
||||||
out.Close()
|
out.Close()
|
||||||
|
|
||||||
fmt.Fprintf(cli.out, "Containers: %d\n", remoteInfo.GetInt("Containers"))
|
if remoteInfo.Exists("Containers") {
|
||||||
fmt.Fprintf(cli.out, "Images: %d\n", remoteInfo.GetInt("Images"))
|
fmt.Fprintf(cli.out, "Containers: %d\n", remoteInfo.GetInt("Containers"))
|
||||||
fmt.Fprintf(cli.out, "Storage Driver: %s\n", remoteInfo.Get("Driver"))
|
|
||||||
var driverStatus [][2]string
|
|
||||||
if err := remoteInfo.GetJson("DriverStatus", &driverStatus); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
for _, pair := range driverStatus {
|
if remoteInfo.Exists("Images") {
|
||||||
fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1])
|
fmt.Fprintf(cli.out, "Images: %d\n", remoteInfo.GetInt("Images"))
|
||||||
|
}
|
||||||
|
if remoteInfo.Exists("Driver") {
|
||||||
|
fmt.Fprintf(cli.out, "Storage Driver: %s\n", remoteInfo.Get("Driver"))
|
||||||
|
}
|
||||||
|
if remoteInfo.Exists("DriverStatus") {
|
||||||
|
var driverStatus [][2]string
|
||||||
|
if err := remoteInfo.GetJson("DriverStatus", &driverStatus); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, pair := range driverStatus {
|
||||||
|
fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if remoteInfo.Exists("ExecutionDriver") {
|
||||||
|
fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver"))
|
||||||
|
}
|
||||||
|
if remoteInfo.Exists("KernelVersion") {
|
||||||
|
fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion"))
|
||||||
|
}
|
||||||
|
if remoteInfo.Exists("OperatingSystem") {
|
||||||
|
fmt.Fprintf(cli.out, "Operating System: %s\n", remoteInfo.Get("OperatingSystem"))
|
||||||
|
}
|
||||||
|
if remoteInfo.Exists("NCPU") {
|
||||||
|
fmt.Fprintf(cli.out, "CPUs: %d\n", remoteInfo.GetInt("NCPU"))
|
||||||
|
}
|
||||||
|
if remoteInfo.Exists("MemTotal") {
|
||||||
|
fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(remoteInfo.GetInt64("MemTotal"))))
|
||||||
|
}
|
||||||
|
if remoteInfo.Exists("Name") {
|
||||||
|
fmt.Fprintf(cli.out, "Name: %s\n", remoteInfo.Get("Name"))
|
||||||
|
}
|
||||||
|
if remoteInfo.Exists("ID") {
|
||||||
|
fmt.Fprintf(cli.out, "ID: %s\n", remoteInfo.Get("ID"))
|
||||||
}
|
}
|
||||||
fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver"))
|
|
||||||
fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion"))
|
|
||||||
fmt.Fprintf(cli.out, "Operating System: %s\n", remoteInfo.Get("OperatingSystem"))
|
|
||||||
|
|
||||||
if remoteInfo.GetBool("Debug") || os.Getenv("DEBUG") != "" {
|
if remoteInfo.GetBool("Debug") || os.Getenv("DEBUG") != "" {
|
||||||
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", remoteInfo.GetBool("Debug"))
|
if remoteInfo.Exists("Debug") {
|
||||||
|
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", remoteInfo.GetBool("Debug"))
|
||||||
|
}
|
||||||
fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
|
fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
|
||||||
fmt.Fprintf(cli.out, "Fds: %d\n", remoteInfo.GetInt("NFd"))
|
if remoteInfo.Exists("NFd") {
|
||||||
fmt.Fprintf(cli.out, "Goroutines: %d\n", remoteInfo.GetInt("NGoroutines"))
|
fmt.Fprintf(cli.out, "Fds: %d\n", remoteInfo.GetInt("NFd"))
|
||||||
fmt.Fprintf(cli.out, "EventsListeners: %d\n", remoteInfo.GetInt("NEventsListener"))
|
}
|
||||||
|
if remoteInfo.Exists("NGoroutines") {
|
||||||
|
fmt.Fprintf(cli.out, "Goroutines: %d\n", remoteInfo.GetInt("NGoroutines"))
|
||||||
|
}
|
||||||
|
if remoteInfo.Exists("NEventsListener") {
|
||||||
|
fmt.Fprintf(cli.out, "EventsListeners: %d\n", remoteInfo.GetInt("NEventsListener"))
|
||||||
|
}
|
||||||
if initSha1 := remoteInfo.Get("InitSha1"); initSha1 != "" {
|
if initSha1 := remoteInfo.Get("InitSha1"); initSha1 != "" {
|
||||||
fmt.Fprintf(cli.out, "Init SHA1: %s\n", initSha1)
|
fmt.Fprintf(cli.out, "Init SHA1: %s\n", initSha1)
|
||||||
}
|
}
|
||||||
if initPath := remoteInfo.Get("InitPath"); initPath != "" {
|
if initPath := remoteInfo.Get("InitPath"); initPath != "" {
|
||||||
fmt.Fprintf(cli.out, "Init Path: %s\n", initPath)
|
fmt.Fprintf(cli.out, "Init Path: %s\n", initPath)
|
||||||
}
|
}
|
||||||
|
if root := remoteInfo.Get("DockerRootDir"); root != "" {
|
||||||
|
fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", root)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(remoteInfo.GetList("IndexServerAddress")) != 0 {
|
if len(remoteInfo.GetList("IndexServerAddress")) != 0 {
|
||||||
|
@ -504,15 +557,22 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||||
fmt.Fprintf(cli.out, "Registry: %v\n", remoteInfo.GetList("IndexServerAddress"))
|
fmt.Fprintf(cli.out, "Registry: %v\n", remoteInfo.GetList("IndexServerAddress"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !remoteInfo.GetBool("MemoryLimit") {
|
if remoteInfo.Exists("MemoryLimit") && !remoteInfo.GetBool("MemoryLimit") {
|
||||||
fmt.Fprintf(cli.err, "WARNING: No memory limit support\n")
|
fmt.Fprintf(cli.err, "WARNING: No memory limit support\n")
|
||||||
}
|
}
|
||||||
if !remoteInfo.GetBool("SwapLimit") {
|
if remoteInfo.Exists("SwapLimit") && !remoteInfo.GetBool("SwapLimit") {
|
||||||
fmt.Fprintf(cli.err, "WARNING: No swap limit support\n")
|
fmt.Fprintf(cli.err, "WARNING: No swap limit support\n")
|
||||||
}
|
}
|
||||||
if !remoteInfo.GetBool("IPv4Forwarding") {
|
if remoteInfo.Exists("IPv4Forwarding") && !remoteInfo.GetBool("IPv4Forwarding") {
|
||||||
fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n")
|
fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n")
|
||||||
}
|
}
|
||||||
|
if remoteInfo.Exists("Labels") {
|
||||||
|
fmt.Fprintln(cli.out, "Labels:")
|
||||||
|
for _, attribute := range remoteInfo.GetList("Labels") {
|
||||||
|
fmt.Fprintf(cli.out, " %s\n", attribute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -575,7 +635,7 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
||||||
signal.CatchAll(sigc)
|
signal.CatchAll(sigc)
|
||||||
go func() {
|
go func() {
|
||||||
for s := range sigc {
|
for s := range sigc {
|
||||||
if s == syscall.SIGCHLD {
|
if s == signal.SIGCHLD {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var sig string
|
var sig string
|
||||||
|
@ -586,7 +646,7 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sig == "" {
|
if sig == "" {
|
||||||
log.Errorf("Unsupported signal: %d. Discarding.", s)
|
log.Errorf("Unsupported signal: %v. Discarding.", s)
|
||||||
}
|
}
|
||||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, false)); err != nil {
|
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, false)); err != nil {
|
||||||
log.Debugf("Error sending signal: %s", err)
|
log.Debugf("Error sending signal: %s", err)
|
||||||
|
@ -614,18 +674,20 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hijacked := make(chan io.Closer)
|
||||||
|
|
||||||
if *attach || *openStdin {
|
if *attach || *openStdin {
|
||||||
if cmd.NArg() > 1 {
|
if cmd.NArg() > 1 {
|
||||||
return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
||||||
}
|
}
|
||||||
|
|
||||||
steam, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)
|
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
env := engine.Env{}
|
env := engine.Env{}
|
||||||
if err := env.Decode(steam); err != nil {
|
if err := env.Decode(stream); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
config := env.GetSubEnv("Config")
|
config := env.GetSubEnv("Config")
|
||||||
|
@ -650,8 +712,24 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
||||||
v.Set("stderr", "1")
|
v.Set("stderr", "1")
|
||||||
|
|
||||||
cErr = promise.Go(func() error {
|
cErr = promise.Go(func() error {
|
||||||
return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil, nil)
|
return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, hijacked, nil)
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
close(hijacked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acknowledge the hijack before starting
|
||||||
|
select {
|
||||||
|
case closer := <-hijacked:
|
||||||
|
// Make sure that the hijack gets closed when returning (results
|
||||||
|
// in closing the hijack chan and freeing server's goroutines)
|
||||||
|
if closer != nil {
|
||||||
|
defer closer.Close()
|
||||||
|
}
|
||||||
|
case err := <-cErr:
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var encounteredError error
|
var encounteredError error
|
||||||
|
@ -681,7 +759,16 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
||||||
log.Errorf("Error monitoring TTY size: %s", err)
|
log.Errorf("Error monitoring TTY size: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return <-cErr
|
if attchErr := <-cErr; attchErr != nil {
|
||||||
|
return attchErr
|
||||||
|
}
|
||||||
|
_, status, err := getExitCode(cli, cmd.Arg(0))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if status != 0 {
|
||||||
|
return &utils.StatusError{StatusCode: status}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -798,7 +885,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||||
// Remove trailing ','
|
// Remove trailing ','
|
||||||
indented.Truncate(indented.Len() - 1)
|
indented.Truncate(indented.Len() - 1)
|
||||||
}
|
}
|
||||||
indented.WriteByte(']')
|
indented.WriteString("]\n")
|
||||||
|
|
||||||
if tmpl == nil {
|
if tmpl == nil {
|
||||||
if _, err := io.Copy(cli.out, indented); err != nil {
|
if _, err := io.Copy(cli.out, indented); err != nil {
|
||||||
|
@ -857,13 +944,13 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
steam, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)
|
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
env := engine.Env{}
|
env := engine.Env{}
|
||||||
if err := env.Decode(steam); err != nil {
|
if err := env.Decode(stream); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ports := nat.PortMap{}
|
ports := nat.PortMap{}
|
||||||
|
@ -1195,7 +1282,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
||||||
)
|
)
|
||||||
taglessRemote, tag := parsers.ParseRepositoryTag(remote)
|
taglessRemote, tag := parsers.ParseRepositoryTag(remote)
|
||||||
if tag == "" && !*allTags {
|
if tag == "" && !*allTags {
|
||||||
newRemote = taglessRemote + ":latest"
|
newRemote = taglessRemote + ":" + graph.DEFAULTTAG
|
||||||
}
|
}
|
||||||
if tag != "" && *allTags {
|
if tag != "" && *allTags {
|
||||||
return fmt.Errorf("tag can't be used with --all-tags/-a")
|
return fmt.Errorf("tag can't be used with --all-tags/-a")
|
||||||
|
@ -1244,7 +1331,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) CmdImages(args ...string) error {
|
func (cli *DockerCli) CmdImages(args ...string) error {
|
||||||
cmd := cli.Subcmd("images", "[NAME]", "List images")
|
cmd := cli.Subcmd("images", "[REPOSITORY]", "List images")
|
||||||
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
||||||
all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (by default filter out the intermediate image layers)")
|
all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (by default filter out the intermediate image layers)")
|
||||||
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
||||||
|
@ -1274,6 +1361,12 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for name := range imageFilterArgs {
|
||||||
|
if _, ok := acceptedImageFilterTags[name]; !ok {
|
||||||
|
return fmt.Errorf("Invalid filter '%s'", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
matchName := cmd.Arg(0)
|
matchName := cmd.Arg(0)
|
||||||
// FIXME: --viz and --tree are deprecated. Remove them in a future version.
|
// FIXME: --viz and --tree are deprecated. Remove them in a future version.
|
||||||
if *flViz || *flTree {
|
if *flViz || *flTree {
|
||||||
|
@ -1483,7 +1576,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||||
|
|
||||||
cmd = cli.Subcmd("ps", "", "List containers")
|
cmd = cli.Subcmd("ps", "", "List containers")
|
||||||
quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
||||||
size = cmd.Bool([]string{"s", "-size"}, false, "Display sizes")
|
size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes")
|
||||||
all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers. Only running containers are shown by default.")
|
all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers. Only running containers are shown by default.")
|
||||||
noTrunc = cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
noTrunc = cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
||||||
nLatest = cmd.Bool([]string{"l", "-latest"}, false, "Show only the latest created container, include non-running ones.")
|
nLatest = cmd.Bool([]string{"l", "-latest"}, false, "Show only the latest created container, include non-running ones.")
|
||||||
|
@ -1692,6 +1785,10 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||||
cmd := cli.Subcmd("events", "", "Get real time events from the server")
|
cmd := cli.Subcmd("events", "", "Get real time events from the server")
|
||||||
since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
|
since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
|
||||||
until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
|
until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
|
||||||
|
|
||||||
|
flFilter := opts.NewListOpts(nil)
|
||||||
|
cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'event=stop')")
|
||||||
|
|
||||||
if err := cmd.Parse(args); err != nil {
|
if err := cmd.Parse(args); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1701,9 +1798,20 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
v = url.Values{}
|
v = url.Values{}
|
||||||
loc = time.FixedZone(time.Now().Zone())
|
loc = time.FixedZone(time.Now().Zone())
|
||||||
|
eventFilterArgs = filters.Args{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Consolidate all filter flags, and sanity check them early.
|
||||||
|
// They'll get process in the daemon/server.
|
||||||
|
for _, f := range flFilter.GetAll() {
|
||||||
|
var err error
|
||||||
|
eventFilterArgs, err = filters.ParseFlag(f, eventFilterArgs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
var setTime = func(key, value string) {
|
var setTime = func(key, value string) {
|
||||||
format := timeutils.RFC3339NanoFixed
|
format := timeutils.RFC3339NanoFixed
|
||||||
if len(value) < len(format) {
|
if len(value) < len(format) {
|
||||||
|
@ -1721,6 +1829,13 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||||
if *until != "" {
|
if *until != "" {
|
||||||
setTime("until", *until)
|
setTime("until", *until)
|
||||||
}
|
}
|
||||||
|
if len(eventFilterArgs) > 0 {
|
||||||
|
filterJson, err := filters.ToParam(eventFilterArgs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Set("filters", filterJson)
|
||||||
|
}
|
||||||
if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil {
|
if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1797,13 +1912,13 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||||
}
|
}
|
||||||
name := cmd.Arg(0)
|
name := cmd.Arg(0)
|
||||||
|
|
||||||
steam, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false)
|
stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
env := engine.Env{}
|
env := engine.Env{}
|
||||||
if err := env.Decode(steam); err != nil {
|
if err := env.Decode(stream); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1827,7 +1942,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||||
var (
|
var (
|
||||||
cmd = cli.Subcmd("attach", "CONTAINER", "Attach to a running container")
|
cmd = cli.Subcmd("attach", "CONTAINER", "Attach to a running container")
|
||||||
noStdin = cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN")
|
noStdin = cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN")
|
||||||
proxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process (even in non-TTY mode). SIGCHLD, SIGKILL, and SIGSTOP are not proxied.")
|
proxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process (non-TTY mode only). SIGCHLD, SIGKILL, and SIGSTOP are not proxied.")
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := cmd.Parse(args); err != nil {
|
if err := cmd.Parse(args); err != nil {
|
||||||
|
@ -1859,6 +1974,10 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||||
tty = config.GetBool("Tty")
|
tty = config.GetBool("Tty")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if err := cli.CheckTtyInput(!*noStdin, tty); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if tty && cli.isTerminalOut {
|
if tty && cli.isTerminalOut {
|
||||||
if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
||||||
log.Debugf("Error monitoring TTY size: %s", err)
|
log.Debugf("Error monitoring TTY size: %s", err)
|
||||||
|
@ -1994,7 +2113,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
||||||
repos, tag := parsers.ParseRepositoryTag(image)
|
repos, tag := parsers.ParseRepositoryTag(image)
|
||||||
// pull only the image tagged 'latest' if no tag was specified
|
// pull only the image tagged 'latest' if no tag was specified
|
||||||
if tag == "" {
|
if tag == "" {
|
||||||
tag = "latest"
|
tag = graph.DEFAULTTAG
|
||||||
}
|
}
|
||||||
v.Set("fromImage", repos)
|
v.Set("fromImage", repos)
|
||||||
v.Set("tag", tag)
|
v.Set("tag", tag)
|
||||||
|
@ -2083,7 +2202,11 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
|
||||||
stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false)
|
stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false)
|
||||||
//if image not found try to pull it
|
//if image not found try to pull it
|
||||||
if statusCode == 404 {
|
if statusCode == 404 {
|
||||||
fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", config.Image)
|
repo, tag := parsers.ParseRepositoryTag(config.Image)
|
||||||
|
if tag == "" {
|
||||||
|
tag = graph.DEFAULTTAG
|
||||||
|
}
|
||||||
|
fmt.Fprintf(cli.err, "Unable to find image '%s:%s' locally\n", repo, tag)
|
||||||
|
|
||||||
// we don't want to write to stdout anything apart from container.ID
|
// we don't want to write to stdout anything apart from container.ID
|
||||||
if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil {
|
if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil {
|
||||||
|
@ -2124,7 +2247,7 @@ func (cli *DockerCli) CmdCreate(args ...string) error {
|
||||||
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
|
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
|
||||||
)
|
)
|
||||||
|
|
||||||
config, hostConfig, cmd, err := runconfig.Parse(cmd, args, nil)
|
config, hostConfig, cmd, err := runconfig.Parse(cmd, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -2151,7 +2274,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
var (
|
var (
|
||||||
flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)")
|
flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)")
|
||||||
flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run the container in the background and print the new container ID")
|
flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run the container in the background and print the new container ID")
|
||||||
flSigProxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy received signals to the process (even in non-TTY mode). SIGCHLD, SIGSTOP, and SIGKILL are not proxied.")
|
flSigProxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy received signals to the process (non-TTY mode only). SIGCHLD, SIGSTOP, and SIGKILL are not proxied.")
|
||||||
flName = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container")
|
flName = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container")
|
||||||
flAttach *opts.ListOpts
|
flAttach *opts.ListOpts
|
||||||
|
|
||||||
|
@ -2160,7 +2283,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
|
ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
|
||||||
)
|
)
|
||||||
|
|
||||||
config, hostConfig, cmd, err := runconfig.Parse(cmd, args, nil)
|
config, hostConfig, cmd, err := runconfig.Parse(cmd, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -2169,7 +2292,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if *flDetach {
|
if !*flDetach {
|
||||||
|
if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if fl := cmd.Lookup("attach"); fl != nil {
|
if fl := cmd.Lookup("attach"); fl != nil {
|
||||||
flAttach = fl.Value.(*opts.ListOpts)
|
flAttach = fl.Value.(*opts.ListOpts)
|
||||||
if flAttach.Len() != 0 {
|
if flAttach.Len() != 0 {
|
||||||
|
@ -2186,7 +2313,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
config.StdinOnce = false
|
config.StdinOnce = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable flSigProxy in case on TTY
|
// Disable flSigProxy when in TTY mode
|
||||||
sigProxy := *flSigProxy
|
sigProxy := *flSigProxy
|
||||||
if config.Tty {
|
if config.Tty {
|
||||||
sigProxy = false
|
sigProxy = false
|
||||||
|
@ -2208,7 +2335,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
)
|
)
|
||||||
|
|
||||||
if !config.AttachStdout && !config.AttachStderr {
|
if !config.AttachStdout && !config.AttachStderr {
|
||||||
// Make this asynchrone in order to let the client write to stdin before having to read the ID
|
// Make this asynchronous to allow the client to write to stdin before having to read the ID
|
||||||
waitDisplayId = make(chan struct{})
|
waitDisplayId = make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
defer close(waitDisplayId)
|
defer close(waitDisplayId)
|
||||||
|
@ -2220,7 +2347,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
return ErrConflictRestartPolicyAndAutoRemove
|
return ErrConflictRestartPolicyAndAutoRemove
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to instanciate the chan because the select needs it. It can
|
// We need to instantiate the chan because the select needs it. It can
|
||||||
// be closed but can't be uninitialized.
|
// be closed but can't be uninitialized.
|
||||||
hijacked := make(chan io.Closer)
|
hijacked := make(chan io.Closer)
|
||||||
|
|
||||||
|
@ -2267,8 +2394,8 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
// Acknowledge the hijack before starting
|
// Acknowledge the hijack before starting
|
||||||
select {
|
select {
|
||||||
case closer := <-hijacked:
|
case closer := <-hijacked:
|
||||||
// Make sure that hijack gets closed when returning. (result
|
// Make sure that the hijack gets closed when returning (results
|
||||||
// in closing hijack chan and freeing server's goroutines.
|
// in closing the hijack chan and freeing server's goroutines)
|
||||||
if closer != nil {
|
if closer != nil {
|
||||||
defer closer.Close()
|
defer closer.Close()
|
||||||
}
|
}
|
||||||
|
@ -2280,7 +2407,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
//start the container
|
//start the container
|
||||||
if _, _, err = readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/start", hostConfig, false)); err != nil {
|
if _, _, err = readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/start", nil, false)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2320,15 +2447,15 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// No Autoremove: Simply retrieve the exit code
|
||||||
if !config.Tty {
|
if !config.Tty {
|
||||||
// In non-tty mode, we can't dettach, so we know we need to wait.
|
// In non-TTY mode, we can't detach, so we must wait for container exit
|
||||||
if status, err = waitForExit(cli, runResult.Get("Id")); err != nil {
|
if status, err = waitForExit(cli, runResult.Get("Id")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// In TTY mode, there is a race. If the process dies too slowly, the state can be update after the getExitCode call
|
// In TTY mode, there is a race: if the process dies too slowly, the state could
|
||||||
// and result in a wrong exit code.
|
// be updated after the getExitCode call and result in the wrong exit code being reported
|
||||||
// No Autoremove: Simply retrieve the exit code
|
|
||||||
if _, status, err = getExitCode(cli, runResult.Get("Id")); err != nil {
|
if _, status, err = getExitCode(cli, runResult.Get("Id")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -2402,7 +2529,10 @@ func (cli *DockerCli) CmdSave(args ...string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else if cli.isTerminalOut {
|
||||||
|
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cmd.Args()) == 1 {
|
if len(cmd.Args()) == 1 {
|
||||||
image := cmd.Arg(0)
|
image := cmd.Arg(0)
|
||||||
if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil {
|
if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil {
|
||||||
|
@ -2450,7 +2580,7 @@ func (cli *DockerCli) CmdLoad(args ...string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) CmdExec(args ...string) error {
|
func (cli *DockerCli) CmdExec(args ...string) error {
|
||||||
cmd := cli.Subcmd("exec", "CONTAINER COMMAND [ARG...]", "Run a command in an existing container")
|
cmd := cli.Subcmd("exec", "CONTAINER COMMAND [ARG...]", "Run a command in a running container")
|
||||||
|
|
||||||
execConfig, err := runconfig.ParseExec(cmd, args)
|
execConfig, err := runconfig.ParseExec(cmd, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2478,10 +2608,16 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if execConfig.Detach {
|
if !execConfig.Detach {
|
||||||
|
if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, false)); err != nil {
|
if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, false)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// For now don't print this - wait for when we support exec wait()
|
||||||
|
// fmt.Fprintf(cli.out, "%s\n", execID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2544,5 +2680,14 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var status int
|
||||||
|
if _, status, err = getExecExitCode(cli, execID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if status != 0 {
|
||||||
|
return &utils.StatusError{StatusCode: status}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/dockerversion"
|
"github.com/docker/docker/dockerversion"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
"github.com/docker/docker/pkg/promise"
|
"github.com/docker/docker/pkg/promise"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
|
|
|
@ -8,20 +8,18 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
gosignal "os/signal"
|
gosignal "os/signal"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/dockerversion"
|
"github.com/docker/docker/dockerversion"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/pkg/log"
|
"github.com/docker/docker/pkg/signal"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
|
@ -33,22 +31,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (cli *DockerCli) HTTPClient() *http.Client {
|
func (cli *DockerCli) HTTPClient() *http.Client {
|
||||||
tr := &http.Transport{
|
return &http.Client{Transport: cli.transport}
|
||||||
TLSClientConfig: cli.tlsConfig,
|
|
||||||
Dial: func(network, addr string) (net.Conn, error) {
|
|
||||||
// Why 32? See issue 8035
|
|
||||||
return net.DialTimeout(cli.proto, cli.addr, 32*time.Second)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if cli.proto == "unix" {
|
|
||||||
// XXX workaround for net/http Transport which caches connections, but is
|
|
||||||
// intended for tcp connections, not unix sockets.
|
|
||||||
tr.DisableKeepAlives = true
|
|
||||||
|
|
||||||
// no need in compressing for local communications
|
|
||||||
tr.DisableCompression = true
|
|
||||||
}
|
|
||||||
return &http.Client{Transport: tr}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
|
func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
|
||||||
|
@ -113,7 +96,12 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b
|
||||||
if strings.Contains(err.Error(), "connection refused") {
|
if strings.Contains(err.Error(), "connection refused") {
|
||||||
return nil, -1, ErrConnectionRefused
|
return nil, -1, ErrConnectionRefused
|
||||||
}
|
}
|
||||||
return nil, -1, err
|
|
||||||
|
if cli.tlsConfig == nil {
|
||||||
|
return nil, -1, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err)
|
||||||
|
}
|
||||||
|
return nil, -1, fmt.Errorf("An error occurred trying to connect: %v", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||||
|
@ -228,7 +216,7 @@ func waitForExit(cli *DockerCli, containerId string) (int, error) {
|
||||||
// getExitCode perform an inspect on the container. It returns
|
// getExitCode perform an inspect on the container. It returns
|
||||||
// the running state and the exit code.
|
// the running state and the exit code.
|
||||||
func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
||||||
steam, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil, false)
|
stream, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we can't connect, then the daemon probably died.
|
// If we can't connect, then the daemon probably died.
|
||||||
if err != ErrConnectionRefused {
|
if err != ErrConnectionRefused {
|
||||||
|
@ -238,7 +226,7 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var result engine.Env
|
var result engine.Env
|
||||||
if err := result.Decode(steam); err != nil {
|
if err := result.Decode(stream); err != nil {
|
||||||
return false, -1, err
|
return false, -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,11 +234,31 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
||||||
return state.GetBool("Running"), state.GetInt("ExitCode"), nil
|
return state.GetBool("Running"), state.GetInt("ExitCode"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getExecExitCode perform an inspect on the exec command. It returns
|
||||||
|
// the running state and the exit code.
|
||||||
|
func getExecExitCode(cli *DockerCli, execId string) (bool, int, error) {
|
||||||
|
stream, _, err := cli.call("GET", "/exec/"+execId+"/json", nil, false)
|
||||||
|
if err != nil {
|
||||||
|
// If we can't connect, then the daemon probably died.
|
||||||
|
if err != ErrConnectionRefused {
|
||||||
|
return false, -1, err
|
||||||
|
}
|
||||||
|
return false, -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result engine.Env
|
||||||
|
if err := result.Decode(stream); err != nil {
|
||||||
|
return false, -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.GetBool("Running"), result.GetInt("ExitCode"), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
||||||
cli.resizeTty(id, isExec)
|
cli.resizeTty(id, isExec)
|
||||||
|
|
||||||
sigchan := make(chan os.Signal, 1)
|
sigchan := make(chan os.Signal, 1)
|
||||||
gosignal.Notify(sigchan, syscall.SIGWINCH)
|
gosignal.Notify(sigchan, signal.SIGWINCH)
|
||||||
go func() {
|
go func() {
|
||||||
for _ = range sigchan {
|
for _ = range sigchan {
|
||||||
cli.resizeTty(id, isExec)
|
cli.resizeTty(id, isExec)
|
||||||
|
|
|
@ -3,16 +3,19 @@ package api
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"mime"
|
"mime"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/pkg/version"
|
"github.com/docker/docker/pkg/version"
|
||||||
|
"github.com/docker/libtrust"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
APIVERSION version.Version = "1.15"
|
APIVERSION version.Version = "1.16"
|
||||||
DEFAULTHTTPHOST = "127.0.0.1"
|
DEFAULTHTTPHOST = "127.0.0.1"
|
||||||
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
||||||
)
|
)
|
||||||
|
@ -47,3 +50,25 @@ func MatchesContentType(contentType, expectedType string) bool {
|
||||||
}
|
}
|
||||||
return err == nil && mimetype == expectedType
|
return err == nil && mimetype == expectedType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadOrCreateTrustKey attempts to load the libtrust key at the given path,
|
||||||
|
// otherwise generates a new one
|
||||||
|
func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
|
||||||
|
err := os.MkdirAll(path.Dir(trustKeyPath), 0700)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
trustKey, err := libtrust.LoadKeyFile(trustKeyPath)
|
||||||
|
if err == libtrust.ErrKeyFileDoesNotExist {
|
||||||
|
trustKey, err = libtrust.GenerateECP256PrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error generating key: %s", err)
|
||||||
|
}
|
||||||
|
if err := libtrust.SaveKey(trustKeyPath, trustKey); err != nil {
|
||||||
|
return nil, fmt.Errorf("Error saving key file: %s", err)
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error loading key file: %s", err)
|
||||||
|
}
|
||||||
|
return trustKey, nil
|
||||||
|
}
|
||||||
|
|
|
@ -3,8 +3,7 @@ package server
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"expvar"
|
"expvar"
|
||||||
|
@ -19,14 +18,17 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
|
||||||
"code.google.com/p/go.net/websocket"
|
"code.google.com/p/go.net/websocket"
|
||||||
"github.com/docker/libcontainer/user"
|
"github.com/docker/libcontainer/user"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/pkg/listenbuffer"
|
"github.com/docker/docker/pkg/listenbuffer"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/docker/docker/pkg/systemd"
|
"github.com/docker/docker/pkg/systemd"
|
||||||
|
@ -39,6 +41,18 @@ var (
|
||||||
activationLock chan struct{}
|
activationLock chan struct{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type HttpServer struct {
|
||||||
|
srv *http.Server
|
||||||
|
l net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HttpServer) Serve() error {
|
||||||
|
return s.srv.Serve(s.l)
|
||||||
|
}
|
||||||
|
func (s *HttpServer) Close() error {
|
||||||
|
return s.l.Close()
|
||||||
|
}
|
||||||
|
|
||||||
type HttpApiFunc func(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error
|
type HttpApiFunc func(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error
|
||||||
|
|
||||||
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
||||||
|
@ -51,6 +65,18 @@ func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
||||||
return conn, conn, nil
|
return conn, conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func closeStreams(streams ...interface{}) {
|
||||||
|
for _, stream := range streams {
|
||||||
|
if tcpc, ok := stream.(interface {
|
||||||
|
CloseWrite() error
|
||||||
|
}); ok {
|
||||||
|
tcpc.CloseWrite()
|
||||||
|
} else if closer, ok := stream.(io.Closer); ok {
|
||||||
|
closer.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check to make sure request's Content-Type is application/json
|
// Check to make sure request's Content-Type is application/json
|
||||||
func checkForJson(r *http.Request) error {
|
func checkForJson(r *http.Request) error {
|
||||||
ct := r.Header.Get("Content-Type")
|
ct := r.Header.Get("Content-Type")
|
||||||
|
@ -92,17 +118,18 @@ func httpError(w http.ResponseWriter, err error) {
|
||||||
// FIXME: this is brittle and should not be necessary.
|
// FIXME: this is brittle and should not be necessary.
|
||||||
// If we need to differentiate between different possible error types, we should
|
// If we need to differentiate between different possible error types, we should
|
||||||
// create appropriate error types with clearly defined meaning.
|
// create appropriate error types with clearly defined meaning.
|
||||||
if strings.Contains(err.Error(), "No such") {
|
errStr := strings.ToLower(err.Error())
|
||||||
|
if strings.Contains(errStr, "no such") {
|
||||||
statusCode = http.StatusNotFound
|
statusCode = http.StatusNotFound
|
||||||
} else if strings.Contains(err.Error(), "Bad parameter") {
|
} else if strings.Contains(errStr, "bad parameter") {
|
||||||
statusCode = http.StatusBadRequest
|
statusCode = http.StatusBadRequest
|
||||||
} else if strings.Contains(err.Error(), "Conflict") {
|
} else if strings.Contains(errStr, "conflict") {
|
||||||
statusCode = http.StatusConflict
|
statusCode = http.StatusConflict
|
||||||
} else if strings.Contains(err.Error(), "Impossible") {
|
} else if strings.Contains(errStr, "impossible") {
|
||||||
statusCode = http.StatusNotAcceptable
|
statusCode = http.StatusNotAcceptable
|
||||||
} else if strings.Contains(err.Error(), "Wrong login/password") {
|
} else if strings.Contains(errStr, "wrong login/password") {
|
||||||
statusCode = http.StatusUnauthorized
|
statusCode = http.StatusUnauthorized
|
||||||
} else if strings.Contains(err.Error(), "hasn't been activated") {
|
} else if strings.Contains(errStr, "hasn't been activated") {
|
||||||
statusCode = http.StatusForbidden
|
statusCode = http.StatusForbidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,6 +327,7 @@ func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWrite
|
||||||
streamJSON(job, w, true)
|
streamJSON(job, w, true)
|
||||||
job.Setenv("since", r.Form.Get("since"))
|
job.Setenv("since", r.Form.Get("since"))
|
||||||
job.Setenv("until", r.Form.Get("until"))
|
job.Setenv("until", r.Form.Get("until"))
|
||||||
|
job.Setenv("filters", r.Form.Get("filters"))
|
||||||
return job.Run()
|
return job.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -855,20 +883,7 @@ func postContainersAttach(eng *engine.Engine, version version.Version, w http.Re
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer closeStreams(inStream, outStream)
|
||||||
if tcpc, ok := inStream.(*net.TCPConn); ok {
|
|
||||||
tcpc.CloseWrite()
|
|
||||||
} else {
|
|
||||||
inStream.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
defer func() {
|
|
||||||
if tcpc, ok := outStream.(*net.TCPConn); ok {
|
|
||||||
tcpc.CloseWrite()
|
|
||||||
} else if closer, ok := outStream.(io.Closer); ok {
|
|
||||||
closer.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var errStream io.Writer
|
var errStream io.Writer
|
||||||
|
|
||||||
|
@ -941,6 +956,15 @@ func getContainersByName(eng *engine.Engine, version version.Version, w http.Res
|
||||||
return job.Run()
|
return job.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getExecByID(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
if vars == nil {
|
||||||
|
return fmt.Errorf("Missing parameter 'id'")
|
||||||
|
}
|
||||||
|
var job = eng.Job("execInspect", vars["id"])
|
||||||
|
streamJSON(job, w, false)
|
||||||
|
return job.Run()
|
||||||
|
}
|
||||||
|
|
||||||
func getImagesByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func getImagesByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if vars == nil {
|
if vars == nil {
|
||||||
return fmt.Errorf("Missing parameter")
|
return fmt.Errorf("Missing parameter")
|
||||||
|
@ -1001,6 +1025,9 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite
|
||||||
} else {
|
} else {
|
||||||
job.Setenv("rm", r.FormValue("rm"))
|
job.Setenv("rm", r.FormValue("rm"))
|
||||||
}
|
}
|
||||||
|
if r.FormValue("pull") == "1" && version.GreaterThanOrEqualTo("1.16") {
|
||||||
|
job.Setenv("pull", "1")
|
||||||
|
}
|
||||||
job.Stdin.Add(r.Body)
|
job.Stdin.Add(r.Body)
|
||||||
job.Setenv("remote", r.FormValue("remote"))
|
job.Setenv("remote", r.FormValue("remote"))
|
||||||
job.Setenv("t", r.FormValue("t"))
|
job.Setenv("t", r.FormValue("t"))
|
||||||
|
@ -1050,7 +1077,7 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp
|
||||||
w.Header().Set("Content-Type", "application/x-tar")
|
w.Header().Set("Content-Type", "application/x-tar")
|
||||||
if err := job.Run(); err != nil {
|
if err := job.Run(); err != nil {
|
||||||
log.Errorf("%s", err.Error())
|
log.Errorf("%s", err.Error())
|
||||||
if strings.Contains(err.Error(), "No such container") {
|
if strings.Contains(strings.ToLower(err.Error()), "no such container") {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
} else if strings.Contains(err.Error(), "no such file or directory") {
|
} else if strings.Contains(err.Error(), "no such file or directory") {
|
||||||
return fmt.Errorf("Could not find the file %s in container %s", origResource, vars["name"])
|
return fmt.Errorf("Could not find the file %s in container %s", origResource, vars["name"])
|
||||||
|
@ -1106,21 +1133,7 @@ func postContainerExecStart(eng *engine.Engine, version version.Version, w http.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer closeStreams(inStream, outStream)
|
||||||
defer func() {
|
|
||||||
if tcpc, ok := inStream.(*net.TCPConn); ok {
|
|
||||||
tcpc.CloseWrite()
|
|
||||||
} else {
|
|
||||||
inStream.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
defer func() {
|
|
||||||
if tcpc, ok := outStream.(*net.TCPConn); ok {
|
|
||||||
tcpc.CloseWrite()
|
|
||||||
} else if closer, ok := outStream.(io.Closer); ok {
|
|
||||||
closer.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var errStream io.Writer
|
var errStream io.Writer
|
||||||
|
|
||||||
|
@ -1166,7 +1179,7 @@ func optionsHandler(eng *engine.Engine, version version.Version, w http.Response
|
||||||
}
|
}
|
||||||
func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
|
func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Add("Access-Control-Allow-Origin", "*")
|
w.Header().Add("Access-Control-Allow-Origin", "*")
|
||||||
w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
|
w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth")
|
||||||
w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
|
w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1231,6 +1244,7 @@ func AttachProfiler(router *mux.Router) {
|
||||||
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||||
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||||
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||||
|
router.HandleFunc("/debug/pprof/block", pprof.Handler("block").ServeHTTP)
|
||||||
router.HandleFunc("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP)
|
router.HandleFunc("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP)
|
||||||
router.HandleFunc("/debug/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP)
|
router.HandleFunc("/debug/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP)
|
||||||
router.HandleFunc("/debug/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
|
router.HandleFunc("/debug/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
|
||||||
|
@ -1262,6 +1276,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
|
||||||
"/containers/{name:.*}/top": getContainersTop,
|
"/containers/{name:.*}/top": getContainersTop,
|
||||||
"/containers/{name:.*}/logs": getContainersLogs,
|
"/containers/{name:.*}/logs": getContainersLogs,
|
||||||
"/containers/{name:.*}/attach/ws": wsContainersAttach,
|
"/containers/{name:.*}/attach/ws": wsContainersAttach,
|
||||||
|
"/exec/{id:.*}/json": getExecByID,
|
||||||
},
|
},
|
||||||
"POST": {
|
"POST": {
|
||||||
"/auth": postAuth,
|
"/auth": postAuth,
|
||||||
|
@ -1333,9 +1348,14 @@ func ServeRequest(eng *engine.Engine, apiversion version.Version, w http.Respons
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeFD creates an http.Server and sets it up to serve given a socket activated
|
// serveFd creates an http.Server and sets it up to serve given a socket activated
|
||||||
// argument.
|
// argument.
|
||||||
func ServeFd(addr string, handle http.Handler) error {
|
func serveFd(addr string, job *engine.Job) error {
|
||||||
|
r, err := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
ls, e := systemd.ListenFD(addr)
|
ls, e := systemd.ListenFD(addr)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return e
|
return e
|
||||||
|
@ -1353,7 +1373,7 @@ func ServeFd(addr string, handle http.Handler) error {
|
||||||
for i := range ls {
|
for i := range ls {
|
||||||
listener := ls[i]
|
listener := ls[i]
|
||||||
go func() {
|
go func() {
|
||||||
httpSrv := http.Server{Handler: handle}
|
httpSrv := http.Server{Handler: r}
|
||||||
chErrors <- httpSrv.Serve(listener)
|
chErrors <- httpSrv.Serve(listener)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -1369,7 +1389,11 @@ func ServeFd(addr string, handle http.Handler) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookupGidByName(nameOrGid string) (int, error) {
|
func lookupGidByName(nameOrGid string) (int, error) {
|
||||||
groups, err := user.ParseGroupFilter(func(g *user.Group) bool {
|
groupFile, err := user.GetGroupFile()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
groups, err := user.ParseGroupFileFilter(groupFile, func(g user.Group) bool {
|
||||||
return g.Name == nameOrGid || strconv.Itoa(g.Gid) == nameOrGid
|
return g.Name == nameOrGid || strconv.Itoa(g.Gid) == nameOrGid
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1381,6 +1405,41 @@ func lookupGidByName(nameOrGid string) (int, error) {
|
||||||
return -1, fmt.Errorf("Group %s not found", nameOrGid)
|
return -1, fmt.Errorf("Group %s not found", nameOrGid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setupTls(cert, key, ca string, l net.Listener) (net.Listener, error) {
|
||||||
|
tlsCert, err := tls.LoadX509KeyPair(cert, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?",
|
||||||
|
cert, key, err)
|
||||||
|
}
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
NextProtos: []string{"http/1.1"},
|
||||||
|
Certificates: []tls.Certificate{tlsCert},
|
||||||
|
// Avoid fallback on insecure SSL protocols
|
||||||
|
MinVersion: tls.VersionTLS10,
|
||||||
|
}
|
||||||
|
|
||||||
|
if ca != "" {
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
|
file, err := ioutil.ReadFile(ca)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Couldn't read CA certificate: %s", err)
|
||||||
|
}
|
||||||
|
certPool.AppendCertsFromPEM(file)
|
||||||
|
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||||
|
tlsConfig.ClientCAs = certPool
|
||||||
|
}
|
||||||
|
|
||||||
|
return tls.NewListener(l, tlsConfig), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newListener(proto, addr string, bufferRequests bool) (net.Listener, error) {
|
||||||
|
if bufferRequests {
|
||||||
|
return listenbuffer.NewListenBuffer(proto, addr, activationLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.Listen(proto, addr)
|
||||||
|
}
|
||||||
|
|
||||||
func changeGroup(addr string, nameOrGid string) error {
|
func changeGroup(addr string, nameOrGid string) error {
|
||||||
gid, err := lookupGidByName(nameOrGid)
|
gid, err := lookupGidByName(nameOrGid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1391,99 +1450,95 @@ func changeGroup(addr string, nameOrGid string) error {
|
||||||
return os.Chown(addr, 0, gid)
|
return os.Chown(addr, 0, gid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenAndServe sets up the required http.Server and gets it listening for
|
func setSocketGroup(addr, group string) error {
|
||||||
// each addr passed in and does protocol specific checking.
|
if group == "" {
|
||||||
func ListenAndServe(proto, addr string, job *engine.Job) error {
|
return nil
|
||||||
var l net.Listener
|
}
|
||||||
|
|
||||||
|
if err := changeGroup(addr, group); err != nil {
|
||||||
|
if group != "docker" {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debugf("Warning: could not chgrp %s to docker: %v", addr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupUnixHttp(addr string, job *engine.Job) (*HttpServer, error) {
|
||||||
r, err := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version"))
|
r, err := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if proto == "fd" {
|
if err := syscall.Unlink(addr); err != nil && !os.IsNotExist(err) {
|
||||||
return ServeFd(addr, r)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
mask := syscall.Umask(0777)
|
||||||
|
defer syscall.Umask(mask)
|
||||||
|
|
||||||
if proto == "unix" {
|
l, err := newListener("unix", addr, job.GetenvBool("BufferRequests"))
|
||||||
if err := syscall.Unlink(addr); err != nil && !os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var oldmask int
|
|
||||||
if proto == "unix" {
|
|
||||||
oldmask = syscall.Umask(0777)
|
|
||||||
}
|
|
||||||
|
|
||||||
if job.GetenvBool("BufferRequests") {
|
|
||||||
l, err = listenbuffer.NewListenBuffer(proto, addr, activationLock)
|
|
||||||
} else {
|
|
||||||
l, err = net.Listen(proto, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if proto == "unix" {
|
|
||||||
syscall.Umask(oldmask)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if proto != "unix" && (job.GetenvBool("Tls") || job.GetenvBool("TlsVerify")) {
|
if err := setSocketGroup(addr, job.Getenv("SocketGroup")); err != nil {
|
||||||
tlsCert := job.Getenv("TlsCert")
|
return nil, err
|
||||||
tlsKey := job.Getenv("TlsKey")
|
}
|
||||||
cert, err := tls.LoadX509KeyPair(tlsCert, tlsKey)
|
|
||||||
if err != nil {
|
if err := os.Chmod(addr, 0660); err != nil {
|
||||||
return fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?",
|
return nil, err
|
||||||
tlsCert, tlsKey, err)
|
}
|
||||||
}
|
|
||||||
tlsConfig := &tls.Config{
|
return &HttpServer{&http.Server{Addr: addr, Handler: r}, l}, nil
|
||||||
NextProtos: []string{"http/1.1"},
|
}
|
||||||
Certificates: []tls.Certificate{cert},
|
|
||||||
// Avoid fallback on insecure SSL protocols
|
func setupTcpHttp(addr string, job *engine.Job) (*HttpServer, error) {
|
||||||
MinVersion: tls.VersionTLS10,
|
if !strings.HasPrefix(addr, "127.0.0.1") && !job.GetenvBool("TlsVerify") {
|
||||||
}
|
log.Infof("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := newListener("tcp", addr, job.GetenvBool("BufferRequests"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if job.GetenvBool("Tls") || job.GetenvBool("TlsVerify") {
|
||||||
|
var tlsCa string
|
||||||
if job.GetenvBool("TlsVerify") {
|
if job.GetenvBool("TlsVerify") {
|
||||||
certPool := x509.NewCertPool()
|
tlsCa = job.Getenv("TlsCa")
|
||||||
file, err := ioutil.ReadFile(job.Getenv("TlsCa"))
|
}
|
||||||
if err != nil {
|
l, err = setupTls(job.Getenv("TlsCert"), job.Getenv("TlsKey"), tlsCa, l)
|
||||||
return fmt.Errorf("Couldn't read CA certificate: %s", err)
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
certPool.AppendCertsFromPEM(file)
|
|
||||||
|
|
||||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
|
||||||
tlsConfig.ClientCAs = certPool
|
|
||||||
}
|
}
|
||||||
l = tls.NewListener(l, tlsConfig)
|
|
||||||
}
|
}
|
||||||
|
return &HttpServer{&http.Server{Addr: addr, Handler: r}, l}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer sets up the required Server and does protocol specific checking.
|
||||||
|
func NewServer(proto, addr string, job *engine.Job) (Server, error) {
|
||||||
// Basic error and sanity checking
|
// Basic error and sanity checking
|
||||||
switch proto {
|
switch proto {
|
||||||
|
case "fd":
|
||||||
|
return nil, serveFd(addr, job)
|
||||||
case "tcp":
|
case "tcp":
|
||||||
if !strings.HasPrefix(addr, "127.0.0.1") && !job.GetenvBool("TlsVerify") {
|
return setupTcpHttp(addr, job)
|
||||||
log.Infof("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
|
||||||
}
|
|
||||||
case "unix":
|
case "unix":
|
||||||
socketGroup := job.Getenv("SocketGroup")
|
return setupUnixHttp(addr, job)
|
||||||
if socketGroup != "" {
|
|
||||||
if err := changeGroup(addr, socketGroup); err != nil {
|
|
||||||
if socketGroup == "docker" {
|
|
||||||
// if the user hasn't explicitly specified the group ownership, don't fail on errors.
|
|
||||||
log.Debugf("Warning: could not chgrp %s to docker: %s", addr, err.Error())
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if err := os.Chmod(addr, 0660); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Invalid protocol format.")
|
return nil, fmt.Errorf("Invalid protocol format.")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
httpSrv := http.Server{Addr: addr, Handler: r}
|
type Server interface {
|
||||||
return httpSrv.Serve(l)
|
Serve() error
|
||||||
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeApi loops through all of the protocols sent in to docker and spawns
|
// ServeApi loops through all of the protocols sent in to docker and spawns
|
||||||
|
@ -1505,7 +1560,12 @@ func ServeApi(job *engine.Job) engine.Status {
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
log.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1])
|
log.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1])
|
||||||
chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], job)
|
srv, err := NewServer(protoAddrParts[0], protoAddrParts[1], job)
|
||||||
|
if err != nil {
|
||||||
|
chErrors <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
chErrors <- srv.Serve()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,8 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/nat"
|
"github.com/docker/docker/nat"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
)
|
)
|
||||||
|
@ -31,21 +31,39 @@ func nullDispatch(b *Builder, args []string, attributes map[string]bool, origina
|
||||||
// in the dockerfile available from the next statement on via ${foo}.
|
// in the dockerfile available from the next statement on via ${foo}.
|
||||||
//
|
//
|
||||||
func env(b *Builder, args []string, attributes map[string]bool, original string) error {
|
func env(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) != 2 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("ENV accepts two arguments")
|
return fmt.Errorf("ENV is missing arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
fullEnv := fmt.Sprintf("%s=%s", args[0], args[1])
|
if len(args)%2 != 0 {
|
||||||
|
// should never get here, but just in case
|
||||||
|
return fmt.Errorf("Bad input to ENV, too many args")
|
||||||
|
}
|
||||||
|
|
||||||
for i, envVar := range b.Config.Env {
|
commitStr := "ENV"
|
||||||
envParts := strings.SplitN(envVar, "=", 2)
|
|
||||||
if args[0] == envParts[0] {
|
for j := 0; j < len(args); j++ {
|
||||||
b.Config.Env[i] = fullEnv
|
// name ==> args[j]
|
||||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("ENV %s", fullEnv))
|
// value ==> args[j+1]
|
||||||
|
newVar := args[j] + "=" + args[j+1] + ""
|
||||||
|
commitStr += " " + newVar
|
||||||
|
|
||||||
|
gotOne := false
|
||||||
|
for i, envVar := range b.Config.Env {
|
||||||
|
envParts := strings.SplitN(envVar, "=", 2)
|
||||||
|
if envParts[0] == args[j] {
|
||||||
|
b.Config.Env[i] = newVar
|
||||||
|
gotOne = true
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if !gotOne {
|
||||||
|
b.Config.Env = append(b.Config.Env, newVar)
|
||||||
|
}
|
||||||
|
j++
|
||||||
}
|
}
|
||||||
b.Config.Env = append(b.Config.Env, fullEnv)
|
|
||||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("ENV %s", fullEnv))
|
return b.commit("", b.Config.Cmd, commitStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MAINTAINER some text <maybe@an.email.address>
|
// MAINTAINER some text <maybe@an.email.address>
|
||||||
|
@ -97,6 +115,12 @@ func from(b *Builder, args []string, attributes map[string]bool, original string
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
image, err := b.Daemon.Repositories().LookupImage(name)
|
image, err := b.Daemon.Repositories().LookupImage(name)
|
||||||
|
if b.Pull {
|
||||||
|
image, err = b.pullImage(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if b.Daemon.Graph().IsNotExist(err) {
|
if b.Daemon.Graph().IsNotExist(err) {
|
||||||
image, err = b.pullImage(name)
|
image, err = b.pullImage(name)
|
||||||
|
@ -183,7 +207,7 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
|
||||||
runCmd.SetOutput(ioutil.Discard)
|
runCmd.SetOutput(ioutil.Discard)
|
||||||
runCmd.Usage = nil
|
runCmd.Usage = nil
|
||||||
|
|
||||||
config, _, _, err := runconfig.Parse(runCmd, append([]string{b.image}, args...), nil)
|
config, _, _, err := runconfig.Parse(runCmd, append([]string{b.image}, args...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,10 +27,10 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/builder/parser"
|
"github.com/docker/docker/builder/parser"
|
||||||
"github.com/docker/docker/daemon"
|
"github.com/docker/docker/daemon"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
"github.com/docker/docker/pkg/tarsum"
|
"github.com/docker/docker/pkg/tarsum"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
|
@ -90,6 +90,7 @@ type Builder struct {
|
||||||
// controls how images and containers are handled between steps.
|
// controls how images and containers are handled between steps.
|
||||||
Remove bool
|
Remove bool
|
||||||
ForceRemove bool
|
ForceRemove bool
|
||||||
|
Pull bool
|
||||||
|
|
||||||
AuthConfig *registry.AuthConfig
|
AuthConfig *registry.AuthConfig
|
||||||
AuthConfigFile *registry.ConfigFile
|
AuthConfigFile *registry.ConfigFile
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -18,17 +19,17 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/builder/parser"
|
"github.com/docker/docker/builder/parser"
|
||||||
"github.com/docker/docker/daemon"
|
"github.com/docker/docker/daemon"
|
||||||
imagepkg "github.com/docker/docker/image"
|
imagepkg "github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/chrootarchive"
|
"github.com/docker/docker/pkg/chrootarchive"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/pkg/promise"
|
|
||||||
"github.com/docker/docker/pkg/symlink"
|
"github.com/docker/docker/pkg/symlink"
|
||||||
"github.com/docker/docker/pkg/system"
|
"github.com/docker/docker/pkg/system"
|
||||||
"github.com/docker/docker/pkg/tarsum"
|
"github.com/docker/docker/pkg/tarsum"
|
||||||
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
@ -217,7 +218,7 @@ func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
||||||
origPath = strings.TrimPrefix(origPath, "./")
|
origPath = strings.TrimPrefix(origPath, "./")
|
||||||
|
|
||||||
// In the remote/URL case, download it and gen its hashcode
|
// In the remote/URL case, download it and gen its hashcode
|
||||||
if utils.IsURL(origPath) {
|
if urlutil.IsURL(origPath) {
|
||||||
if !allowRemote {
|
if !allowRemote {
|
||||||
return fmt.Errorf("Source can't be a URL for %s", cmdName)
|
return fmt.Errorf("Source can't be a URL for %s", cmdName)
|
||||||
}
|
}
|
||||||
|
@ -257,8 +258,21 @@ func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
||||||
fmt.Fprintf(b.OutStream, "\n")
|
fmt.Fprintf(b.OutStream, "\n")
|
||||||
tmpFile.Close()
|
tmpFile.Close()
|
||||||
|
|
||||||
// Remove the mtime of the newly created tmp file
|
// Set the mtime to the Last-Modified header value if present
|
||||||
if err := system.UtimesNano(tmpFileName, make([]syscall.Timespec, 2)); err != nil {
|
// Otherwise just remove atime and mtime
|
||||||
|
times := make([]syscall.Timespec, 2)
|
||||||
|
|
||||||
|
lastMod := resp.Header.Get("Last-Modified")
|
||||||
|
if lastMod != "" {
|
||||||
|
mTime, err := http.ParseTime(lastMod)
|
||||||
|
// If we can't parse it then just let it default to 'zero'
|
||||||
|
// otherwise use the parsed time value
|
||||||
|
if err == nil {
|
||||||
|
times[1] = syscall.NsecToTimespec(mTime.UnixNano())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := system.UtimesNano(tmpFileName, times); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,25 +528,19 @@ func (b *Builder) create() (*daemon.Container, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) run(c *daemon.Container) error {
|
func (b *Builder) run(c *daemon.Container) error {
|
||||||
var errCh chan error
|
|
||||||
if b.Verbose {
|
|
||||||
errCh = promise.Go(func() error {
|
|
||||||
// FIXME: call the 'attach' job so that daemon.Attach can be made private
|
|
||||||
//
|
|
||||||
// FIXME (LK4D4): Also, maybe makes sense to call "logs" job, it is like attach
|
|
||||||
// but without hijacking for stdin. Also, with attach there can be race
|
|
||||||
// condition because of some output already was printed before it.
|
|
||||||
return <-b.Daemon.Attach(&c.StreamConfig, c.Config.OpenStdin, c.Config.StdinOnce, c.Config.Tty, nil, nil, b.OutStream, b.ErrStream)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//start the container
|
//start the container
|
||||||
if err := c.Start(); err != nil {
|
if err := c.Start(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if errCh != nil {
|
if b.Verbose {
|
||||||
if err := <-errCh; err != nil {
|
logsJob := b.Engine.Job("logs", c.ID)
|
||||||
|
logsJob.Setenv("follow", "1")
|
||||||
|
logsJob.Setenv("stdout", "1")
|
||||||
|
logsJob.Setenv("stderr", "1")
|
||||||
|
logsJob.Stdout.Add(b.OutStream)
|
||||||
|
logsJob.Stderr.Set(b.ErrStream)
|
||||||
|
if err := logsJob.Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -641,37 +649,45 @@ func (b *Builder) addContext(container *daemon.Container, orig, dest string, dec
|
||||||
resPath = path.Join(destPath, path.Base(origPath))
|
resPath = path.Join(destPath, path.Base(origPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
return fixPermissions(resPath, 0, 0)
|
return fixPermissions(origPath, resPath, 0, 0, destExists)
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyAsDirectory(source, destination string, destinationExists bool) error {
|
func copyAsDirectory(source, destination string, destExisted bool) error {
|
||||||
if err := chrootarchive.CopyWithTar(source, destination); err != nil {
|
if err := chrootarchive.CopyWithTar(source, destination); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return fixPermissions(source, destination, 0, 0, destExisted)
|
||||||
|
}
|
||||||
|
|
||||||
if destinationExists {
|
func fixPermissions(source, destination string, uid, gid int, destExisted bool) error {
|
||||||
files, err := ioutil.ReadDir(source)
|
// If the destination didn't already exist, or the destination isn't a
|
||||||
|
// directory, then we should Lchown the destination. Otherwise, we shouldn't
|
||||||
|
// Lchown the destination.
|
||||||
|
destStat, err := os.Stat(destination)
|
||||||
|
if err != nil {
|
||||||
|
// This should *never* be reached, because the destination must've already
|
||||||
|
// been created while untar-ing the context.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
doChownDestination := !destExisted || !destStat.IsDir()
|
||||||
|
|
||||||
|
// We Walk on the source rather than on the destination because we don't
|
||||||
|
// want to change permissions on things we haven't created or modified.
|
||||||
|
return filepath.Walk(source, func(fullpath string, info os.FileInfo, err error) error {
|
||||||
|
// Do not alter the walk root iff. it existed before, as it doesn't fall under
|
||||||
|
// the domain of "things we should chown".
|
||||||
|
if !doChownDestination && (source == fullpath) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path is prefixed by source: substitute with destination instead.
|
||||||
|
cleaned, err := filepath.Rel(source, fullpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
fullpath = path.Join(destination, cleaned)
|
||||||
if err := fixPermissions(filepath.Join(destination, file.Name()), 0, 0); err != nil {
|
return os.Lchown(fullpath, uid, gid)
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fixPermissions(destination, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fixPermissions(destination string, uid, gid int) error {
|
|
||||||
return filepath.Walk(destination, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err := os.Lchown(path, uid, gid); err != nil && !os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,13 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/daemon"
|
"github.com/docker/docker/daemon"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/graph"
|
"github.com/docker/docker/graph"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
@ -36,6 +36,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
|
||||||
noCache = job.GetenvBool("nocache")
|
noCache = job.GetenvBool("nocache")
|
||||||
rm = job.GetenvBool("rm")
|
rm = job.GetenvBool("rm")
|
||||||
forceRm = job.GetenvBool("forcerm")
|
forceRm = job.GetenvBool("forcerm")
|
||||||
|
pull = job.GetenvBool("pull")
|
||||||
authConfig = ®istry.AuthConfig{}
|
authConfig = ®istry.AuthConfig{}
|
||||||
configFile = ®istry.ConfigFile{}
|
configFile = ®istry.ConfigFile{}
|
||||||
tag string
|
tag string
|
||||||
|
@ -58,8 +59,8 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
|
||||||
|
|
||||||
if remoteURL == "" {
|
if remoteURL == "" {
|
||||||
context = ioutil.NopCloser(job.Stdin)
|
context = ioutil.NopCloser(job.Stdin)
|
||||||
} else if utils.IsGIT(remoteURL) {
|
} else if urlutil.IsGitURL(remoteURL) {
|
||||||
if !strings.HasPrefix(remoteURL, "git://") {
|
if !urlutil.IsGitTransport(remoteURL) {
|
||||||
remoteURL = "https://" + remoteURL
|
remoteURL = "https://" + remoteURL
|
||||||
}
|
}
|
||||||
root, err := ioutil.TempDir("", "docker-build-git")
|
root, err := ioutil.TempDir("", "docker-build-git")
|
||||||
|
@ -77,7 +78,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
|
||||||
return job.Error(err)
|
return job.Error(err)
|
||||||
}
|
}
|
||||||
context = c
|
context = c
|
||||||
} else if utils.IsURL(remoteURL) {
|
} else if urlutil.IsURL(remoteURL) {
|
||||||
f, err := utils.Download(remoteURL)
|
f, err := utils.Download(remoteURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return job.Error(err)
|
return job.Error(err)
|
||||||
|
@ -112,6 +113,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
|
||||||
UtilizeCache: !noCache,
|
UtilizeCache: !noCache,
|
||||||
Remove: rm,
|
Remove: rm,
|
||||||
ForceRemove: forceRm,
|
ForceRemove: forceRm,
|
||||||
|
Pull: pull,
|
||||||
OutOld: job.Stdout,
|
OutOld: job.Stdout,
|
||||||
StreamFormatter: sf,
|
StreamFormatter: sf,
|
||||||
AuthConfig: authConfig,
|
AuthConfig: authConfig,
|
||||||
|
@ -124,7 +126,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
if repoName != "" {
|
if repoName != "" {
|
||||||
b.Daemon.Repositories().Set(repoName, tag, id, false)
|
b.Daemon.Repositories().Set(repoName, tag, id, true)
|
||||||
}
|
}
|
||||||
return engine.StatusOK
|
return engine.StatusOK
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -41,17 +42,139 @@ func parseSubCommand(rest string) (*Node, map[string]bool, error) {
|
||||||
// parse environment like statements. Note that this does *not* handle
|
// parse environment like statements. Note that this does *not* handle
|
||||||
// variable interpolation, which will be handled in the evaluator.
|
// variable interpolation, which will be handled in the evaluator.
|
||||||
func parseEnv(rest string) (*Node, map[string]bool, error) {
|
func parseEnv(rest string) (*Node, map[string]bool, error) {
|
||||||
node := &Node{}
|
// This is kind of tricky because we need to support the old
|
||||||
rootnode := node
|
// variant: ENV name value
|
||||||
strs := TOKEN_WHITESPACE.Split(rest, 2)
|
// as well as the new one: ENV name=value ...
|
||||||
|
// The trigger to know which one is being used will be whether we hit
|
||||||
|
// a space or = first. space ==> old, "=" ==> new
|
||||||
|
|
||||||
if len(strs) < 2 {
|
const (
|
||||||
return nil, nil, fmt.Errorf("ENV must have two arguments")
|
inSpaces = iota // looking for start of a word
|
||||||
|
inWord
|
||||||
|
inQuote
|
||||||
|
)
|
||||||
|
|
||||||
|
words := []string{}
|
||||||
|
phase := inSpaces
|
||||||
|
word := ""
|
||||||
|
quote := '\000'
|
||||||
|
blankOK := false
|
||||||
|
var ch rune
|
||||||
|
|
||||||
|
for pos := 0; pos <= len(rest); pos++ {
|
||||||
|
if pos != len(rest) {
|
||||||
|
ch = rune(rest[pos])
|
||||||
|
}
|
||||||
|
|
||||||
|
if phase == inSpaces { // Looking for start of word
|
||||||
|
if pos == len(rest) { // end of input
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if unicode.IsSpace(ch) { // skip spaces
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
phase = inWord // found it, fall thru
|
||||||
|
}
|
||||||
|
if (phase == inWord || phase == inQuote) && (pos == len(rest)) {
|
||||||
|
if blankOK || len(word) > 0 {
|
||||||
|
words = append(words, word)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if phase == inWord {
|
||||||
|
if unicode.IsSpace(ch) {
|
||||||
|
phase = inSpaces
|
||||||
|
if blankOK || len(word) > 0 {
|
||||||
|
words = append(words, word)
|
||||||
|
|
||||||
|
// Look for = and if no there assume
|
||||||
|
// we're doing the old stuff and
|
||||||
|
// just read the rest of the line
|
||||||
|
if !strings.Contains(word, "=") {
|
||||||
|
word = strings.TrimSpace(rest[pos:])
|
||||||
|
words = append(words, word)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
word = ""
|
||||||
|
blankOK = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ch == '\'' || ch == '"' {
|
||||||
|
quote = ch
|
||||||
|
blankOK = true
|
||||||
|
phase = inQuote
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ch == '\\' {
|
||||||
|
if pos+1 == len(rest) {
|
||||||
|
continue // just skip \ at end
|
||||||
|
}
|
||||||
|
pos++
|
||||||
|
ch = rune(rest[pos])
|
||||||
|
}
|
||||||
|
word += string(ch)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if phase == inQuote {
|
||||||
|
if ch == quote {
|
||||||
|
phase = inWord
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ch == '\\' {
|
||||||
|
if pos+1 == len(rest) {
|
||||||
|
phase = inWord
|
||||||
|
continue // just skip \ at end
|
||||||
|
}
|
||||||
|
pos++
|
||||||
|
ch = rune(rest[pos])
|
||||||
|
}
|
||||||
|
word += string(ch)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Value = strs[0]
|
if len(words) == 0 {
|
||||||
node.Next = &Node{}
|
return nil, nil, fmt.Errorf("ENV must have some arguments")
|
||||||
node.Next.Value = strs[1]
|
}
|
||||||
|
|
||||||
|
// Old format (ENV name value)
|
||||||
|
var rootnode *Node
|
||||||
|
|
||||||
|
if !strings.Contains(words[0], "=") {
|
||||||
|
node := &Node{}
|
||||||
|
rootnode = node
|
||||||
|
strs := TOKEN_WHITESPACE.Split(rest, 2)
|
||||||
|
|
||||||
|
if len(strs) < 2 {
|
||||||
|
return nil, nil, fmt.Errorf("ENV must have two arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Value = strs[0]
|
||||||
|
node.Next = &Node{}
|
||||||
|
node.Next.Value = strs[1]
|
||||||
|
} else {
|
||||||
|
var prevNode *Node
|
||||||
|
for i, word := range words {
|
||||||
|
if !strings.Contains(word, "=") {
|
||||||
|
return nil, nil, fmt.Errorf("Syntax error - can't find = in %q. Must be of the form: name=value", word)
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(word, "=", 2)
|
||||||
|
|
||||||
|
name := &Node{}
|
||||||
|
value := &Node{}
|
||||||
|
|
||||||
|
name.Next = value
|
||||||
|
name.Value = parts[0]
|
||||||
|
value.Value = parts[1]
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
rootnode = name
|
||||||
|
} else {
|
||||||
|
prevNode.Next = name
|
||||||
|
}
|
||||||
|
prevNode = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return rootnode, nil, nil
|
return rootnode, nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,10 +103,6 @@ func Parse(rwc io.Reader) (*Node, error) {
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
scannedLine := strings.TrimLeftFunc(scanner.Text(), unicode.IsSpace)
|
scannedLine := strings.TrimLeftFunc(scanner.Text(), unicode.IsSpace)
|
||||||
if stripComments(scannedLine) == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
line, child, err := parseLine(scannedLine)
|
line, child, err := parseLine(scannedLine)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -129,6 +125,12 @@ func Parse(rwc io.Reader) (*Node, error) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if child == nil && line != "" {
|
||||||
|
line, child, err = parseLine(line)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if child != nil {
|
if child != nil {
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
FROM busybox
|
FROM busybox
|
||||||
|
|
||||||
ENV PATH=PATH
|
ENV PATH
|
|
@ -23,7 +23,6 @@
|
||||||
# the case. Therefore, you don't have to disable it anymore.
|
# the case. Therefore, you don't have to disable it anymore.
|
||||||
#
|
#
|
||||||
|
|
||||||
docker-version 0.6.1
|
|
||||||
FROM ubuntu:14.04
|
FROM ubuntu:14.04
|
||||||
MAINTAINER Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
MAINTAINER Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
(docker-version)
|
|
||||||
(from "ubuntu:14.04")
|
(from "ubuntu:14.04")
|
||||||
(maintainer "Tianon Gravi <admwiggin@gmail.com> (@tianon)")
|
(maintainer "Tianon Gravi <admwiggin@gmail.com> (@tianon)")
|
||||||
(run "apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq apt-utils aufs-tools automake btrfs-tools build-essential curl dpkg-sig git iptables libapparmor-dev libcap-dev libsqlite3-dev lxc=1.0* mercurial pandoc parallel reprepro ruby1.9.1 ruby1.9.1-dev s3cmd=1.1.0* --no-install-recommends")
|
(run "apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq apt-utils aufs-tools automake btrfs-tools build-essential curl dpkg-sig git iptables libapparmor-dev libcap-dev libsqlite3-dev lxc=1.0* mercurial pandoc parallel reprepro ruby1.9.1 ruby1.9.1-dev s3cmd=1.1.0* --no-install-recommends")
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
FROM ubuntu
|
||||||
|
ENV name value
|
||||||
|
ENV name=value
|
||||||
|
ENV name=value name2=value2
|
||||||
|
ENV name="value value1"
|
||||||
|
ENV name=value\ value2
|
||||||
|
ENV name="value'quote space'value2"
|
||||||
|
ENV name='value"double quote"value2'
|
||||||
|
ENV name=value\ value2 name2=value2\ value3
|
||||||
|
ENV name=value \
|
||||||
|
name1=value1 \
|
||||||
|
name2="value2a \
|
||||||
|
value2b" \
|
||||||
|
name3="value3a\n\"value3b\"" \
|
||||||
|
name4="value4a\\nvalue4b" \
|
|
@ -0,0 +1,10 @@
|
||||||
|
(from "ubuntu")
|
||||||
|
(env "name" "value")
|
||||||
|
(env "name" "value")
|
||||||
|
(env "name" "value" "name2" "value2")
|
||||||
|
(env "name" "value value1")
|
||||||
|
(env "name" "value value2")
|
||||||
|
(env "name" "value'quote space'value2")
|
||||||
|
(env "name" "value\"double quote\"value2")
|
||||||
|
(env "name" "value value2" "name2" "value2 value3")
|
||||||
|
(env "name" "value" "name1" "value1" "name2" "value2a value2b" "name3" "value3an\"value3b\"" "name4" "value4a\\nvalue4b")
|
|
@ -76,7 +76,7 @@ check_flags() {
|
||||||
for flag in "$@"; do
|
for flag in "$@"; do
|
||||||
echo "- $(check_flag "$flag")"
|
echo "- $(check_flag "$flag")"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ ! -e "$CONFIG" ]; then
|
if [ ! -e "$CONFIG" ]; then
|
||||||
wrap_warning "warning: $CONFIG does not exist, searching other paths for kernel config..."
|
wrap_warning "warning: $CONFIG does not exist, searching other paths for kernel config..."
|
||||||
|
@ -135,7 +135,7 @@ flags=(
|
||||||
DEVPTS_MULTIPLE_INSTANCES
|
DEVPTS_MULTIPLE_INSTANCES
|
||||||
CGROUPS CGROUP_CPUACCT CGROUP_DEVICE CGROUP_FREEZER CGROUP_SCHED
|
CGROUPS CGROUP_CPUACCT CGROUP_DEVICE CGROUP_FREEZER CGROUP_SCHED
|
||||||
MACVLAN VETH BRIDGE
|
MACVLAN VETH BRIDGE
|
||||||
NF_NAT_IPV4 IP_NF_TARGET_MASQUERADE
|
NF_NAT_IPV4 IP_NF_FILTER IP_NF_TARGET_MASQUERADE
|
||||||
NETFILTER_XT_MATCH_{ADDRTYPE,CONNTRACK}
|
NETFILTER_XT_MATCH_{ADDRTYPE,CONNTRACK}
|
||||||
NF_NAT NF_NAT_NEEDED
|
NF_NAT NF_NAT_NEEDED
|
||||||
)
|
)
|
||||||
|
@ -153,16 +153,20 @@ check_flags "${flags[@]}"
|
||||||
echo '- Storage Drivers:'
|
echo '- Storage Drivers:'
|
||||||
{
|
{
|
||||||
echo '- "'$(wrap_color 'aufs' blue)'":'
|
echo '- "'$(wrap_color 'aufs' blue)'":'
|
||||||
check_flags AUFS_FS EXT4_FS_POSIX_ACL EXT4_FS_SECURITY | sed 's/^/ /'
|
check_flags AUFS_FS | sed 's/^/ /'
|
||||||
if ! is_set AUFS_FS && grep -q aufs /proc/filesystems; then
|
if ! is_set AUFS_FS && grep -q aufs /proc/filesystems; then
|
||||||
echo " $(wrap_color '(note that some kernels include AUFS patches but not the AUFS_FS flag)' bold black)"
|
echo " $(wrap_color '(note that some kernels include AUFS patches but not the AUFS_FS flag)' bold black)"
|
||||||
fi
|
fi
|
||||||
|
check_flags EXT4_FS_POSIX_ACL EXT4_FS_SECURITY | sed 's/^/ /'
|
||||||
|
|
||||||
echo '- "'$(wrap_color 'btrfs' blue)'":'
|
echo '- "'$(wrap_color 'btrfs' blue)'":'
|
||||||
check_flags BTRFS_FS | sed 's/^/ /'
|
check_flags BTRFS_FS | sed 's/^/ /'
|
||||||
|
|
||||||
echo '- "'$(wrap_color 'devicemapper' blue)'":'
|
echo '- "'$(wrap_color 'devicemapper' blue)'":'
|
||||||
check_flags BLK_DEV_DM DM_THIN_PROVISIONING EXT4_FS EXT4_FS_POSIX_ACL EXT4_FS_SECURITY | sed 's/^/ /'
|
check_flags BLK_DEV_DM DM_THIN_PROVISIONING EXT4_FS EXT4_FS_POSIX_ACL EXT4_FS_SECURITY | sed 's/^/ /'
|
||||||
|
|
||||||
|
echo '- "'$(wrap_color 'overlay' blue)'":'
|
||||||
|
check_flags OVERLAY_FS | sed 's/^/ /'
|
||||||
} | sed 's/^/ /'
|
} | sed 's/^/ /'
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||||
|
Jessie Frazelle <jess@docker.com> (@jfrazelle)
|
|
@ -1,8 +1,8 @@
|
||||||
#!bash
|
#!/bin/bash
|
||||||
#
|
#
|
||||||
# bash completion file for core docker commands
|
# bash completion file for core docker commands
|
||||||
#
|
#
|
||||||
# This script provides supports completion of:
|
# This script provides completion of:
|
||||||
# - commands and their options
|
# - commands and their options
|
||||||
# - container ids and names
|
# - container ids and names
|
||||||
# - image repos and tags
|
# - image repos and tags
|
||||||
|
@ -11,9 +11,9 @@
|
||||||
# To enable the completions either:
|
# To enable the completions either:
|
||||||
# - place this file in /etc/bash_completion.d
|
# - place this file in /etc/bash_completion.d
|
||||||
# or
|
# or
|
||||||
# - copy this file and add the line below to your .bashrc after
|
# - copy this file to e.g. ~/.docker-completion.sh and add the line
|
||||||
# bash completion features are loaded
|
# below to your .bashrc after bash completion features are loaded
|
||||||
# . docker.bash
|
# . ~/.docker-completion.sh
|
||||||
#
|
#
|
||||||
# Note:
|
# Note:
|
||||||
# Currently, the completions will not work if the docker daemon is not
|
# Currently, the completions will not work if the docker daemon is not
|
||||||
|
@ -99,13 +99,60 @@ __docker_pos_first_nonflag() {
|
||||||
echo $counter
|
echo $counter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__docker_resolve_hostname() {
|
||||||
|
command -v host >/dev/null 2>&1 || return
|
||||||
|
COMPREPLY=( $(host 2>/dev/null "${cur%:}" | awk '/has address/ {print $4}') )
|
||||||
|
}
|
||||||
|
|
||||||
|
__docker_capabilities() {
|
||||||
|
# The list of capabilities is defined in types.go, ALL was added manually.
|
||||||
|
COMPREPLY=( $( compgen -W "
|
||||||
|
ALL
|
||||||
|
AUDIT_CONTROL
|
||||||
|
AUDIT_WRITE
|
||||||
|
BLOCK_SUSPEND
|
||||||
|
CHOWN
|
||||||
|
DAC_OVERRIDE
|
||||||
|
DAC_READ_SEARCH
|
||||||
|
FOWNER
|
||||||
|
FSETID
|
||||||
|
IPC_LOCK
|
||||||
|
IPC_OWNER
|
||||||
|
KILL
|
||||||
|
LEASE
|
||||||
|
LINUX_IMMUTABLE
|
||||||
|
MAC_ADMIN
|
||||||
|
MAC_OVERRIDE
|
||||||
|
MKNOD
|
||||||
|
NET_ADMIN
|
||||||
|
NET_BIND_SERVICE
|
||||||
|
NET_BROADCAST
|
||||||
|
NET_RAW
|
||||||
|
SETFCAP
|
||||||
|
SETGID
|
||||||
|
SETPCAP
|
||||||
|
SETUID
|
||||||
|
SYS_ADMIN
|
||||||
|
SYS_BOOT
|
||||||
|
SYS_CHROOT
|
||||||
|
SYSLOG
|
||||||
|
SYS_MODULE
|
||||||
|
SYS_NICE
|
||||||
|
SYS_PACCT
|
||||||
|
SYS_PTRACE
|
||||||
|
SYS_RAWIO
|
||||||
|
SYS_RESOURCE
|
||||||
|
SYS_TIME
|
||||||
|
SYS_TTY_CONFIG
|
||||||
|
WAKE_ALARM
|
||||||
|
" -- "$cur" ) )
|
||||||
|
}
|
||||||
|
|
||||||
_docker_docker() {
|
_docker_docker() {
|
||||||
case "$prev" in
|
case "$prev" in
|
||||||
-H)
|
-H)
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
|
@ -138,8 +185,6 @@ _docker_build() {
|
||||||
__docker_image_repos_and_tags
|
__docker_image_repos_and_tags
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
|
@ -160,8 +205,6 @@ _docker_commit() {
|
||||||
-m|--message|-a|--author|--run)
|
-m|--message|-a|--author|--run)
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
|
@ -222,7 +265,7 @@ _docker_create() {
|
||||||
__docker_containers_all
|
__docker_containers_all
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
-v|--volume)
|
-v|--volume|--device)
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
*:*)
|
*:*)
|
||||||
# TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine)
|
# TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine)
|
||||||
|
@ -255,19 +298,72 @@ _docker_create() {
|
||||||
esac
|
esac
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|-c|--cpu-shares|-n|--name|-p|--publish|--expose|--dns|--lxc-conf)
|
--add-host)
|
||||||
|
case "$cur" in
|
||||||
|
*:)
|
||||||
|
__docker_resolve_hostname
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
--cap-add|--cap-drop)
|
||||||
|
__docker_capabilities
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
--net)
|
||||||
|
case "$cur" in
|
||||||
|
container:*)
|
||||||
|
local cur=${cur#*:}
|
||||||
|
__docker_containers_all
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
COMPREPLY=( $( compgen -W "bridge none container: host" -- "$cur") )
|
||||||
|
if [ "${COMPREPLY[*]}" = "container:" ] ; then
|
||||||
|
compopt -o nospace
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
--restart)
|
||||||
|
case "$cur" in
|
||||||
|
on-failure:*)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
COMPREPLY=( $( compgen -W "no on-failure on-failure: always" -- "$cur") )
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
--security-opt)
|
||||||
|
case "$cur" in
|
||||||
|
label:*:*)
|
||||||
|
;;
|
||||||
|
label:*)
|
||||||
|
local cur=${cur##*:}
|
||||||
|
COMPREPLY=( $( compgen -W "user: role: type: level: disable" -- "$cur") )
|
||||||
|
if [ "${COMPREPLY[*]}" != "disable" ] ; then
|
||||||
|
compopt -o nospace
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
COMPREPLY=( $( compgen -W "label apparmor" -S ":" -- "$cur") )
|
||||||
|
compopt -o nospace
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|--cpuset|-c|--cpu-shares|-n|--name|-p|--publish|--expose|--dns|--lxc-conf|--dns-search)
|
||||||
|
return
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
COMPREPLY=( $( compgen -W "-n --networking --privileged -P --publish-all -i --interactive -t --tty --cidfile --entrypoint -h --hostname -m --memory -u --user -w --workdir -c --cpu-shares --name -a --attach -v --volume --link -e --env -p --publish --expose --dns --volumes-from --lxc-conf" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "--privileged -P --publish-all -i --interactive -t --tty --cidfile --entrypoint -h --hostname -m --memory -u --user -w --workdir --cpuset -c --cpu-shares --name -a --attach -v --volume --link -e --env --env-file -p --publish --expose --dns --volumes-from --lxc-conf --security-opt --add-host --cap-add --cap-drop --device --dns-search --net --restart" -- "$cur" ) )
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
local counter=$(__docker_pos_first_nonflag '--cidfile|--volumes-from|-v|--volume|-e|--env|--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf')
|
local counter=$(__docker_pos_first_nonflag '--cidfile|--volumes-from|-v|--volume|-e|--env|--env-file|--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|--cpuset|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf|--security-opt|--add-host|--cap-add|--cap-drop|--device|--dns-search|--net|--restart')
|
||||||
|
|
||||||
if [ $cword -eq $counter ]; then
|
if [ $cword -eq $counter ]; then
|
||||||
__docker_image_repos_and_tags_and_ids
|
__docker_image_repos_and_tags_and_ids
|
||||||
|
@ -288,16 +384,12 @@ _docker_events() {
|
||||||
--since)
|
--since)
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
COMPREPLY=( $( compgen -W "--since" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "--since" -- "$cur" ) )
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,8 +468,6 @@ _docker_inspect() {
|
||||||
-f|--format)
|
-f|--format)
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
|
@ -403,16 +493,12 @@ _docker_login() {
|
||||||
-u|--username|-p|--password|-e|--email)
|
-u|--username|-p|--password|-e|--email)
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
COMPREPLY=( $( compgen -W "-u --username -p --password -e --email" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "-u --username -p --password -e --email" -- "$cur" ) )
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,16 +538,12 @@ _docker_ps() {
|
||||||
-n)
|
-n)
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
COMPREPLY=( $( compgen -W "-q --quiet -s --size -a --all --no-trunc -l --latest --since --before -n" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "-q --quiet -s --size -a --all --no-trunc -l --latest --since --before -n" -- "$cur" ) )
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,8 +552,6 @@ _docker_pull() {
|
||||||
-t|--tag)
|
-t|--tag)
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
|
@ -499,8 +579,6 @@ _docker_restart() {
|
||||||
-t|--time)
|
-t|--time)
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
|
@ -520,7 +598,6 @@ _docker_rm() {
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
local force=
|
|
||||||
for arg in "${COMP_WORDS[@]}"; do
|
for arg in "${COMP_WORDS[@]}"; do
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
-f|--force)
|
-f|--force)
|
||||||
|
@ -553,7 +630,7 @@ _docker_run() {
|
||||||
__docker_containers_all
|
__docker_containers_all
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
-v|--volume)
|
-v|--volume|--device)
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
*:*)
|
*:*)
|
||||||
# TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine)
|
# TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine)
|
||||||
|
@ -586,20 +663,72 @@ _docker_run() {
|
||||||
esac
|
esac
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|--cpuset|-c|--cpu-shares|-n|--name|-p|--publish|--expose|--dns|--lxc-conf)
|
--add-host)
|
||||||
|
case "$cur" in
|
||||||
|
*:)
|
||||||
|
__docker_resolve_hostname
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
--cap-add|--cap-drop)
|
||||||
|
__docker_capabilities
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
--net)
|
||||||
|
case "$cur" in
|
||||||
|
container:*)
|
||||||
|
local cur=${cur#*:}
|
||||||
|
__docker_containers_all
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
COMPREPLY=( $( compgen -W "bridge none container: host" -- "$cur") )
|
||||||
|
if [ "${COMPREPLY[*]}" = "container:" ] ; then
|
||||||
|
compopt -o nospace
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
--restart)
|
||||||
|
case "$cur" in
|
||||||
|
on-failure:*)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
COMPREPLY=( $( compgen -W "no on-failure on-failure: always" -- "$cur") )
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
--security-opt)
|
||||||
|
case "$cur" in
|
||||||
|
label:*:*)
|
||||||
|
;;
|
||||||
|
label:*)
|
||||||
|
local cur=${cur##*:}
|
||||||
|
COMPREPLY=( $( compgen -W "user: role: type: level: disable" -- "$cur") )
|
||||||
|
if [ "${COMPREPLY[*]}" != "disable" ] ; then
|
||||||
|
compopt -o nospace
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
COMPREPLY=( $( compgen -W "label apparmor" -S ":" -- "$cur") )
|
||||||
|
compopt -o nospace
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|--cpuset|-c|--cpu-shares|-n|--name|-p|--publish|--expose|--dns|--lxc-conf|--dns-search)
|
||||||
|
return
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
COMPREPLY=( $( compgen -W "--rm -d --detach -n --networking --privileged -P --publish-all -i --interactive -t --tty --cidfile --entrypoint -h --hostname -m --memory -u --user -w --workdir --cpuset -c --cpu-shares --sig-proxy --name -a --attach -v --volume --link -e --env -p --publish --expose --dns --volumes-from --lxc-conf --security-opt" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "--rm -d --detach --privileged -P --publish-all -i --interactive -t --tty --cidfile --entrypoint -h --hostname -m --memory -u --user -w --workdir --cpuset -c --cpu-shares --sig-proxy --name -a --attach -v --volume --link -e --env --env-file -p --publish --expose --dns --volumes-from --lxc-conf --security-opt --add-host --cap-add --cap-drop --device --dns-search --net --restart" -- "$cur" ) )
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
local counter=$(__docker_pos_first_nonflag '--cidfile|--volumes-from|-v|--volume|-e|--env|--env-file|--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|--cpuset|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf|--security-opt|--add-host|--cap-add|--cap-drop|--device|--dns-search|--net|--restart')
|
||||||
local counter=$(__docker_pos_first_nonflag '--cidfile|--volumes-from|-v|--volume|-e|--env|--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|--cpuset|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf|--security-opt')
|
|
||||||
|
|
||||||
if [ $cword -eq $counter ]; then
|
if [ $cword -eq $counter ]; then
|
||||||
__docker_image_repos_and_tags_and_ids
|
__docker_image_repos_and_tags_and_ids
|
||||||
|
@ -620,16 +749,12 @@ _docker_search() {
|
||||||
-s|--stars)
|
-s|--stars)
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
COMPREPLY=( $( compgen -W "--no-trunc --automated -s --stars" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "--no-trunc --automated -s --stars" -- "$cur" ) )
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,8 +774,6 @@ _docker_stop() {
|
||||||
-t|--time)
|
-t|--time)
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
|
@ -752,7 +875,7 @@ _docker() {
|
||||||
local cur prev words cword
|
local cur prev words cword
|
||||||
_get_comp_words_by_ref -n : cur prev words cword
|
_get_comp_words_by_ref -n : cur prev words cword
|
||||||
|
|
||||||
local command='docker'
|
local command='docker' cpos=0
|
||||||
local counter=1
|
local counter=1
|
||||||
while [ $counter -lt $cword ]; do
|
while [ $counter -lt $cword ]; do
|
||||||
case "${words[$counter]}" in
|
case "${words[$counter]}" in
|
||||||
|
|
|
@ -53,7 +53,7 @@ complete -c docker -f -n '__fish_docker_no_subcommand' -s d -l daemon -d 'Enable
|
||||||
complete -c docker -f -n '__fish_docker_no_subcommand' -l dns -d 'Force docker to use specific DNS servers'
|
complete -c docker -f -n '__fish_docker_no_subcommand' -l dns -d 'Force docker to use specific DNS servers'
|
||||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s e -l exec-driver -d 'Force the docker runtime to use a specific exec driver'
|
complete -c docker -f -n '__fish_docker_no_subcommand' -s e -l exec-driver -d 'Force the docker runtime to use a specific exec driver'
|
||||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s g -l graph -d 'Path to use as the root of the docker runtime'
|
complete -c docker -f -n '__fish_docker_no_subcommand' -s g -l graph -d 'Path to use as the root of the docker runtime'
|
||||||
complete -c docker -f -n '__fish_docker_no_subcommand' -l icc -d 'Enable inter-container communication'
|
complete -c docker -f -n '__fish_docker_no_subcommand' -l icc -d 'Allow unrestricted inter-container and Docker daemon host communication'
|
||||||
complete -c docker -f -n '__fish_docker_no_subcommand' -l ip -d 'Default IP address to use when binding container ports'
|
complete -c docker -f -n '__fish_docker_no_subcommand' -l ip -d 'Default IP address to use when binding container ports'
|
||||||
complete -c docker -f -n '__fish_docker_no_subcommand' -l ip-forward -d 'Disable enabling of net.ipv4.ip_forward'
|
complete -c docker -f -n '__fish_docker_no_subcommand' -l ip-forward -d 'Disable enabling of net.ipv4.ip_forward'
|
||||||
complete -c docker -f -n '__fish_docker_no_subcommand' -l iptables -d "Disable docker's addition of iptables rules"
|
complete -c docker -f -n '__fish_docker_no_subcommand' -l iptables -d "Disable docker's addition of iptables rules"
|
||||||
|
@ -67,7 +67,7 @@ complete -c docker -f -n '__fish_docker_no_subcommand' -s v -l version -d 'Print
|
||||||
# attach
|
# attach
|
||||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a attach -d 'Attach to a running container'
|
complete -c docker -f -n '__fish_docker_no_subcommand' -a attach -d 'Attach to a running container'
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from attach' -l no-stdin -d 'Do not attach stdin'
|
complete -c docker -A -f -n '__fish_seen_subcommand_from attach' -l no-stdin -d 'Do not attach stdin'
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from attach' -l sig-proxy -d 'Proxify all received signal to the process (even in non-tty mode)'
|
complete -c docker -A -f -n '__fish_seen_subcommand_from attach' -l sig-proxy -d 'Proxify all received signal to the process (non-TTY mode only)'
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from attach' -a '(__fish_print_docker_containers running)' -d "Container"
|
complete -c docker -A -f -n '__fish_seen_subcommand_from attach' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||||
|
|
||||||
# build
|
# build
|
||||||
|
@ -185,7 +185,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s l -l latest -d '
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s n -d 'Show n last created containers, include non-running ones.'
|
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s n -d 'Show n last created containers, include non-running ones.'
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -l no-trunc -d "Don't truncate output"
|
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -l no-trunc -d "Don't truncate output"
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s q -l quiet -d 'Only display numeric IDs'
|
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s q -l quiet -d 'Only display numeric IDs'
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s s -l size -d 'Display sizes'
|
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s s -l size -d 'Display total file sizes'
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -l since -d 'Show only containers created since Id or Name, include non-running ones.'
|
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -l since -d 'Show only containers created since Id or Name, include non-running ones.'
|
||||||
|
|
||||||
# pull
|
# pull
|
||||||
|
@ -237,7 +237,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l name -d 'Assign
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s p -l publish -d "Publish a container's port to the host (format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort) (use 'docker port' to see the actual mapping)"
|
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s p -l publish -d "Publish a container's port to the host (format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort) (use 'docker port' to see the actual mapping)"
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l privileged -d 'Give extended privileges to this container'
|
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l privileged -d 'Give extended privileges to this container'
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l rm -d 'Automatically remove the container when it exits (incompatible with -d)'
|
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l rm -d 'Automatically remove the container when it exits (incompatible with -d)'
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l sig-proxy -d 'Proxify all received signal to the process (even in non-tty mode)'
|
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l sig-proxy -d 'Proxify all received signal to the process (non-TTY mode only)'
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s t -l tty -d 'Allocate a pseudo-tty'
|
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s t -l tty -d 'Allocate a pseudo-tty'
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s u -l user -d 'Username or UID'
|
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s u -l user -d 'Username or UID'
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s v -l volume -d 'Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)'
|
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s v -l volume -d 'Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)'
|
||||||
|
|
|
@ -177,7 +177,9 @@ __docker_commands () {
|
||||||
if ( [[ ${+_docker_subcommands} -eq 0 ]] || _cache_invalid docker_subcommands) \
|
if ( [[ ${+_docker_subcommands} -eq 0 ]] || _cache_invalid docker_subcommands) \
|
||||||
&& ! _retrieve_cache docker_subcommands;
|
&& ! _retrieve_cache docker_subcommands;
|
||||||
then
|
then
|
||||||
_docker_subcommands=(${${${${(f)"$(_call_program commands docker 2>&1)"}[5,-1]}## #}/ ##/:})
|
local -a lines
|
||||||
|
lines=(${(f)"$(_call_program commands docker 2>&1)"})
|
||||||
|
_docker_subcommands=(${${${lines[$((${lines[(i)Commands:]} + 1)),${lines[(I) *]}]}## #}/ ##/:})
|
||||||
_docker_subcommands=($_docker_subcommands 'help:Show help for a command')
|
_docker_subcommands=($_docker_subcommands 'help:Show help for a command')
|
||||||
_store_cache docker_subcommands _docker_subcommands
|
_store_cache docker_subcommands _docker_subcommands
|
||||||
fi
|
fi
|
||||||
|
@ -190,22 +192,23 @@ __docker_subcommand () {
|
||||||
(attach)
|
(attach)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--no-stdin[Do not attach stdin]' \
|
'--no-stdin[Do not attach stdin]' \
|
||||||
'--sig-proxy[Proxify all received signal]' \
|
'--sig-proxy[Proxy all received signals to the process (non-TTY mode only)]' \
|
||||||
':containers:__docker_runningcontainers'
|
':containers:__docker_runningcontainers'
|
||||||
;;
|
;;
|
||||||
(build)
|
(build)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--force-rm[Always remove intermediate containers, even after unsuccessful builds]' \
|
'--force-rm[Always remove intermediate containers]' \
|
||||||
'--no-cache[Do not use cache when building the image]' \
|
'--no-cache[Do not use cache when building the image]' \
|
||||||
'-q[Suppress verbose build output]' \
|
{-q,--quiet}'[Suppress verbose build output]' \
|
||||||
'--rm[Remove intermediate containers after a successful build]' \
|
'--rm[Remove intermediate containers after a successful build]' \
|
||||||
'-t:repository:__docker_repositories_with_tags' \
|
{-t,--tag=-}'[Repository, name and tag to be applied]:repository:__docker_repositories_with_tags' \
|
||||||
':path or URL:_directories'
|
':path or URL:_directories'
|
||||||
;;
|
;;
|
||||||
(commit)
|
(commit)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--author=-[Author]:author: ' \
|
{-a,--author=-}'[Author]:author: ' \
|
||||||
'-m[Commit message]:message: ' \
|
{-m,--message=-}'[Commit message]:message: ' \
|
||||||
|
{-p,--pause}'[Pause container during commit]' \
|
||||||
'--run=-[Configuration automatically applied when the image is run]:configuration: ' \
|
'--run=-[Configuration automatically applied when the image is run]:configuration: ' \
|
||||||
':container:__docker_containers' \
|
':container:__docker_containers' \
|
||||||
':repository:__docker_repositories_with_tags'
|
':repository:__docker_repositories_with_tags'
|
||||||
|
@ -224,60 +227,40 @@ __docker_subcommand () {
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
(create)
|
|
||||||
_arguments \
|
|
||||||
'-P[Publish all exposed ports to the host]' \
|
|
||||||
'-a[Attach to stdin, stdout or stderr]' \
|
|
||||||
'-c=-[CPU shares (relative weight)]:CPU shares:(0 10 100 200 500 800 1000)' \
|
|
||||||
'--cidfile=-[Write the container ID to the file]:CID file:_files' \
|
|
||||||
'*--dns=-[Set custom dns servers]:dns server: ' \
|
|
||||||
'*-e=-[Set environment variables]:environment variable: ' \
|
|
||||||
'--entrypoint=-[Overwrite the default entrypoint of the image]:entry point: ' \
|
|
||||||
'*--expose=-[Expose a port from the container without publishing it]: ' \
|
|
||||||
'-h=-[Container host name]:hostname:_hosts' \
|
|
||||||
'-i[Keep stdin open even if not attached]' \
|
|
||||||
'--link=-[Add link to another container]:link:->link' \
|
|
||||||
'--lxc-conf=-[Add custom lxc options]:lxc options: ' \
|
|
||||||
'-m=-[Memory limit (in bytes)]:limit: ' \
|
|
||||||
'--name=-[Container name]:name: ' \
|
|
||||||
'*-p=-[Expose a container'"'"'s port to the host]:port:_ports' \
|
|
||||||
'--privileged[Give extended privileges to this container]' \
|
|
||||||
'-t[Allocate a pseudo-tty]' \
|
|
||||||
'-u=-[Username or UID]:user:_users' \
|
|
||||||
'*-v=-[Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)]:volume: '\
|
|
||||||
'--volumes-from=-[Mount volumes from the specified container]:volume: ' \
|
|
||||||
'-w=-[Working directory inside the container]:directory:_directories' \
|
|
||||||
'(-):images:__docker_images' \
|
|
||||||
'(-):command: _command_names -e' \
|
|
||||||
'*::arguments: _normal'
|
|
||||||
(diff|export)
|
(diff|export)
|
||||||
_arguments '*:containers:__docker_containers'
|
_arguments '*:containers:__docker_containers'
|
||||||
;;
|
;;
|
||||||
|
(events)
|
||||||
|
_arguments \
|
||||||
|
'--since=-[Events created since this timestamp]:timestamp: ' \
|
||||||
|
'--until=-[Events created until this timestamp]:timestamp: '
|
||||||
|
;;
|
||||||
(exec)
|
(exec)
|
||||||
_arguments \
|
_arguments \
|
||||||
'-d[Detached mode: leave the container running in the background]' \
|
{-d,--detach}'[Detached mode: leave the container running in the background]' \
|
||||||
'-i[Keep stdin open even if not attached]' \
|
{-i,--interactive}'[Keep stdin open even if not attached]' \
|
||||||
'-t[Allocate a pseudo-tty]' \
|
{-t,--tty}'[Allocate a pseudo-tty]' \
|
||||||
':containers:__docker_runningcontainers'
|
':containers:__docker_runningcontainers'
|
||||||
;;
|
;;
|
||||||
(history)
|
(history)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--no-trunc[Do not truncate output]' \
|
'--no-trunc[Do not truncate output]' \
|
||||||
'-q[Only show numeric IDs]' \
|
{-q,--quiet}'[Only show numeric IDs]' \
|
||||||
'*:images:__docker_images'
|
'*:images:__docker_images'
|
||||||
;;
|
;;
|
||||||
(images)
|
(images)
|
||||||
_arguments \
|
_arguments \
|
||||||
'-a[Show all images]' \
|
{-a,--all}'[Show all images]' \
|
||||||
|
'*'{-f,--filter=-}'[Filter values]:filter: ' \
|
||||||
'--no-trunc[Do not truncate output]' \
|
'--no-trunc[Do not truncate output]' \
|
||||||
'-q[Only show numeric IDs]' \
|
{-q,--quiet}'[Only show numeric IDs]' \
|
||||||
'--tree[Output graph in tree format]' \
|
'--tree[Output graph in tree format]' \
|
||||||
'--viz[Output graph in graphviz format]' \
|
'--viz[Output graph in graphviz format]' \
|
||||||
':repository:__docker_repositories'
|
':repository:__docker_repositories'
|
||||||
;;
|
;;
|
||||||
(inspect)
|
(inspect)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--format=-[Format the output using the given go template]:template: ' \
|
{-f,--format=-}'[Format the output using the given go template]:template: ' \
|
||||||
'*:containers:__docker_containers'
|
'*:containers:__docker_containers'
|
||||||
;;
|
;;
|
||||||
(import)
|
(import)
|
||||||
|
@ -298,20 +281,29 @@ __docker_subcommand () {
|
||||||
'3:file:_files'
|
'3:file:_files'
|
||||||
;;
|
;;
|
||||||
(kill)
|
(kill)
|
||||||
_arguments '*:containers:__docker_runningcontainers'
|
_arguments \
|
||||||
|
{-s,--signal=-}'[Signal to send]:signal:_signals' \
|
||||||
|
'*:containers:__docker_runningcontainers'
|
||||||
;;
|
;;
|
||||||
(load)
|
(load)
|
||||||
|
_arguments \
|
||||||
|
{-i,--input=-}'[Read from tar archive file]:tar:_files'
|
||||||
;;
|
;;
|
||||||
(login)
|
(login)
|
||||||
_arguments \
|
_arguments \
|
||||||
'-e[Email]:email: ' \
|
{-e,--email=-}'[Email]:email: ' \
|
||||||
'-p[Password]:password: ' \
|
{-p,--password=-}'[Password]:password: ' \
|
||||||
'-u[Username]:username: ' \
|
{-u,--user=-}'[Username]:username: ' \
|
||||||
|
':server: '
|
||||||
|
;;
|
||||||
|
(logout)
|
||||||
|
_arguments \
|
||||||
':server: '
|
':server: '
|
||||||
;;
|
;;
|
||||||
(logs)
|
(logs)
|
||||||
_arguments \
|
_arguments \
|
||||||
'-f[Follow log output]' \
|
{-f,--follow}'[Follow log output]' \
|
||||||
|
{-t,--timestamps}'[Show timestamps]' \
|
||||||
'*:containers:__docker_containers'
|
'*:containers:__docker_containers'
|
||||||
;;
|
;;
|
||||||
(port)
|
(port)
|
||||||
|
@ -319,24 +311,32 @@ __docker_subcommand () {
|
||||||
'1:containers:__docker_runningcontainers' \
|
'1:containers:__docker_runningcontainers' \
|
||||||
'2:port:_ports'
|
'2:port:_ports'
|
||||||
;;
|
;;
|
||||||
|
(pause|unpause)
|
||||||
|
_arguments \
|
||||||
|
'1:containers:__docker_runningcontainers'
|
||||||
|
;;
|
||||||
(start)
|
(start)
|
||||||
_arguments \
|
_arguments \
|
||||||
'-a[Attach container'"'"'s stdout/stderr and forward all signals]' \
|
{-a,--attach}'[Attach container'"'"'s stdout/stderr and forward all signals]' \
|
||||||
'-i[Attach container'"'"'s stding]' \
|
{-i,--interactive}'[Attach container'"'"'s stding]' \
|
||||||
'*:containers:__docker_stoppedcontainers'
|
'*:containers:__docker_stoppedcontainers'
|
||||||
;;
|
;;
|
||||||
(rm)
|
(rm)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--link[Remove the specified link and not the underlying container]' \
|
{-f,--force}'[Force removal]' \
|
||||||
'-v[Remove the volumes associated to the container]' \
|
{-l,--link}'[Remove the specified link and not the underlying container]' \
|
||||||
|
{-v,--volumes}'[Remove the volumes associated to the container]' \
|
||||||
'*:containers:__docker_stoppedcontainers'
|
'*:containers:__docker_stoppedcontainers'
|
||||||
;;
|
;;
|
||||||
(rmi)
|
(rmi)
|
||||||
_arguments \
|
_arguments \
|
||||||
|
{-f,--force}'[Force removal]' \
|
||||||
|
'--no-prune[Do not delete untagged parents]' \
|
||||||
'*:images:__docker_images'
|
'*:images:__docker_images'
|
||||||
;;
|
;;
|
||||||
(restart|stop)
|
(restart|stop)
|
||||||
_arguments '-t[Number of seconds to try to stop for before killing the container]:seconds to before killing:(1 5 10 30 60)' \
|
_arguments \
|
||||||
|
{-t,--time=-}'[Number of seconds to try to stop for before killing the container]:seconds to before killing:(1 5 10 30 60)' \
|
||||||
'*:containers:__docker_runningcontainers'
|
'*:containers:__docker_runningcontainers'
|
||||||
;;
|
;;
|
||||||
(top)
|
(top)
|
||||||
|
@ -352,47 +352,58 @@ __docker_subcommand () {
|
||||||
;;
|
;;
|
||||||
(ps)
|
(ps)
|
||||||
_arguments \
|
_arguments \
|
||||||
'-a[Show all containers]' \
|
{-a,--all}'[Show all containers]' \
|
||||||
'--before=-[Show only container created before...]:containers:__docker_containers' \
|
'--before=-[Show only container created before...]:containers:__docker_containers' \
|
||||||
'-l[Show only the latest created container]' \
|
'*'{-f,--filter=-}'[Filter values]:filter: ' \
|
||||||
|
{-l,--latest}'[Show only the latest created container]' \
|
||||||
'-n[Show n last created containers, include non-running one]:n:(1 5 10 25 50)' \
|
'-n[Show n last created containers, include non-running one]:n:(1 5 10 25 50)' \
|
||||||
'--no-trunc[Do not truncate output]' \
|
'--no-trunc[Do not truncate output]' \
|
||||||
'-q[Only show numeric IDs]' \
|
{-q,--quiet}'[Only show numeric IDs]' \
|
||||||
'-s[Display sizes]' \
|
{-s,--size}'[Display total file sizes]' \
|
||||||
'--since=-[Show only containers created since...]:containers:__docker_containers'
|
'--since=-[Show only containers created since...]:containers:__docker_containers'
|
||||||
;;
|
;;
|
||||||
(tag)
|
(tag)
|
||||||
_arguments \
|
_arguments \
|
||||||
'-f[force]'\
|
{-f,--force}'[force]'\
|
||||||
':image:__docker_images'\
|
':image:__docker_images'\
|
||||||
':repository:__docker_repositories_with_tags'
|
':repository:__docker_repositories_with_tags'
|
||||||
;;
|
;;
|
||||||
(run)
|
(create|run)
|
||||||
_arguments \
|
_arguments \
|
||||||
'-P[Publish all exposed ports to the host]' \
|
{-a,--attach}'[Attach to stdin, stdout or stderr]' \
|
||||||
'-a[Attach to stdin, stdout or stderr]' \
|
'*--add-host=-[Add a custom host-to-IP mapping]:host\:ip mapping: ' \
|
||||||
'-c[CPU shares (relative weight)]:CPU shares:(0 10 100 200 500 800 1000)' \
|
{-c,--cpu-shares=-}'[CPU shares (relative weight)]:CPU shares:(0 10 100 200 500 800 1000)' \
|
||||||
|
'*--cap-add=-[Add Linux capabilities]:capability: ' \
|
||||||
|
'*--cap-drop=-[Drop Linux capabilities]:capability: ' \
|
||||||
'--cidfile=-[Write the container ID to the file]:CID file:_files' \
|
'--cidfile=-[Write the container ID to the file]:CID file:_files' \
|
||||||
'-d[Detached mode: leave the container running in the background]' \
|
'--cpuset=-[CPUs in which to allow execution]:CPU set: ' \
|
||||||
|
{-d,--detach}'[Detached mode: leave the container running in the background]' \
|
||||||
|
'*--device=-[Add a host device to the container]:device:_files' \
|
||||||
'*--dns=-[Set custom dns servers]:dns server: ' \
|
'*--dns=-[Set custom dns servers]:dns server: ' \
|
||||||
'*-e[Set environment variables]:environment variable: ' \
|
'*--dns-search=-[Set custom DNS search domains]:dns domains: ' \
|
||||||
|
'*'{-e,--environment=-}'[Set environment variables]:environment variable: ' \
|
||||||
'--entrypoint=-[Overwrite the default entrypoint of the image]:entry point: ' \
|
'--entrypoint=-[Overwrite the default entrypoint of the image]:entry point: ' \
|
||||||
|
'*--env-file=-[Read environment variables from a file]:environment file:_files' \
|
||||||
'*--expose=-[Expose a port from the container without publishing it]: ' \
|
'*--expose=-[Expose a port from the container without publishing it]: ' \
|
||||||
'-h[Container host name]:hostname:_hosts' \
|
{-h,--hostname=-}'[Container host name]:hostname:_hosts' \
|
||||||
'-i[Keep stdin open even if not attached]' \
|
{-i,--interactive}'[Keep stdin open even if not attached]' \
|
||||||
'--link=-[Add link to another container]:link:->link' \
|
'*--link=-[Add link to another container]:link:->link' \
|
||||||
'--lxc-conf=-[Add custom lxc options]:lxc options: ' \
|
'*--lxc-conf=-[Add custom lxc options]:lxc options: ' \
|
||||||
'-m[Memory limit (in bytes)]:limit: ' \
|
'-m[Memory limit (in bytes)]:limit: ' \
|
||||||
'--name=-[Container name]:name: ' \
|
'--name=-[Container name]:name: ' \
|
||||||
'*-p[Expose a container'"'"'s port to the host]:port:_ports' \
|
'--net=-[Network mode]:network mode:(bridge none container: host)' \
|
||||||
|
{-P,--publish-all}'[Publish all exposed ports]' \
|
||||||
|
'*'{-p,--publish=-}'[Expose a container'"'"'s port to the host]:port:_ports' \
|
||||||
'--privileged[Give extended privileges to this container]' \
|
'--privileged[Give extended privileges to this container]' \
|
||||||
|
'--restart=-[Restart policy]:restart policy:(no on-failure always)' \
|
||||||
'--rm[Remove intermediate containers when it exits]' \
|
'--rm[Remove intermediate containers when it exits]' \
|
||||||
'--sig-proxy[Proxify all received signal]' \
|
'*--security-opt=-[Security options]:security option: ' \
|
||||||
'-t[Allocate a pseudo-tty]' \
|
'--sig-proxy[Proxy all received signals to the process (non-TTY mode only)]' \
|
||||||
'-u[Username or UID]:user:_users' \
|
{-t,--tty}'[Allocate a pseudo-tty]' \
|
||||||
'*-v[Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)]:volume: '\
|
{-u,--user=-}'[Username or UID]:user:_users' \
|
||||||
'--volumes-from=-[Mount volumes from the specified container]:volume: ' \
|
'*-v[Bind mount a volume]:volume: '\
|
||||||
'-w[Working directory inside the container]:directory:_directories' \
|
'*--volumes-from=-[Mount volumes from the specified container]:volume: ' \
|
||||||
|
{-w,--workdir=-}'[Working directory inside the container]:directory:_directories' \
|
||||||
'(-):images:__docker_images' \
|
'(-):images:__docker_images' \
|
||||||
'(-):command: _command_names -e' \
|
'(-):command: _command_names -e' \
|
||||||
'*::arguments: _normal'
|
'*::arguments: _normal'
|
||||||
|
@ -416,6 +427,7 @@ __docker_subcommand () {
|
||||||
;;
|
;;
|
||||||
(save)
|
(save)
|
||||||
_arguments \
|
_arguments \
|
||||||
|
{-o,--output=-}'[Write to file]:file:_files' \
|
||||||
':images:__docker_images'
|
':images:__docker_images'
|
||||||
;;
|
;;
|
||||||
(wait)
|
(wait)
|
||||||
|
|
|
@ -20,8 +20,6 @@
|
||||||
# docker run --volumes-from chromium-data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
# docker run --volumes-from chromium-data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||||
# -e DISPLAY=unix$DISPLAY chromium
|
# -e DISPLAY=unix$DISPLAY chromium
|
||||||
|
|
||||||
DOCKER_VERSION 1.3
|
|
||||||
|
|
||||||
# Base docker image
|
# Base docker image
|
||||||
FROM debian:jessie
|
FROM debian:jessie
|
||||||
MAINTAINER Jessica Frazelle <jess@docker.com>
|
MAINTAINER Jessica Frazelle <jess@docker.com>
|
||||||
|
|
|
@ -17,8 +17,6 @@
|
||||||
# -e DISPLAY=unix$DISPLAY gparted
|
# -e DISPLAY=unix$DISPLAY gparted
|
||||||
#
|
#
|
||||||
|
|
||||||
DOCKER-VERSION 1.3
|
|
||||||
|
|
||||||
# Base docker image
|
# Base docker image
|
||||||
FROM debian:jessie
|
FROM debian:jessie
|
||||||
MAINTAINER Jessica Frazelle <jess@docker.com>
|
MAINTAINER Jessica Frazelle <jess@docker.com>
|
||||||
|
|
|
@ -3,12 +3,15 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/docker/docker/daemon/graphdriver/devmapper"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/daemon/graphdriver/devmapper"
|
||||||
|
"github.com/docker/docker/pkg/devicemapper"
|
||||||
)
|
)
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
|
@ -60,6 +63,7 @@ func main() {
|
||||||
|
|
||||||
if *flDebug {
|
if *flDebug {
|
||||||
os.Setenv("DEBUG", "1")
|
os.Setenv("DEBUG", "1")
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
if flag.NArg() < 1 {
|
if flag.NArg() < 1 {
|
||||||
|
@ -69,7 +73,7 @@ func main() {
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
|
|
||||||
home := path.Join(*root, "devicemapper")
|
home := path.Join(*root, "devicemapper")
|
||||||
devices, err := devmapper.NewDeviceSet(home, false)
|
devices, err := devmapper.NewDeviceSet(home, false, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Can't initialize device mapper: ", err)
|
fmt.Println("Can't initialize device mapper: ", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -142,7 +146,7 @@ func main() {
|
||||||
usage()
|
usage()
|
||||||
}
|
}
|
||||||
|
|
||||||
err := devices.RemoveDevice(args[1])
|
err := devicemapper.RemoveDevice(args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Can't remove device: ", err)
|
fmt.Println("Can't remove device: ", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -153,7 +157,7 @@ func main() {
|
||||||
usage()
|
usage()
|
||||||
}
|
}
|
||||||
|
|
||||||
err := devices.MountDevice(args[1], args[2], false)
|
err := devices.MountDevice(args[1], args[2], "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Can't create snap device: ", err)
|
fmt.Println("Can't create snap device: ", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
# This Dockerfile will create an image that allows to generate upstart and
|
# This Dockerfile will create an image that allows to generate upstart and
|
||||||
# systemd scripts (more to come)
|
# systemd scripts (more to come)
|
||||||
#
|
#
|
||||||
# docker-version 0.6.2
|
|
||||||
#
|
|
||||||
|
|
||||||
FROM ubuntu:12.10
|
FROM ubuntu:12.10
|
||||||
MAINTAINER Guillaume J. Charmes <guillaume@docker.com>
|
MAINTAINER Guillaume J. Charmes <guillaume@docker.com>
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
Lokesh Mandvekar <lsm5@fedoraproject.org> (@lsm5)
|
Lokesh Mandvekar <lsm5@fedoraproject.org> (@lsm5)
|
||||||
Brandon Philips <brandon.philips@coreos.com> (@philips)
|
Brandon Philips <brandon.philips@coreos.com> (@philips)
|
||||||
|
Jessie Frazelle <jess@docker.com> (@jfrazelle)
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||||
|
Jessie Frazelle <jess@docker.com> (@jfrazelle)
|
|
@ -60,6 +60,6 @@ mknod -m 600 $DEV/initctl p
|
||||||
mknod -m 666 $DEV/ptmx c 5 2
|
mknod -m 666 $DEV/ptmx c 5 2
|
||||||
ln -sf /proc/self/fd $DEV/fd
|
ln -sf /proc/self/fd $DEV/fd
|
||||||
|
|
||||||
tar --numeric-owner -C $ROOTFS -c . | docker import - archlinux
|
tar --numeric-owner --xattrs --acls -C $ROOTFS -c . | docker import - archlinux
|
||||||
docker run -i -t archlinux echo Success.
|
docker run -i -t archlinux echo Success.
|
||||||
rm -rf $ROOTFS
|
rm -rf $ROOTFS
|
||||||
|
|
|
@ -57,7 +57,7 @@ mknod -m 666 "$target"/dev/tty0 c 4 0
|
||||||
mknod -m 666 "$target"/dev/urandom c 1 9
|
mknod -m 666 "$target"/dev/urandom c 1 9
|
||||||
mknod -m 666 "$target"/dev/zero c 1 5
|
mknod -m 666 "$target"/dev/zero c 1 5
|
||||||
|
|
||||||
yum -c "$yum_config" --installroot="$target" --setopt=tsflags=nodocs \
|
yum -c "$yum_config" --installroot="$target" --releasever=/ --setopt=tsflags=nodocs \
|
||||||
--setopt=group_package_types=mandatory -y groupinstall Core
|
--setopt=group_package_types=mandatory -y groupinstall Core
|
||||||
yum -c "$yum_config" --installroot="$target" -y clean all
|
yum -c "$yum_config" --installroot="$target" -y clean all
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,12 @@ done
|
||||||
suite="$1"
|
suite="$1"
|
||||||
shift
|
shift
|
||||||
|
|
||||||
|
# allow for DEBOOTSTRAP=qemu-debootstrap ./mkimage.sh ...
|
||||||
|
: ${DEBOOTSTRAP:=debootstrap}
|
||||||
|
|
||||||
(
|
(
|
||||||
set -x
|
set -x
|
||||||
debootstrap "${before[@]}" "$suite" "$rootfsDir" "$@"
|
$DEBOOTSTRAP "${before[@]}" "$suite" "$rootfsDir" "$@"
|
||||||
)
|
)
|
||||||
|
|
||||||
# now for some Docker-specific tweaks
|
# now for some Docker-specific tweaks
|
||||||
|
|
|
@ -3,4 +3,5 @@ Victor Vieux <vieux@docker.com> (@vieux)
|
||||||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||||
Cristian Staretu <cristian.staretu@gmail.com> (@unclejack)
|
Cristian Staretu <cristian.staretu@gmail.com> (@unclejack)
|
||||||
Tibor Vass <teabee89@gmail.com> (@tiborvass)
|
Tibor Vass <teabee89@gmail.com> (@tiborvass)
|
||||||
|
Vishnu Kannan <vishnuk@google.com> (@vishh)
|
||||||
volumes.go: Brian Goff <cpuguy83@gmail.com> (@cpuguy83)
|
volumes.go: Brian Goff <cpuguy83@gmail.com> (@cpuguy83)
|
||||||
|
|
|
@ -6,10 +6,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
"github.com/docker/docker/pkg/jsonlog"
|
"github.com/docker/docker/pkg/jsonlog"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
"github.com/docker/docker/pkg/promise"
|
"github.com/docker/docker/pkg/promise"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
@ -83,7 +83,6 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
||||||
var (
|
var (
|
||||||
cStdin io.ReadCloser
|
cStdin io.ReadCloser
|
||||||
cStdout, cStderr io.Writer
|
cStdout, cStderr io.Writer
|
||||||
cStdinCloser io.Closer
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if stdin {
|
if stdin {
|
||||||
|
@ -94,7 +93,6 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
||||||
io.Copy(w, job.Stdin)
|
io.Copy(w, job.Stdin)
|
||||||
}()
|
}()
|
||||||
cStdin = r
|
cStdin = r
|
||||||
cStdinCloser = job.Stdin
|
|
||||||
}
|
}
|
||||||
if stdout {
|
if stdout {
|
||||||
cStdout = job.Stdout
|
cStdout = job.Stdout
|
||||||
|
@ -103,7 +101,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
||||||
cStderr = job.Stderr
|
cStderr = job.Stderr
|
||||||
}
|
}
|
||||||
|
|
||||||
<-daemon.Attach(&container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, cStdin, cStdinCloser, cStdout, cStderr)
|
<-daemon.attach(&container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, cStdin, cStdout, cStderr)
|
||||||
// If we are in stdinonce mode, wait for the process to end
|
// If we are in stdinonce mode, wait for the process to end
|
||||||
// otherwise, simply return
|
// otherwise, simply return
|
||||||
if container.Config.StdinOnce && !container.Config.Tty {
|
if container.Config.StdinOnce && !container.Config.Tty {
|
||||||
|
@ -113,13 +111,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
||||||
return engine.StatusOK
|
return engine.StatusOK
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: this should be private, and every outside subsystem
|
func (daemon *Daemon) attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error {
|
||||||
// should go through the "container_attach" job. But that would require
|
|
||||||
// that job to be properly documented, as well as the relationship between
|
|
||||||
// Attach and ContainerAttach.
|
|
||||||
//
|
|
||||||
// This method is in use by builder/builder.go.
|
|
||||||
func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
|
|
||||||
var (
|
var (
|
||||||
cStdout, cStderr io.ReadCloser
|
cStdout, cStderr io.ReadCloser
|
||||||
nJobs int
|
nJobs int
|
||||||
|
@ -136,10 +128,10 @@ func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, t
|
||||||
go func() {
|
go func() {
|
||||||
log.Debugf("attach: stdin: begin")
|
log.Debugf("attach: stdin: begin")
|
||||||
defer log.Debugf("attach: stdin: end")
|
defer log.Debugf("attach: stdin: end")
|
||||||
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
|
||||||
if stdinOnce && !tty {
|
if stdinOnce && !tty {
|
||||||
defer cStdin.Close()
|
defer cStdin.Close()
|
||||||
} else {
|
} else {
|
||||||
|
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
||||||
defer func() {
|
defer func() {
|
||||||
if cStdout != nil {
|
if cStdout != nil {
|
||||||
cStdout.Close()
|
cStdout.Close()
|
||||||
|
@ -179,9 +171,6 @@ func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, t
|
||||||
if stdinOnce && stdin != nil {
|
if stdinOnce && stdin != nil {
|
||||||
defer stdin.Close()
|
defer stdin.Close()
|
||||||
}
|
}
|
||||||
if stdinCloser != nil {
|
|
||||||
defer stdinCloser.Close()
|
|
||||||
}
|
|
||||||
_, err := io.Copy(stdout, cStdout)
|
_, err := io.Copy(stdout, cStdout)
|
||||||
if err == io.ErrClosedPipe {
|
if err == io.ErrClosedPipe {
|
||||||
err = nil
|
err = nil
|
||||||
|
@ -195,9 +184,6 @@ func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, t
|
||||||
} else {
|
} else {
|
||||||
// Point stdout of container to a no-op writer.
|
// Point stdout of container to a no-op writer.
|
||||||
go func() {
|
go func() {
|
||||||
if stdinCloser != nil {
|
|
||||||
defer stdinCloser.Close()
|
|
||||||
}
|
|
||||||
if cStdout, err := streamConfig.StdoutPipe(); err != nil {
|
if cStdout, err := streamConfig.StdoutPipe(); err != nil {
|
||||||
log.Errorf("attach: stdout pipe: %s", err)
|
log.Errorf("attach: stdout pipe: %s", err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -219,9 +205,6 @@ func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, t
|
||||||
if stdinOnce && stdin != nil {
|
if stdinOnce && stdin != nil {
|
||||||
defer stdin.Close()
|
defer stdin.Close()
|
||||||
}
|
}
|
||||||
if stdinCloser != nil {
|
|
||||||
defer stdinCloser.Close()
|
|
||||||
}
|
|
||||||
_, err := io.Copy(stderr, cStderr)
|
_, err := io.Copy(stderr, cStderr)
|
||||||
if err == io.ErrClosedPipe {
|
if err == io.ErrClosedPipe {
|
||||||
err = nil
|
err = nil
|
||||||
|
@ -235,10 +218,6 @@ func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, t
|
||||||
} else {
|
} else {
|
||||||
// Point stderr at a no-op writer.
|
// Point stderr at a no-op writer.
|
||||||
go func() {
|
go func() {
|
||||||
if stdinCloser != nil {
|
|
||||||
defer stdinCloser.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if cStderr, err := streamConfig.StderrPipe(); err != nil {
|
if cStderr, err := streamConfig.StderrPipe(); err != nil {
|
||||||
log.Errorf("attach: stdout pipe: %s", err)
|
log.Errorf("attach: stdout pipe: %s", err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -257,8 +236,6 @@ func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, t
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// FIXME: how to clean up the stdin goroutine without the unwanted side effect
|
|
||||||
// of closing the passed stdin? Add an intermediary io.Pipe?
|
|
||||||
for i := 0; i < nJobs; i++ {
|
for i := 0; i < nJobs; i++ {
|
||||||
log.Debugf("attach: waiting for job %d/%d", i+1, nJobs)
|
log.Debugf("attach: waiting for job %d/%d", i+1, nJobs)
|
||||||
if err := <-errors; err != nil {
|
if err := <-errors; err != nil {
|
||||||
|
|
|
@ -40,6 +40,8 @@ type Config struct {
|
||||||
DisableNetwork bool
|
DisableNetwork bool
|
||||||
EnableSelinuxSupport bool
|
EnableSelinuxSupport bool
|
||||||
Context map[string][]string
|
Context map[string][]string
|
||||||
|
TrustKeyPath string
|
||||||
|
Labels []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstallFlags adds command-line options to the top-level flag parser for
|
// InstallFlags adds command-line options to the top-level flag parser for
|
||||||
|
@ -57,7 +59,7 @@ func (config *Config) InstallFlags() {
|
||||||
flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking")
|
flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking")
|
||||||
flag.StringVar(&config.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)\nthis subnet must be nested in the bridge subnet (which is defined by -b or --bip)")
|
flag.StringVar(&config.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)\nthis subnet must be nested in the bridge subnet (which is defined by -b or --bip)")
|
||||||
opts.ListVar(&config.InsecureRegistries, []string{"-insecure-registry"}, "Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback) (e.g., localhost:5000 or 10.20.0.0/16)")
|
opts.ListVar(&config.InsecureRegistries, []string{"-insecure-registry"}, "Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback) (e.g., localhost:5000 or 10.20.0.0/16)")
|
||||||
flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication")
|
flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Allow unrestricted inter-container and Docker daemon host communication")
|
||||||
flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver")
|
flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver")
|
||||||
flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Force the Docker runtime to use a specific exec driver")
|
flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Force the Docker runtime to use a specific exec driver")
|
||||||
flag.BoolVar(&config.EnableSelinuxSupport, []string{"-selinux-enabled"}, false, "Enable selinux support. SELinux does not presently support the BTRFS storage driver")
|
flag.BoolVar(&config.EnableSelinuxSupport, []string{"-selinux-enabled"}, false, "Enable selinux support. SELinux does not presently support the BTRFS storage driver")
|
||||||
|
@ -68,6 +70,7 @@ func (config *Config) InstallFlags() {
|
||||||
opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "Force Docker to use specific DNS servers")
|
opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "Force Docker to use specific DNS servers")
|
||||||
opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains")
|
opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains")
|
||||||
opts.MirrorListVar(&config.Mirrors, []string{"-registry-mirror"}, "Specify a preferred Docker registry mirror")
|
opts.MirrorListVar(&config.Mirrors, []string{"-registry-mirror"}, "Specify a preferred Docker registry mirror")
|
||||||
|
opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon (displayed in `docker info`)")
|
||||||
|
|
||||||
// Localhost is by default considered as an insecure registry
|
// Localhost is by default considered as an insecure registry
|
||||||
// This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker).
|
// This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker).
|
||||||
|
@ -78,7 +81,7 @@ func (config *Config) InstallFlags() {
|
||||||
config.InsecureRegistries = append(config.InsecureRegistries, "127.0.0.0/8")
|
config.InsecureRegistries = append(config.InsecureRegistries, "127.0.0.0/8")
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultNetworkMtu() int {
|
func getDefaultNetworkMtu() int {
|
||||||
if iface, err := networkdriver.GetDefaultRouteIface(); err == nil {
|
if iface, err := networkdriver.GetDefaultRouteIface(); err == nil {
|
||||||
return iface.MTU
|
return iface.MTU
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"github.com/docker/libcontainer/devices"
|
"github.com/docker/libcontainer/devices"
|
||||||
"github.com/docker/libcontainer/label"
|
"github.com/docker/libcontainer/label"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/daemon/execdriver"
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/image"
|
"github.com/docker/docker/image"
|
||||||
|
@ -25,7 +26,6 @@ import (
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/broadcastwriter"
|
"github.com/docker/docker/pkg/broadcastwriter"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
"github.com/docker/docker/pkg/networkfs/etchosts"
|
"github.com/docker/docker/pkg/networkfs/etchosts"
|
||||||
"github.com/docker/docker/pkg/networkfs/resolvconf"
|
"github.com/docker/docker/pkg/networkfs/resolvconf"
|
||||||
"github.com/docker/docker/pkg/promise"
|
"github.com/docker/docker/pkg/promise"
|
||||||
|
@ -102,13 +102,17 @@ func (container *Container) FromDisk() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(pth)
|
jsonSource, err := os.Open(pth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer jsonSource.Close()
|
||||||
|
|
||||||
|
dec := json.NewDecoder(jsonSource)
|
||||||
|
|
||||||
// Load container settings
|
// Load container settings
|
||||||
// udp broke compat of docker.PortMapping, but it's not used when loading a container, we can skip it
|
// udp broke compat of docker.PortMapping, but it's not used when loading a container, we can skip it
|
||||||
if err := json.Unmarshal(data, container); err != nil && !strings.Contains(err.Error(), "docker.PortMapping") {
|
if err := dec.Decode(container); err != nil && !strings.Contains(err.Error(), "docker.PortMapping") {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,6 +233,18 @@ func populateCommand(c *Container, env []string) error {
|
||||||
return fmt.Errorf("invalid network mode: %s", c.hostConfig.NetworkMode)
|
return fmt.Errorf("invalid network mode: %s", c.hostConfig.NetworkMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipc := &execdriver.Ipc{}
|
||||||
|
|
||||||
|
if c.hostConfig.IpcMode.IsContainer() {
|
||||||
|
ic, err := c.getIpcContainer()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ipc.ContainerID = ic.ID
|
||||||
|
} else {
|
||||||
|
ipc.HostIpc = c.hostConfig.IpcMode.IsHost()
|
||||||
|
}
|
||||||
|
|
||||||
// Build lists of devices allowed and created within the container.
|
// Build lists of devices allowed and created within the container.
|
||||||
userSpecifiedDevices := make([]*devices.Device, len(c.hostConfig.Devices))
|
userSpecifiedDevices := make([]*devices.Device, len(c.hostConfig.Devices))
|
||||||
for i, deviceMapping := range c.hostConfig.Devices {
|
for i, deviceMapping := range c.hostConfig.Devices {
|
||||||
|
@ -244,7 +260,10 @@ func populateCommand(c *Container, env []string) error {
|
||||||
autoCreatedDevices := append(devices.DefaultAutoCreatedDevices, userSpecifiedDevices...)
|
autoCreatedDevices := append(devices.DefaultAutoCreatedDevices, userSpecifiedDevices...)
|
||||||
|
|
||||||
// TODO: this can be removed after lxc-conf is fully deprecated
|
// TODO: this can be removed after lxc-conf is fully deprecated
|
||||||
lxcConfig := mergeLxcConfIntoOptions(c.hostConfig)
|
lxcConfig, err := mergeLxcConfIntoOptions(c.hostConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
resources := &execdriver.Resources{
|
resources := &execdriver.Resources{
|
||||||
Memory: c.Config.Memory,
|
Memory: c.Config.Memory,
|
||||||
|
@ -270,6 +289,7 @@ func populateCommand(c *Container, env []string) error {
|
||||||
InitPath: "/.dockerinit",
|
InitPath: "/.dockerinit",
|
||||||
WorkingDir: c.Config.WorkingDir,
|
WorkingDir: c.Config.WorkingDir,
|
||||||
Network: en,
|
Network: en,
|
||||||
|
Ipc: ipc,
|
||||||
Resources: resources,
|
Resources: resources,
|
||||||
AllowedDevices: allowedDevices,
|
AllowedDevices: allowedDevices,
|
||||||
AutoCreatedDevices: autoCreatedDevices,
|
AutoCreatedDevices: autoCreatedDevices,
|
||||||
|
@ -297,6 +317,12 @@ func (container *Container) Start() (err error) {
|
||||||
// setup has been cleaned up properly
|
// setup has been cleaned up properly
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
container.setError(err)
|
||||||
|
// if no one else has set it, make sure we don't leave it at zero
|
||||||
|
if container.ExitCode == 0 {
|
||||||
|
container.ExitCode = 128
|
||||||
|
}
|
||||||
|
container.toDisk()
|
||||||
container.cleanup()
|
container.cleanup()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -414,7 +440,7 @@ func (container *Container) buildHostsFiles(IP string) error {
|
||||||
}
|
}
|
||||||
container.HostsPath = hostsPath
|
container.HostsPath = hostsPath
|
||||||
|
|
||||||
extraContent := make(map[string]string)
|
var extraContent []etchosts.Record
|
||||||
|
|
||||||
children, err := container.daemon.Children(container.Name)
|
children, err := container.daemon.Children(container.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -423,15 +449,15 @@ func (container *Container) buildHostsFiles(IP string) error {
|
||||||
|
|
||||||
for linkAlias, child := range children {
|
for linkAlias, child := range children {
|
||||||
_, alias := path.Split(linkAlias)
|
_, alias := path.Split(linkAlias)
|
||||||
extraContent[alias] = child.NetworkSettings.IPAddress
|
extraContent = append(extraContent, etchosts.Record{Hosts: alias, IP: child.NetworkSettings.IPAddress})
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, extraHost := range container.hostConfig.ExtraHosts {
|
for _, extraHost := range container.hostConfig.ExtraHosts {
|
||||||
parts := strings.Split(extraHost, ":")
|
parts := strings.Split(extraHost, ":")
|
||||||
extraContent[parts[0]] = parts[1]
|
extraContent = append(extraContent, etchosts.Record{Hosts: parts[0], IP: parts[1]})
|
||||||
}
|
}
|
||||||
|
|
||||||
return etchosts.Build(container.HostsPath, IP, container.Config.Hostname, container.Config.Domainname, &extraContent)
|
return etchosts.Build(container.HostsPath, IP, container.Config.Hostname, container.Config.Domainname, extraContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (container *Container) buildHostnameAndHostsFiles(IP string) error {
|
func (container *Container) buildHostnameAndHostsFiles(IP string) error {
|
||||||
|
@ -455,6 +481,7 @@ func (container *Container) AllocateNetwork() error {
|
||||||
)
|
)
|
||||||
|
|
||||||
job := eng.Job("allocate_interface", container.ID)
|
job := eng.Job("allocate_interface", container.ID)
|
||||||
|
job.Setenv("RequestedMac", container.Config.MacAddress)
|
||||||
if env, err = job.Stdout.AddEnv(); err != nil {
|
if env, err = job.Stdout.AddEnv(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -525,7 +552,9 @@ func (container *Container) ReleaseNetwork() {
|
||||||
}
|
}
|
||||||
eng := container.daemon.eng
|
eng := container.daemon.eng
|
||||||
|
|
||||||
eng.Job("release_interface", container.ID).Run()
|
job := eng.Job("release_interface", container.ID)
|
||||||
|
job.SetenvBool("overrideShutdown", true)
|
||||||
|
job.Run()
|
||||||
container.NetworkSettings = &NetworkSettings{}
|
container.NetworkSettings = &NetworkSettings{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,6 +605,10 @@ func (container *Container) cleanup() {
|
||||||
if err := container.Unmount(); err != nil {
|
if err := container.Unmount(); err != nil {
|
||||||
log.Errorf("%v: Failed to umount filesystem: %v", container.ID, err)
|
log.Errorf("%v: Failed to umount filesystem: %v", container.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, eConfig := range container.execCommands.s {
|
||||||
|
container.daemon.unregisterExecCommand(eConfig)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (container *Container) KillSig(sig int) error {
|
func (container *Container) KillSig(sig int) error {
|
||||||
|
@ -691,6 +724,9 @@ func (container *Container) Restart(seconds int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (container *Container) Resize(h, w int) error {
|
func (container *Container) Resize(h, w int) error {
|
||||||
|
if !container.IsRunning() {
|
||||||
|
return fmt.Errorf("Cannot resize container %s, container is not running", container.ID)
|
||||||
|
}
|
||||||
return container.command.ProcessConfig.Terminal.Resize(h, w)
|
return container.command.ProcessConfig.Terminal.Resize(h, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -826,19 +862,25 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var filter []string
|
|
||||||
|
|
||||||
basePath, err := container.getResourcePath(resource)
|
basePath, err := container.getResourcePath(resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
container.Unmount()
|
container.Unmount()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this is actually in a volume
|
||||||
|
for _, mnt := range container.VolumeMounts() {
|
||||||
|
if len(mnt.MountToPath) > 0 && strings.HasPrefix(resource, mnt.MountToPath[1:]) {
|
||||||
|
return mnt.Export(resource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stat, err := os.Stat(basePath)
|
stat, err := os.Stat(basePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
container.Unmount()
|
container.Unmount()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
var filter []string
|
||||||
if !stat.IsDir() {
|
if !stat.IsDir() {
|
||||||
d, f := path.Split(basePath)
|
d, f := path.Split(basePath)
|
||||||
basePath = d
|
basePath = d
|
||||||
|
@ -965,7 +1007,7 @@ func (container *Container) updateParentsHosts() error {
|
||||||
c := container.daemon.Get(cid)
|
c := container.daemon.Get(cid)
|
||||||
if c != nil && !container.daemon.config.DisableNetwork && container.hostConfig.NetworkMode.IsPrivate() {
|
if c != nil && !container.daemon.config.DisableNetwork && container.hostConfig.NetworkMode.IsPrivate() {
|
||||||
if err := etchosts.Update(c.HostsPath, container.NetworkSettings.IPAddress, container.Name[1:]); err != nil {
|
if err := etchosts.Update(c.HostsPath, container.NetworkSettings.IPAddress, container.Name[1:]); err != nil {
|
||||||
return fmt.Errorf("Failed to update /etc/hosts in parent container: %v", err)
|
log.Errorf("Failed to update /etc/hosts in parent container: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1228,10 +1270,25 @@ func (container *Container) GetMountLabel() string {
|
||||||
return container.MountLabel
|
return container.MountLabel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (container *Container) getIpcContainer() (*Container, error) {
|
||||||
|
containerID := container.hostConfig.IpcMode.Container()
|
||||||
|
c := container.daemon.Get(containerID)
|
||||||
|
if c == nil {
|
||||||
|
return nil, fmt.Errorf("no such container to join IPC: %s", containerID)
|
||||||
|
}
|
||||||
|
if !c.IsRunning() {
|
||||||
|
return nil, fmt.Errorf("cannot join IPC of a non running container: %s", containerID)
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (container *Container) getNetworkedContainer() (*Container, error) {
|
func (container *Container) getNetworkedContainer() (*Container, error) {
|
||||||
parts := strings.SplitN(string(container.hostConfig.NetworkMode), ":", 2)
|
parts := strings.SplitN(string(container.hostConfig.NetworkMode), ":", 2)
|
||||||
switch parts[0] {
|
switch parts[0] {
|
||||||
case "container":
|
case "container":
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return nil, fmt.Errorf("no container specified to join network")
|
||||||
|
}
|
||||||
nc := container.daemon.Get(parts[1])
|
nc := container.daemon.Get(parts[1])
|
||||||
if nc == nil {
|
if nc == nil {
|
||||||
return nil, fmt.Errorf("no such container to join network: %s", parts[1])
|
return nil, fmt.Errorf("no such container to join network: %s", parts[1])
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
package daemon
|
package daemon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/graph"
|
"github.com/docker/docker/graph"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
|
"github.com/docker/libcontainer/label"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (daemon *Daemon) ContainerCreate(job *engine.Job) engine.Status {
|
func (daemon *Daemon) ContainerCreate(job *engine.Job) engine.Status {
|
||||||
|
@ -50,12 +53,9 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) engine.Status {
|
||||||
job.Errorf("IPv4 forwarding is disabled.\n")
|
job.Errorf("IPv4 forwarding is disabled.\n")
|
||||||
}
|
}
|
||||||
container.LogEvent("create")
|
container.LogEvent("create")
|
||||||
// FIXME: this is necessary because daemon.Create might return a nil container
|
|
||||||
// with a non-nil error. This should not happen! Once it's fixed we
|
job.Printf("%s\n", container.ID)
|
||||||
// can remove this workaround.
|
|
||||||
if container != nil {
|
|
||||||
job.Printf("%s\n", container.ID)
|
|
||||||
}
|
|
||||||
for _, warning := range buildWarnings {
|
for _, warning := range buildWarnings {
|
||||||
job.Errorf("%s\n", warning)
|
job.Errorf("%s\n", warning)
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,12 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
|
||||||
if warnings, err = daemon.mergeAndVerifyConfig(config, img); err != nil {
|
if warnings, err = daemon.mergeAndVerifyConfig(config, img); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
if hostConfig != nil && hostConfig.SecurityOpt == nil {
|
||||||
|
hostConfig.SecurityOpt, err = daemon.GenerateSecurityOpt(hostConfig.IpcMode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
if container, err = daemon.newContainer(name, config, img); err != nil {
|
if container, err = daemon.newContainer(name, config, img); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -94,8 +100,33 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := container.Mount(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
defer container.Unmount()
|
||||||
|
if err := container.prepareVolumes(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
if err := container.ToDisk(); err != nil {
|
if err := container.ToDisk(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return container, warnings, nil
|
return container, warnings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (daemon *Daemon) GenerateSecurityOpt(ipcMode runconfig.IpcMode) ([]string, error) {
|
||||||
|
if ipcMode.IsHost() {
|
||||||
|
return label.DisableSecOpt(), nil
|
||||||
|
}
|
||||||
|
if ipcContainer := ipcMode.Container(); ipcContainer != "" {
|
||||||
|
c := daemon.Get(ipcContainer)
|
||||||
|
if c == nil {
|
||||||
|
return nil, fmt.Errorf("no such container to join IPC: %s", ipcContainer)
|
||||||
|
}
|
||||||
|
if !c.IsRunning() {
|
||||||
|
return nil, fmt.Errorf("cannot join IPC of a non running container: %s", ipcContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return label.DupSecOpt(c.ProcessLabel), nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ import (
|
||||||
|
|
||||||
"github.com/docker/libcontainer/label"
|
"github.com/docker/libcontainer/label"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/daemon/execdriver"
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
"github.com/docker/docker/daemon/execdriver/execdrivers"
|
"github.com/docker/docker/daemon/execdriver/execdrivers"
|
||||||
"github.com/docker/docker/daemon/execdriver/lxc"
|
"github.com/docker/docker/daemon/execdriver/lxc"
|
||||||
|
@ -29,7 +31,6 @@ import (
|
||||||
"github.com/docker/docker/pkg/broadcastwriter"
|
"github.com/docker/docker/pkg/broadcastwriter"
|
||||||
"github.com/docker/docker/pkg/graphdb"
|
"github.com/docker/docker/pkg/graphdb"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
"github.com/docker/docker/pkg/namesgenerator"
|
"github.com/docker/docker/pkg/namesgenerator"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/pkg/parsers/kernel"
|
"github.com/docker/docker/pkg/parsers/kernel"
|
||||||
|
@ -83,6 +84,7 @@ func (c *contStore) List() []*Container {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Daemon struct {
|
type Daemon struct {
|
||||||
|
ID string
|
||||||
repository string
|
repository string
|
||||||
sysInitPath string
|
sysInitPath string
|
||||||
containers *contStore
|
containers *contStore
|
||||||
|
@ -128,6 +130,7 @@ func (daemon *Daemon) Install(eng *engine.Engine) error {
|
||||||
"execCreate": daemon.ContainerExecCreate,
|
"execCreate": daemon.ContainerExecCreate,
|
||||||
"execStart": daemon.ContainerExecStart,
|
"execStart": daemon.ContainerExecStart,
|
||||||
"execResize": daemon.ContainerExecResize,
|
"execResize": daemon.ContainerExecResize,
|
||||||
|
"execInspect": daemon.ContainerExecInspect,
|
||||||
} {
|
} {
|
||||||
if err := eng.Register(name, method); err != nil {
|
if err := eng.Register(name, method); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -231,7 +234,7 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err
|
||||||
log.Debugf("killing old running container %s", container.ID)
|
log.Debugf("killing old running container %s", container.ID)
|
||||||
|
|
||||||
existingPid := container.Pid
|
existingPid := container.Pid
|
||||||
container.SetStopped(0)
|
container.SetStopped(&execdriver.ExitStatus{0, false})
|
||||||
|
|
||||||
// We only have to handle this for lxc because the other drivers will ensure that
|
// We only have to handle this for lxc because the other drivers will ensure that
|
||||||
// no processes are left when docker dies
|
// no processes are left when docker dies
|
||||||
|
@ -263,7 +266,7 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err
|
||||||
|
|
||||||
log.Debugf("Marking as stopped")
|
log.Debugf("Marking as stopped")
|
||||||
|
|
||||||
container.SetStopped(-127)
|
container.SetStopped(&execdriver.ExitStatus{-127, false})
|
||||||
if err := container.ToDisk(); err != nil {
|
if err := container.ToDisk(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -304,7 +307,7 @@ func (daemon *Daemon) restore() error {
|
||||||
)
|
)
|
||||||
|
|
||||||
if !debug {
|
if !debug {
|
||||||
log.Infof("Loading containers: ")
|
log.Infof("Loading containers: start.")
|
||||||
}
|
}
|
||||||
dir, err := ioutil.ReadDir(daemon.repository)
|
dir, err := ioutil.ReadDir(daemon.repository)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -392,7 +395,8 @@ func (daemon *Daemon) restore() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !debug {
|
if !debug {
|
||||||
log.Infof(": done.")
|
fmt.Println()
|
||||||
|
log.Infof("Loading containers: done.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -692,6 +696,9 @@ func (daemon *Daemon) RegisterLinks(container *Container, hostConfig *runconfig.
|
||||||
if child == nil {
|
if child == nil {
|
||||||
return fmt.Errorf("Could not get container for %s", parts["name"])
|
return fmt.Errorf("Could not get container for %s", parts["name"])
|
||||||
}
|
}
|
||||||
|
if child.hostConfig.NetworkMode.IsHost() {
|
||||||
|
return runconfig.ErrConflictHostNetworkAndLinks
|
||||||
|
}
|
||||||
if err := daemon.RegisterLink(container, child, parts["alias"]); err != nil {
|
if err := daemon.RegisterLink(container, child, parts["alias"]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -717,10 +724,8 @@ func NewDaemon(config *Config, eng *engine.Engine) (*Daemon, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) {
|
func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) {
|
||||||
// Apply configuration defaults
|
|
||||||
if config.Mtu == 0 {
|
if config.Mtu == 0 {
|
||||||
// FIXME: GetDefaultNetwork Mtu doesn't need to be public anymore
|
config.Mtu = getDefaultNetworkMtu()
|
||||||
config.Mtu = GetDefaultNetworkMtu()
|
|
||||||
}
|
}
|
||||||
// Check for mutually incompatible config options
|
// Check for mutually incompatible config options
|
||||||
if config.BridgeIface != "" && config.BridgeIP != "" {
|
if config.BridgeIface != "" && config.BridgeIP != "" {
|
||||||
|
@ -893,7 +898,13 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trustKey, err := api.LoadOrCreateTrustKey(config.TrustKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
daemon := &Daemon{
|
daemon := &Daemon{
|
||||||
|
ID: trustKey.PublicKey().KeyID(),
|
||||||
repository: daemonRepo,
|
repository: daemonRepo,
|
||||||
containers: &contStore{s: make(map[string]*Container)},
|
containers: &contStore{s: make(map[string]*Container)},
|
||||||
execCommands: newExecStore(),
|
execCommands: newExecStore(),
|
||||||
|
@ -918,7 +929,6 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
|
||||||
eng.OnShutdown(func() {
|
eng.OnShutdown(func() {
|
||||||
// FIXME: if these cleanup steps can be called concurrently, register
|
// FIXME: if these cleanup steps can be called concurrently, register
|
||||||
// them as separate handlers to speed up total shutdown time
|
// them as separate handlers to speed up total shutdown time
|
||||||
// FIXME: use engine logging instead of log.Errorf
|
|
||||||
if err := daemon.shutdown(); err != nil {
|
if err := daemon.shutdown(); err != nil {
|
||||||
log.Errorf("daemon.shutdown(): %s", err)
|
log.Errorf("daemon.shutdown(): %s", err)
|
||||||
}
|
}
|
||||||
|
@ -968,6 +978,7 @@ func (daemon *Daemon) Mount(container *Container) error {
|
||||||
if container.basefs == "" {
|
if container.basefs == "" {
|
||||||
container.basefs = dir
|
container.basefs = dir
|
||||||
} else if container.basefs != dir {
|
} else if container.basefs != dir {
|
||||||
|
daemon.driver.Put(container.ID)
|
||||||
return fmt.Errorf("Error: driver %s is returning inconsistent paths for container %s ('%s' then '%s')",
|
return fmt.Errorf("Error: driver %s is returning inconsistent paths for container %s ('%s' then '%s')",
|
||||||
daemon.driver, container.ID, container.basefs, dir)
|
daemon.driver, container.ID, container.basefs, dir)
|
||||||
}
|
}
|
||||||
|
@ -989,7 +1000,7 @@ func (daemon *Daemon) Diff(container *Container) (archive.Archive, error) {
|
||||||
return daemon.driver.Diff(container.ID, initID)
|
return daemon.driver.Diff(container.ID, initID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (daemon *Daemon) Run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
func (daemon *Daemon) Run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
|
||||||
return daemon.execDriver.Run(c.command, pipes, startCallback)
|
return daemon.execDriver.Run(c.command, pipes, startCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
package daemon
|
package daemon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/daemon/graphdriver"
|
"github.com/docker/docker/daemon/graphdriver"
|
||||||
"github.com/docker/docker/daemon/graphdriver/aufs"
|
"github.com/docker/docker/daemon/graphdriver/aufs"
|
||||||
"github.com/docker/docker/graph"
|
"github.com/docker/docker/graph"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Given the graphdriver ad, if it is aufs, then migrate it.
|
// Given the graphdriver ad, if it is aufs, then migrate it.
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
// +build !exclude_graphdriver_overlay
|
||||||
|
|
||||||
|
package daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/docker/docker/daemon/graphdriver/overlay"
|
||||||
|
)
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (daemon *Daemon) ContainerRm(job *engine.Job) engine.Status {
|
func (daemon *Daemon) ContainerRm(job *engine.Job) engine.Status {
|
||||||
|
|
|
@ -9,12 +9,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/daemon/execdriver"
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
"github.com/docker/docker/daemon/execdriver/lxc"
|
"github.com/docker/docker/daemon/execdriver/lxc"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/pkg/broadcastwriter"
|
"github.com/docker/docker/pkg/broadcastwriter"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
"github.com/docker/docker/pkg/promise"
|
"github.com/docker/docker/pkg/promise"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
|
@ -24,6 +24,7 @@ type execConfig struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
ID string
|
ID string
|
||||||
Running bool
|
Running bool
|
||||||
|
ExitCode int
|
||||||
ProcessConfig execdriver.ProcessConfig
|
ProcessConfig execdriver.ProcessConfig
|
||||||
StreamConfig
|
StreamConfig
|
||||||
OpenStdin bool
|
OpenStdin bool
|
||||||
|
@ -97,7 +98,9 @@ func (d *Daemon) getActiveContainer(name string) (*Container, error) {
|
||||||
if !container.IsRunning() {
|
if !container.IsRunning() {
|
||||||
return nil, fmt.Errorf("Container %s is not running", name)
|
return nil, fmt.Errorf("Container %s is not running", name)
|
||||||
}
|
}
|
||||||
|
if container.IsPaused() {
|
||||||
|
return nil, fmt.Errorf("Container %s is paused, unpause the container before exec", name)
|
||||||
|
}
|
||||||
return container, nil
|
return container, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,13 +120,14 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) engine.Status {
|
||||||
return job.Error(err)
|
return job.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
config := runconfig.ExecConfigFromJob(job)
|
config, err := runconfig.ExecConfigFromJob(job)
|
||||||
|
if err != nil {
|
||||||
|
return job.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
entrypoint, args := d.getEntrypointAndArgs(nil, config.Cmd)
|
entrypoint, args := d.getEntrypointAndArgs(nil, config.Cmd)
|
||||||
|
|
||||||
processConfig := execdriver.ProcessConfig{
|
processConfig := execdriver.ProcessConfig{
|
||||||
Privileged: config.Privileged,
|
|
||||||
User: config.User,
|
|
||||||
Tty: config.Tty,
|
Tty: config.Tty,
|
||||||
Entrypoint: entrypoint,
|
Entrypoint: entrypoint,
|
||||||
Arguments: args,
|
Arguments: args,
|
||||||
|
@ -155,7 +159,6 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status {
|
||||||
var (
|
var (
|
||||||
cStdin io.ReadCloser
|
cStdin io.ReadCloser
|
||||||
cStdout, cStderr io.Writer
|
cStdout, cStderr io.Writer
|
||||||
cStdinCloser io.Closer
|
|
||||||
execName = job.Args[0]
|
execName = job.Args[0]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -183,10 +186,10 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status {
|
||||||
r, w := io.Pipe()
|
r, w := io.Pipe()
|
||||||
go func() {
|
go func() {
|
||||||
defer w.Close()
|
defer w.Close()
|
||||||
|
defer log.Debugf("Closing buffered stdin pipe")
|
||||||
io.Copy(w, job.Stdin)
|
io.Copy(w, job.Stdin)
|
||||||
}()
|
}()
|
||||||
cStdin = r
|
cStdin = r
|
||||||
cStdinCloser = job.Stdin
|
|
||||||
}
|
}
|
||||||
if execConfig.OpenStdout {
|
if execConfig.OpenStdout {
|
||||||
cStdout = job.Stdout
|
cStdout = job.Stdout
|
||||||
|
@ -204,12 +207,13 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status {
|
||||||
execConfig.StreamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
|
execConfig.StreamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
|
||||||
}
|
}
|
||||||
|
|
||||||
attachErr := d.Attach(&execConfig.StreamConfig, execConfig.OpenStdin, false, execConfig.ProcessConfig.Tty, cStdin, cStdinCloser, cStdout, cStderr)
|
attachErr := d.attach(&execConfig.StreamConfig, execConfig.OpenStdin, true, execConfig.ProcessConfig.Tty, cStdin, cStdout, cStderr)
|
||||||
|
|
||||||
execErr := make(chan error)
|
execErr := make(chan error)
|
||||||
|
|
||||||
// Remove exec from daemon and container.
|
// Note, the execConfig data will be removed when the container
|
||||||
defer d.unregisterExecCommand(execConfig)
|
// itself is deleted. This allows us to query it (for things like
|
||||||
|
// the exitStatus) even after the cmd is done running.
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err := container.Exec(execConfig)
|
err := container.Exec(execConfig)
|
||||||
|
@ -232,7 +236,17 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Daemon) Exec(c *Container, execConfig *execConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
func (d *Daemon) Exec(c *Container, execConfig *execConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
||||||
return d.execDriver.Exec(c.command, &execConfig.ProcessConfig, pipes, startCallback)
|
exitStatus, err := d.execDriver.Exec(c.command, &execConfig.ProcessConfig, pipes, startCallback)
|
||||||
|
|
||||||
|
// On err, make sure we don't leave ExitCode at zero
|
||||||
|
if err != nil && exitStatus == 0 {
|
||||||
|
exitStatus = 128
|
||||||
|
}
|
||||||
|
|
||||||
|
execConfig.ExitCode = exitStatus
|
||||||
|
execConfig.Running = false
|
||||||
|
|
||||||
|
return exitStatus, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (container *Container) Exec(execConfig *execConfig) error {
|
func (container *Container) Exec(execConfig *execConfig) error {
|
||||||
|
|
|
@ -40,8 +40,17 @@ type TtyTerminal interface {
|
||||||
Master() *os.File
|
Master() *os.File
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExitStatus provides exit reasons for a container.
|
||||||
|
type ExitStatus struct {
|
||||||
|
// The exit code with which the container exited.
|
||||||
|
ExitCode int
|
||||||
|
|
||||||
|
// Whether the container encountered an OOM.
|
||||||
|
OOMKilled bool
|
||||||
|
}
|
||||||
|
|
||||||
type Driver interface {
|
type Driver interface {
|
||||||
Run(c *Command, pipes *Pipes, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code
|
Run(c *Command, pipes *Pipes, startCallback StartCallback) (ExitStatus, error) // Run executes the process and blocks until the process exits and returns the exit code
|
||||||
// Exec executes the process in an existing container, blocks until the process exits and returns the exit code
|
// Exec executes the process in an existing container, blocks until the process exits and returns the exit code
|
||||||
Exec(c *Command, processConfig *ProcessConfig, pipes *Pipes, startCallback StartCallback) (int, error)
|
Exec(c *Command, processConfig *ProcessConfig, pipes *Pipes, startCallback StartCallback) (int, error)
|
||||||
Kill(c *Command, sig int) error
|
Kill(c *Command, sig int) error
|
||||||
|
@ -62,6 +71,12 @@ type Network struct {
|
||||||
HostNetworking bool `json:"host_networking"`
|
HostNetworking bool `json:"host_networking"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IPC settings of the container
|
||||||
|
type Ipc struct {
|
||||||
|
ContainerID string `json:"container_id"` // id of the container to join ipc.
|
||||||
|
HostIpc bool `json:"host_ipc"`
|
||||||
|
}
|
||||||
|
|
||||||
type NetworkInterface struct {
|
type NetworkInterface struct {
|
||||||
Gateway string `json:"gateway"`
|
Gateway string `json:"gateway"`
|
||||||
IPAddress string `json:"ip"`
|
IPAddress string `json:"ip"`
|
||||||
|
@ -106,6 +121,7 @@ type Command struct {
|
||||||
WorkingDir string `json:"working_dir"`
|
WorkingDir string `json:"working_dir"`
|
||||||
ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver
|
ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver
|
||||||
Network *Network `json:"network"`
|
Network *Network `json:"network"`
|
||||||
|
Ipc *Ipc `json:"ipc"`
|
||||||
Resources *Resources `json:"resources"`
|
Resources *Resources `json:"resources"`
|
||||||
Mounts []Mount `json:"mounts"`
|
Mounts []Mount `json:"mounts"`
|
||||||
AllowedDevices []*devices.Device `json:"allowed_devices"`
|
AllowedDevices []*devices.Device `json:"allowed_devices"`
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
# the LXC exec driver needs more maintainers and contributions
|
||||||
Dinesh Subhraveti <dineshs@altiscale.com> (@dineshs-altiscale)
|
Dinesh Subhraveti <dineshs@altiscale.com> (@dineshs-altiscale)
|
||||||
|
|
|
@ -17,8 +17,8 @@ import (
|
||||||
|
|
||||||
"github.com/kr/pty"
|
"github.com/kr/pty"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/daemon/execdriver"
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
|
@ -55,7 +55,7 @@ func (d *driver) Name() string {
|
||||||
return fmt.Sprintf("%s-%s", DriverName, version)
|
return fmt.Sprintf("%s-%s", DriverName, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
|
||||||
var (
|
var (
|
||||||
term execdriver.Terminal
|
term execdriver.Terminal
|
||||||
err error
|
err error
|
||||||
|
@ -76,20 +76,27 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := d.generateEnvConfig(c); err != nil {
|
if err := d.generateEnvConfig(c); err != nil {
|
||||||
return -1, err
|
return execdriver.ExitStatus{-1, false}, err
|
||||||
}
|
}
|
||||||
configPath, err := d.generateLXCConfig(c)
|
configPath, err := d.generateLXCConfig(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return execdriver.ExitStatus{-1, false}, err
|
||||||
}
|
}
|
||||||
params := []string{
|
params := []string{
|
||||||
"lxc-start",
|
"lxc-start",
|
||||||
"-n", c.ID,
|
"-n", c.ID,
|
||||||
"-f", configPath,
|
"-f", configPath,
|
||||||
"--",
|
}
|
||||||
c.InitPath,
|
if c.Network.ContainerID != "" {
|
||||||
|
params = append(params,
|
||||||
|
"--share-net", c.Network.ContainerID,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
params = append(params,
|
||||||
|
"--",
|
||||||
|
c.InitPath,
|
||||||
|
)
|
||||||
if c.Network.Interface != nil {
|
if c.Network.Interface != nil {
|
||||||
params = append(params,
|
params = append(params,
|
||||||
"-g", c.Network.Interface.Gateway,
|
"-g", c.Network.Interface.Gateway,
|
||||||
|
@ -116,14 +123,6 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
||||||
params = append(params, "-w", c.WorkingDir)
|
params = append(params, "-w", c.WorkingDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.CapAdd) > 0 {
|
|
||||||
params = append(params, fmt.Sprintf("-cap-add=%s", strings.Join(c.CapAdd, ":")))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.CapDrop) > 0 {
|
|
||||||
params = append(params, fmt.Sprintf("-cap-drop=%s", strings.Join(c.CapDrop, ":")))
|
|
||||||
}
|
|
||||||
|
|
||||||
params = append(params, "--", c.ProcessConfig.Entrypoint)
|
params = append(params, "--", c.ProcessConfig.Entrypoint)
|
||||||
params = append(params, c.ProcessConfig.Arguments...)
|
params = append(params, c.ProcessConfig.Arguments...)
|
||||||
|
|
||||||
|
@ -155,11 +154,11 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
||||||
c.ProcessConfig.Args = append([]string{name}, arg...)
|
c.ProcessConfig.Args = append([]string{name}, arg...)
|
||||||
|
|
||||||
if err := nodes.CreateDeviceNodes(c.Rootfs, c.AutoCreatedDevices); err != nil {
|
if err := nodes.CreateDeviceNodes(c.Rootfs, c.AutoCreatedDevices); err != nil {
|
||||||
return -1, err
|
return execdriver.ExitStatus{-1, false}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ProcessConfig.Start(); err != nil {
|
if err := c.ProcessConfig.Start(); err != nil {
|
||||||
return -1, err
|
return execdriver.ExitStatus{-1, false}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -183,7 +182,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
||||||
c.ProcessConfig.Process.Kill()
|
c.ProcessConfig.Process.Kill()
|
||||||
c.ProcessConfig.Wait()
|
c.ProcessConfig.Wait()
|
||||||
}
|
}
|
||||||
return -1, err
|
return execdriver.ExitStatus{-1, false}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ContainerPid = pid
|
c.ContainerPid = pid
|
||||||
|
@ -194,7 +193,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
||||||
|
|
||||||
<-waitLock
|
<-waitLock
|
||||||
|
|
||||||
return getExitCode(c), waitErr
|
return execdriver.ExitStatus{getExitCode(c), false}, waitErr
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the exit code of the process
|
/// Return the exit code of the process
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -14,7 +13,6 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/reexec"
|
"github.com/docker/docker/pkg/reexec"
|
||||||
"github.com/docker/libcontainer/netlink"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Args provided to the init function for a driver
|
// Args provided to the init function for a driver
|
||||||
|
@ -59,12 +57,7 @@ func setupNamespace(args *InitArgs) error {
|
||||||
if err := setupEnv(args); err != nil {
|
if err := setupEnv(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := setupHostname(args); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := setupNetworking(args); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := finalizeNamespace(args); err != nil {
|
if err := finalizeNamespace(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -138,59 +131,6 @@ func setupEnv(args *InitArgs) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupHostname(args *InitArgs) error {
|
|
||||||
hostname := getEnv(args, "HOSTNAME")
|
|
||||||
if hostname == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return setHostname(hostname)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup networking
|
|
||||||
func setupNetworking(args *InitArgs) error {
|
|
||||||
if args.Ip != "" {
|
|
||||||
// eth0
|
|
||||||
iface, err := net.InterfaceByName("eth0")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Unable to set up networking: %v", err)
|
|
||||||
}
|
|
||||||
ip, ipNet, err := net.ParseCIDR(args.Ip)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Unable to set up networking: %v", err)
|
|
||||||
}
|
|
||||||
if err := netlink.NetworkLinkAddIp(iface, ip, ipNet); err != nil {
|
|
||||||
return fmt.Errorf("Unable to set up networking: %v", err)
|
|
||||||
}
|
|
||||||
if err := netlink.NetworkSetMTU(iface, args.Mtu); err != nil {
|
|
||||||
return fmt.Errorf("Unable to set MTU: %v", err)
|
|
||||||
}
|
|
||||||
if err := netlink.NetworkLinkUp(iface); err != nil {
|
|
||||||
return fmt.Errorf("Unable to set up networking: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// loopback
|
|
||||||
iface, err = net.InterfaceByName("lo")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Unable to set up networking: %v", err)
|
|
||||||
}
|
|
||||||
if err := netlink.NetworkLinkUp(iface); err != nil {
|
|
||||||
return fmt.Errorf("Unable to set up networking: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if args.Gateway != "" {
|
|
||||||
gw := net.ParseIP(args.Gateway)
|
|
||||||
if gw == nil {
|
|
||||||
return fmt.Errorf("Unable to set up networking, %s is not a valid gateway IP", args.Gateway)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := netlink.AddDefaultGw(gw.String(), "eth0"); err != nil {
|
|
||||||
return fmt.Errorf("Unable to set up networking: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup working directory
|
// Setup working directory
|
||||||
func setupWorkingDirectory(args *InitArgs) error {
|
func setupWorkingDirectory(args *InitArgs) error {
|
||||||
if args.WorkDir == "" {
|
if args.WorkDir == "" {
|
||||||
|
|
|
@ -2,74 +2,19 @@ package lxc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/docker/docker/daemon/execdriver"
|
|
||||||
"github.com/docker/docker/daemon/execdriver/native/template"
|
|
||||||
"github.com/docker/libcontainer/namespaces"
|
"github.com/docker/libcontainer/namespaces"
|
||||||
"github.com/docker/libcontainer/security/capabilities"
|
|
||||||
"github.com/docker/libcontainer/system"
|
|
||||||
"github.com/docker/libcontainer/utils"
|
"github.com/docker/libcontainer/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setHostname(hostname string) error {
|
|
||||||
return syscall.Sethostname([]byte(hostname))
|
|
||||||
}
|
|
||||||
|
|
||||||
func finalizeNamespace(args *InitArgs) error {
|
func finalizeNamespace(args *InitArgs) error {
|
||||||
if err := utils.CloseExecFrom(3); err != nil {
|
if err := utils.CloseExecFrom(3); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// We use the native drivers default template so that things like caps are consistent
|
|
||||||
// across both drivers
|
|
||||||
container := template.New()
|
|
||||||
|
|
||||||
if !args.Privileged {
|
|
||||||
// drop capabilities in bounding set before changing user
|
|
||||||
if err := capabilities.DropBoundingSet(container.Capabilities); err != nil {
|
|
||||||
return fmt.Errorf("drop bounding set %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// preserve existing capabilities while we change users
|
|
||||||
if err := system.SetKeepCaps(); err != nil {
|
|
||||||
return fmt.Errorf("set keep caps %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := namespaces.SetupUser(args.User); err != nil {
|
if err := namespaces.SetupUser(args.User); err != nil {
|
||||||
return fmt.Errorf("setup user %s", err)
|
return fmt.Errorf("setup user %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !args.Privileged {
|
|
||||||
if err := system.ClearKeepCaps(); err != nil {
|
|
||||||
return fmt.Errorf("clear keep caps %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
adds []string
|
|
||||||
drops []string
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.CapAdd != "" {
|
|
||||||
adds = strings.Split(args.CapAdd, ":")
|
|
||||||
}
|
|
||||||
if args.CapDrop != "" {
|
|
||||||
drops = strings.Split(args.CapDrop, ":")
|
|
||||||
}
|
|
||||||
|
|
||||||
caps, err := execdriver.TweakCapabilities(container.Capabilities, adds, drops)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// drop all other capabilities
|
|
||||||
if err := capabilities.DropCapabilities(caps); err != nil {
|
|
||||||
return fmt.Errorf("drop capabilities %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := setupWorkingDirectory(args); err != nil {
|
if err := setupWorkingDirectory(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,6 @@
|
||||||
|
|
||||||
package lxc
|
package lxc
|
||||||
|
|
||||||
import "github.com/docker/docker/daemon/execdriver"
|
func finalizeNamespace(args *InitArgs) error {
|
||||||
|
|
||||||
func setHostname(hostname string) error {
|
|
||||||
panic("Not supported on darwin")
|
|
||||||
}
|
|
||||||
|
|
||||||
func finalizeNamespace(args *execdriver.InitArgs) error {
|
|
||||||
panic("Not supported on darwin")
|
panic("Not supported on darwin")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
package lxc
|
package lxc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
|
nativeTemplate "github.com/docker/docker/daemon/execdriver/native/template"
|
||||||
|
"github.com/docker/libcontainer/label"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/docker/docker/daemon/execdriver"
|
|
||||||
"github.com/docker/libcontainer/label"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const LxcTemplate = `
|
const LxcTemplate = `
|
||||||
|
@ -15,6 +16,13 @@ lxc.network.type = veth
|
||||||
lxc.network.link = {{.Network.Interface.Bridge}}
|
lxc.network.link = {{.Network.Interface.Bridge}}
|
||||||
lxc.network.name = eth0
|
lxc.network.name = eth0
|
||||||
lxc.network.mtu = {{.Network.Mtu}}
|
lxc.network.mtu = {{.Network.Mtu}}
|
||||||
|
{{if .Network.Interface.IPAddress}}
|
||||||
|
lxc.network.ipv4 = {{.Network.Interface.IPAddress}}/{{.Network.Interface.IPPrefixLen}}
|
||||||
|
{{end}}
|
||||||
|
{{if .Network.Interface.Gateway}}
|
||||||
|
lxc.network.ipv4.gateway = {{.Network.Interface.Gateway}}
|
||||||
|
{{end}}
|
||||||
|
lxc.network.flags = up
|
||||||
{{else if .Network.HostNetworking}}
|
{{else if .Network.HostNetworking}}
|
||||||
lxc.network.type = none
|
lxc.network.type = none
|
||||||
{{else}}
|
{{else}}
|
||||||
|
@ -70,10 +78,23 @@ lxc.mount.entry = devpts {{escapeFstabSpaces $ROOTFS}}/dev/pts devpts {{formatMo
|
||||||
lxc.mount.entry = shm {{escapeFstabSpaces $ROOTFS}}/dev/shm tmpfs {{formatMountLabel "size=65536k,nosuid,nodev,noexec" ""}} 0 0
|
lxc.mount.entry = shm {{escapeFstabSpaces $ROOTFS}}/dev/shm tmpfs {{formatMountLabel "size=65536k,nosuid,nodev,noexec" ""}} 0 0
|
||||||
|
|
||||||
{{range $value := .Mounts}}
|
{{range $value := .Mounts}}
|
||||||
|
{{$createVal := isDirectory $value.Source}}
|
||||||
{{if $value.Writable}}
|
{{if $value.Writable}}
|
||||||
lxc.mount.entry = {{$value.Source}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabSpaces $value.Destination}} none rbind,rw 0 0
|
lxc.mount.entry = {{$value.Source}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabSpaces $value.Destination}} none rbind,rw,create={{$createVal}} 0 0
|
||||||
{{else}}
|
{{else}}
|
||||||
lxc.mount.entry = {{$value.Source}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabSpaces $value.Destination}} none rbind,ro 0 0
|
lxc.mount.entry = {{$value.Source}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabSpaces $value.Destination}} none rbind,ro,create={{$createVal}} 0 0
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .ProcessConfig.Env}}
|
||||||
|
lxc.utsname = {{getHostname .ProcessConfig.Env}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .ProcessConfig.Privileged}}
|
||||||
|
# No cap values are needed, as lxc is starting in privileged mode
|
||||||
|
{{else}}
|
||||||
|
{{range $value := keepCapabilities .CapAdd .CapDrop}}
|
||||||
|
lxc.cap.keep = {{$value}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
@ -117,6 +138,33 @@ func escapeFstabSpaces(field string) string {
|
||||||
return strings.Replace(field, " ", "\\040", -1)
|
return strings.Replace(field, " ", "\\040", -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func keepCapabilities(adds []string, drops []string) []string {
|
||||||
|
container := nativeTemplate.New()
|
||||||
|
caps, err := execdriver.TweakCapabilities(container.Capabilities, adds, drops)
|
||||||
|
var newCaps []string
|
||||||
|
for _, cap := range caps {
|
||||||
|
newCaps = append(newCaps, strings.ToLower(cap))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
return newCaps
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDirectory(source string) string {
|
||||||
|
f, err := os.Stat(source)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return "dir"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if f.IsDir() {
|
||||||
|
return "dir"
|
||||||
|
}
|
||||||
|
return "file"
|
||||||
|
}
|
||||||
|
|
||||||
func getMemorySwap(v *execdriver.Resources) int64 {
|
func getMemorySwap(v *execdriver.Resources) int64 {
|
||||||
// By default, MemorySwap is set to twice the size of RAM.
|
// By default, MemorySwap is set to twice the size of RAM.
|
||||||
// If you want to omit MemorySwap, set it to `-1'.
|
// If you want to omit MemorySwap, set it to `-1'.
|
||||||
|
@ -137,12 +185,25 @@ func getLabel(c map[string][]string, name string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getHostname(env []string) string {
|
||||||
|
for _, kv := range env {
|
||||||
|
parts := strings.SplitN(kv, "=", 2)
|
||||||
|
if parts[0] == "HOSTNAME" && len(parts) == 2 {
|
||||||
|
return parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var err error
|
var err error
|
||||||
funcMap := template.FuncMap{
|
funcMap := template.FuncMap{
|
||||||
"getMemorySwap": getMemorySwap,
|
"getMemorySwap": getMemorySwap,
|
||||||
"escapeFstabSpaces": escapeFstabSpaces,
|
"escapeFstabSpaces": escapeFstabSpaces,
|
||||||
"formatMountLabel": label.FormatMountLabel,
|
"formatMountLabel": label.FormatMountLabel,
|
||||||
|
"isDirectory": isDirectory,
|
||||||
|
"keepCapabilities": keepCapabilities,
|
||||||
|
"getHostname": getHostname,
|
||||||
}
|
}
|
||||||
LxcTemplateCompiled, err = template.New("lxc").Funcs(funcMap).Parse(LxcTemplate)
|
LxcTemplateCompiled, err = template.New("lxc").Funcs(funcMap).Parse(LxcTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/daemon/execdriver"
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
|
nativeTemplate "github.com/docker/docker/daemon/execdriver/native/template"
|
||||||
"github.com/docker/libcontainer/devices"
|
"github.com/docker/libcontainer/devices"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -104,6 +105,10 @@ func TestCustomLxcConfig(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func grepFile(t *testing.T, path string, pattern string) {
|
func grepFile(t *testing.T, path string, pattern string) {
|
||||||
|
grepFileWithReverse(t, path, pattern, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func grepFileWithReverse(t *testing.T, path string, pattern string, inverseGrep bool) {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -117,9 +122,15 @@ func grepFile(t *testing.T, path string, pattern string) {
|
||||||
for err == nil {
|
for err == nil {
|
||||||
line, err = r.ReadString('\n')
|
line, err = r.ReadString('\n')
|
||||||
if strings.Contains(line, pattern) == true {
|
if strings.Contains(line, pattern) == true {
|
||||||
|
if inverseGrep {
|
||||||
|
t.Fatalf("grepFile: pattern \"%s\" found in \"%s\"", pattern, path)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if inverseGrep {
|
||||||
|
return
|
||||||
|
}
|
||||||
t.Fatalf("grepFile: pattern \"%s\" not found in \"%s\"", pattern, path)
|
t.Fatalf("grepFile: pattern \"%s\" not found in \"%s\"", pattern, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,3 +151,152 @@ func TestEscapeFstabSpaces(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsDirectory(t *testing.T) {
|
||||||
|
tempDir, err := ioutil.TempDir("", "TestIsDir")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
|
tempFile, err := ioutil.TempFile(tempDir, "TestIsDirFile")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDirectory(tempDir) != "dir" {
|
||||||
|
t.Logf("Could not identify %s as a directory", tempDir)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDirectory(tempFile.Name()) != "file" {
|
||||||
|
t.Logf("Could not identify %s as a file", tempFile.Name())
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomLxcConfigMounts(t *testing.T) {
|
||||||
|
root, err := ioutil.TempDir("", "TestCustomLxcConfig")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(root)
|
||||||
|
tempDir, err := ioutil.TempDir("", "TestIsDir")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
|
tempFile, err := ioutil.TempFile(tempDir, "TestIsDirFile")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
||||||
|
|
||||||
|
driver, err := NewDriver(root, "", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
processConfig := execdriver.ProcessConfig{
|
||||||
|
Privileged: false,
|
||||||
|
}
|
||||||
|
mounts := []execdriver.Mount{
|
||||||
|
{
|
||||||
|
Source: tempDir,
|
||||||
|
Destination: tempDir,
|
||||||
|
Writable: false,
|
||||||
|
Private: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Source: tempFile.Name(),
|
||||||
|
Destination: tempFile.Name(),
|
||||||
|
Writable: true,
|
||||||
|
Private: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
command := &execdriver.Command{
|
||||||
|
ID: "1",
|
||||||
|
LxcConfig: []string{
|
||||||
|
"lxc.utsname = docker",
|
||||||
|
"lxc.cgroup.cpuset.cpus = 0,1",
|
||||||
|
},
|
||||||
|
Network: &execdriver.Network{
|
||||||
|
Mtu: 1500,
|
||||||
|
Interface: nil,
|
||||||
|
},
|
||||||
|
Mounts: mounts,
|
||||||
|
ProcessConfig: processConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := driver.generateLXCConfig(command)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
grepFile(t, p, "lxc.utsname = docker")
|
||||||
|
grepFile(t, p, "lxc.cgroup.cpuset.cpus = 0,1")
|
||||||
|
|
||||||
|
grepFile(t, p, fmt.Sprintf("lxc.mount.entry = %s %s none rbind,ro,create=%s 0 0", tempDir, "/"+tempDir, "dir"))
|
||||||
|
grepFile(t, p, fmt.Sprintf("lxc.mount.entry = %s %s none rbind,rw,create=%s 0 0", tempFile.Name(), "/"+tempFile.Name(), "file"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomLxcConfigMisc(t *testing.T) {
|
||||||
|
root, err := ioutil.TempDir("", "TestCustomLxcConfig")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(root)
|
||||||
|
os.MkdirAll(path.Join(root, "containers", "1"), 0777)
|
||||||
|
driver, err := NewDriver(root, "", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
processConfig := execdriver.ProcessConfig{
|
||||||
|
Privileged: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
processConfig.Env = []string{"HOSTNAME=testhost"}
|
||||||
|
command := &execdriver.Command{
|
||||||
|
ID: "1",
|
||||||
|
LxcConfig: []string{
|
||||||
|
"lxc.cgroup.cpuset.cpus = 0,1",
|
||||||
|
},
|
||||||
|
Network: &execdriver.Network{
|
||||||
|
Mtu: 1500,
|
||||||
|
Interface: &execdriver.NetworkInterface{
|
||||||
|
Gateway: "10.10.10.1",
|
||||||
|
IPAddress: "10.10.10.10",
|
||||||
|
IPPrefixLen: 24,
|
||||||
|
Bridge: "docker0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ProcessConfig: processConfig,
|
||||||
|
CapAdd: []string{"net_admin", "syslog"},
|
||||||
|
CapDrop: []string{"kill", "mknod"},
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := driver.generateLXCConfig(command)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// network
|
||||||
|
grepFile(t, p, "lxc.network.type = veth")
|
||||||
|
grepFile(t, p, "lxc.network.link = docker0")
|
||||||
|
grepFile(t, p, "lxc.network.name = eth0")
|
||||||
|
grepFile(t, p, "lxc.network.ipv4 = 10.10.10.10/24")
|
||||||
|
grepFile(t, p, "lxc.network.ipv4.gateway = 10.10.10.1")
|
||||||
|
grepFile(t, p, "lxc.network.flags = up")
|
||||||
|
|
||||||
|
// hostname
|
||||||
|
grepFile(t, p, "lxc.utsname = testhost")
|
||||||
|
grepFile(t, p, "lxc.cgroup.cpuset.cpus = 0,1")
|
||||||
|
container := nativeTemplate.New()
|
||||||
|
for _, cap := range container.Capabilities {
|
||||||
|
cap = strings.ToLower(cap)
|
||||||
|
if cap != "mknod" && cap != "kill" {
|
||||||
|
grepFile(t, p, fmt.Sprintf("lxc.cap.keep = %s", cap))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grepFileWithReverse(t, p, fmt.Sprintf("lxc.cap.keep = kill"), true)
|
||||||
|
grepFileWithReverse(t, p, fmt.Sprintf("lxc.cap.keep = mknod"), true)
|
||||||
|
}
|
||||||
|
|
|
@ -36,6 +36,10 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e
|
||||||
container.MountConfig.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != ""
|
container.MountConfig.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != ""
|
||||||
container.RestrictSys = true
|
container.RestrictSys = true
|
||||||
|
|
||||||
|
if err := d.createIpc(container, c); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if err := d.createNetwork(container, c); err != nil {
|
if err := d.createNetwork(container, c); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -124,6 +128,28 @@ func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Com
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *driver) createIpc(container *libcontainer.Config, c *execdriver.Command) error {
|
||||||
|
if c.Ipc.HostIpc {
|
||||||
|
container.Namespaces["NEWIPC"] = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Ipc.ContainerID != "" {
|
||||||
|
d.Lock()
|
||||||
|
active := d.activeContainers[c.Ipc.ContainerID]
|
||||||
|
d.Unlock()
|
||||||
|
|
||||||
|
if active == nil || active.cmd.Process == nil {
|
||||||
|
return fmt.Errorf("%s is not a valid running container to join", c.Ipc.ContainerID)
|
||||||
|
}
|
||||||
|
cmd := active.cmd
|
||||||
|
|
||||||
|
container.IpcNsPath = filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "ipc")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *driver) setPrivileged(container *libcontainer.Config) (err error) {
|
func (d *driver) setPrivileged(container *libcontainer.Config) (err error) {
|
||||||
container.Capabilities = capabilities.GetAllCapabilities()
|
container.Capabilities = capabilities.GetAllCapabilities()
|
||||||
container.Cgroups.AllowAllDevices = true
|
container.Cgroups.AllowAllDevices = true
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/daemon/execdriver"
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
"github.com/docker/libcontainer"
|
"github.com/docker/libcontainer"
|
||||||
|
@ -60,11 +61,20 @@ func NewDriver(root, initPath string) (*driver, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
func (d *driver) notifyOnOOM(config *libcontainer.Config) (<-chan struct{}, error) {
|
||||||
|
return fs.NotifyOnOOM(config.Cgroups)
|
||||||
|
}
|
||||||
|
|
||||||
|
type execOutput struct {
|
||||||
|
exitCode int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
|
||||||
// take the Command and populate the libcontainer.Config from it
|
// take the Command and populate the libcontainer.Config from it
|
||||||
container, err := d.createContainer(c)
|
container, err := d.createContainer(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return execdriver.ExitStatus{-1, false}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var term execdriver.Terminal
|
var term execdriver.Terminal
|
||||||
|
@ -75,7 +85,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
||||||
term, err = execdriver.NewStdConsole(&c.ProcessConfig, pipes)
|
term, err = execdriver.NewStdConsole(&c.ProcessConfig, pipes)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return execdriver.ExitStatus{-1, false}, err
|
||||||
}
|
}
|
||||||
c.ProcessConfig.Terminal = term
|
c.ProcessConfig.Terminal = term
|
||||||
|
|
||||||
|
@ -92,40 +102,66 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := d.createContainerRoot(c.ID); err != nil {
|
if err := d.createContainerRoot(c.ID); err != nil {
|
||||||
return -1, err
|
return execdriver.ExitStatus{-1, false}, err
|
||||||
}
|
}
|
||||||
defer d.cleanContainer(c.ID)
|
defer d.cleanContainer(c.ID)
|
||||||
|
|
||||||
if err := d.writeContainerFile(container, c.ID); err != nil {
|
if err := d.writeContainerFile(container, c.ID); err != nil {
|
||||||
return -1, err
|
return execdriver.ExitStatus{-1, false}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return namespaces.Exec(container, c.ProcessConfig.Stdin, c.ProcessConfig.Stdout, c.ProcessConfig.Stderr, c.ProcessConfig.Console, dataPath, args, func(container *libcontainer.Config, console, dataPath, init string, child *os.File, args []string) *exec.Cmd {
|
execOutputChan := make(chan execOutput, 1)
|
||||||
c.ProcessConfig.Path = d.initPath
|
waitForStart := make(chan struct{})
|
||||||
c.ProcessConfig.Args = append([]string{
|
|
||||||
DriverName,
|
|
||||||
"-console", console,
|
|
||||||
"-pipe", "3",
|
|
||||||
"-root", filepath.Join(d.root, c.ID),
|
|
||||||
"--",
|
|
||||||
}, args...)
|
|
||||||
|
|
||||||
// set this to nil so that when we set the clone flags anything else is reset
|
go func() {
|
||||||
c.ProcessConfig.SysProcAttr = &syscall.SysProcAttr{
|
exitCode, err := namespaces.Exec(container, c.ProcessConfig.Stdin, c.ProcessConfig.Stdout, c.ProcessConfig.Stderr, c.ProcessConfig.Console, dataPath, args, func(container *libcontainer.Config, console, dataPath, init string, child *os.File, args []string) *exec.Cmd {
|
||||||
Cloneflags: uintptr(namespaces.GetNamespaceFlags(container.Namespaces)),
|
c.ProcessConfig.Path = d.initPath
|
||||||
}
|
c.ProcessConfig.Args = append([]string{
|
||||||
c.ProcessConfig.ExtraFiles = []*os.File{child}
|
DriverName,
|
||||||
|
"-console", console,
|
||||||
|
"-pipe", "3",
|
||||||
|
"-root", filepath.Join(d.root, c.ID),
|
||||||
|
"--",
|
||||||
|
}, args...)
|
||||||
|
|
||||||
c.ProcessConfig.Env = container.Env
|
// set this to nil so that when we set the clone flags anything else is reset
|
||||||
c.ProcessConfig.Dir = container.RootFs
|
c.ProcessConfig.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
Cloneflags: uintptr(namespaces.GetNamespaceFlags(container.Namespaces)),
|
||||||
|
}
|
||||||
|
c.ProcessConfig.ExtraFiles = []*os.File{child}
|
||||||
|
|
||||||
return &c.ProcessConfig.Cmd
|
c.ProcessConfig.Env = container.Env
|
||||||
}, func() {
|
c.ProcessConfig.Dir = container.RootFs
|
||||||
if startCallback != nil {
|
|
||||||
c.ContainerPid = c.ProcessConfig.Process.Pid
|
return &c.ProcessConfig.Cmd
|
||||||
startCallback(&c.ProcessConfig, c.ContainerPid)
|
}, func() {
|
||||||
}
|
close(waitForStart)
|
||||||
})
|
if startCallback != nil {
|
||||||
|
c.ContainerPid = c.ProcessConfig.Process.Pid
|
||||||
|
startCallback(&c.ProcessConfig, c.ContainerPid)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
execOutputChan <- execOutput{exitCode, err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case execOutput := <-execOutputChan:
|
||||||
|
return execdriver.ExitStatus{execOutput.exitCode, false}, execOutput.err
|
||||||
|
case <-waitForStart:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
oomKill := false
|
||||||
|
oomKillNotification, err := d.notifyOnOOM(container)
|
||||||
|
if err == nil {
|
||||||
|
_, oomKill = <-oomKillNotification
|
||||||
|
} else {
|
||||||
|
log.Warnf("WARNING: Your kernel does not support OOM notifications: %s", err)
|
||||||
|
}
|
||||||
|
// wait for the container to exit.
|
||||||
|
execOutput := <-execOutputChan
|
||||||
|
|
||||||
|
return execdriver.ExitStatus{execOutput.exitCode, oomKill}, execOutput.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *driver) Kill(p *execdriver.Command, sig int) error {
|
func (d *driver) Kill(p *execdriver.Command, sig int) error {
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"github.com/docker/docker/pkg/reexec"
|
"github.com/docker/docker/pkg/reexec"
|
||||||
"github.com/docker/libcontainer"
|
"github.com/docker/libcontainer"
|
||||||
"github.com/docker/libcontainer/namespaces"
|
"github.com/docker/libcontainer/namespaces"
|
||||||
"github.com/docker/libcontainer/syncpipe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -48,12 +47,7 @@ func initializer() {
|
||||||
writeError(err)
|
writeError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
syncPipe, err := syncpipe.NewSyncPipeFromFd(0, uintptr(*pipe))
|
if err := namespaces.Init(container, rootfs, *console, os.NewFile(uintptr(*pipe), "child"), flag.Args()); err != nil {
|
||||||
if err != nil {
|
|
||||||
writeError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := namespaces.Init(container, rootfs, *console, syncPipe, flag.Args()); err != nil {
|
|
||||||
writeError(err)
|
writeError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
package native
|
package native
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/docker/libcontainer"
|
"github.com/docker/libcontainer"
|
||||||
"github.com/docker/libcontainer/syncpipe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func findUserArgs() []string {
|
func findUserArgs() []string {
|
||||||
|
@ -21,15 +21,9 @@ func findUserArgs() []string {
|
||||||
// loadConfigFromFd loads a container's config from the sync pipe that is provided by
|
// loadConfigFromFd loads a container's config from the sync pipe that is provided by
|
||||||
// fd 3 when running a process
|
// fd 3 when running a process
|
||||||
func loadConfigFromFd() (*libcontainer.Config, error) {
|
func loadConfigFromFd() (*libcontainer.Config, error) {
|
||||||
syncPipe, err := syncpipe.NewSyncPipeFromFd(0, 3)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var config *libcontainer.Config
|
var config *libcontainer.Config
|
||||||
if err := syncPipe.ReadFromParent(&config); err != nil {
|
if err := json.NewDecoder(os.NewFile(3, "child")).Decode(&config); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,10 +30,10 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/daemon/graphdriver"
|
"github.com/docker/docker/daemon/graphdriver"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/chrootarchive"
|
"github.com/docker/docker/pkg/chrootarchive"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
mountpk "github.com/docker/docker/pkg/mount"
|
mountpk "github.com/docker/docker/pkg/mount"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
"github.com/docker/libcontainer/label"
|
"github.com/docker/libcontainer/label"
|
||||||
|
@ -99,7 +99,7 @@ func Init(root string, options []string) (graphdriver.Driver, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := graphdriver.MakePrivate(root); err != nil {
|
if err := mountpk.MakePrivate(root); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,6 +301,7 @@ func (a *Driver) Diff(id, parent string) (archive.Archive, error) {
|
||||||
// AUFS doesn't need the parent layer to produce a diff.
|
// AUFS doesn't need the parent layer to produce a diff.
|
||||||
return archive.TarWithOptions(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
|
return archive.TarWithOptions(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
|
||||||
Compression: archive.Uncompressed,
|
Compression: archive.Uncompressed,
|
||||||
|
Excludes: []string{".wh..wh.*"},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,39 +413,44 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err = a.tryMount(ro, rw, target, mountLabel); err != nil {
|
// Mount options are clipped to page size(4096 bytes). If there are more
|
||||||
if err = a.mountRw(rw, target, mountLabel); err != nil {
|
// layers then these are remounted individually using append.
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, layer := range ro {
|
b := make([]byte, syscall.Getpagesize()-len(mountLabel)-50) // room for xino & mountLabel
|
||||||
data := label.FormatMountLabel(fmt.Sprintf("append:%s=ro+wh", layer), mountLabel)
|
bp := copy(b, fmt.Sprintf("br:%s=rw", rw))
|
||||||
if err = mount("none", target, "aufs", MsRemount, data); err != nil {
|
|
||||||
return
|
firstMount := true
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for {
|
||||||
|
for ; i < len(ro); i++ {
|
||||||
|
layer := fmt.Sprintf(":%s=ro+wh", ro[i])
|
||||||
|
|
||||||
|
if firstMount {
|
||||||
|
if bp+len(layer) > len(b) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
bp += copy(b[bp:], layer)
|
||||||
|
} else {
|
||||||
|
data := label.FormatMountLabel(fmt.Sprintf("append%s", layer), mountLabel)
|
||||||
|
if err = mount("none", target, "aufs", MsRemount, data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if firstMount {
|
||||||
|
data := label.FormatMountLabel(fmt.Sprintf("%s,xino=/dev/shm/aufs.xino", string(b[:bp])), mountLabel)
|
||||||
|
if err = mount("none", target, "aufs", 0, data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
firstMount = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == len(ro) {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to mount using the aufs fast path, if this fails then
|
|
||||||
// append ro layers.
|
|
||||||
func (a *Driver) tryMount(ro []string, rw, target, mountLabel string) (err error) {
|
|
||||||
var (
|
|
||||||
rwBranch = fmt.Sprintf("%s=rw", rw)
|
|
||||||
roBranches = fmt.Sprintf("%s=ro+wh:", strings.Join(ro, "=ro+wh:"))
|
|
||||||
data = label.FormatMountLabel(fmt.Sprintf("br:%v:%v,xino=/dev/shm/aufs.xino", rwBranch, roBranches), mountLabel)
|
|
||||||
)
|
|
||||||
return mount("none", target, "aufs", 0, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Driver) mountRw(rw, target, mountLabel string) error {
|
|
||||||
data := label.FormatMountLabel(fmt.Sprintf("br:%s,xino=/dev/shm/aufs.xino", rw), mountLabel)
|
|
||||||
return mount("none", target, "aufs", 0, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func rollbackMount(target string, err error) {
|
|
||||||
if err != nil {
|
|
||||||
Unmount(target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -15,7 +15,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
tmp = path.Join(os.TempDir(), "aufs-tests", "aufs")
|
tmpOuter = path.Join(os.TempDir(), "aufs-tests")
|
||||||
|
tmp = path.Join(tmpOuter, "aufs")
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -641,9 +642,13 @@ func hash(c string) string {
|
||||||
return hex.EncodeToString(h.Sum(nil))
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMountMoreThan42Layers(t *testing.T) {
|
func testMountMoreThan42Layers(t *testing.T, mountPath string) {
|
||||||
d := newDriver(t)
|
if err := os.MkdirAll(mountPath, 0755); err != nil {
|
||||||
defer os.RemoveAll(tmp)
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.RemoveAll(mountPath)
|
||||||
|
d := testInit(mountPath, t).(*Driver)
|
||||||
defer d.Cleanup()
|
defer d.Cleanup()
|
||||||
var last string
|
var last string
|
||||||
var expected int
|
var expected int
|
||||||
|
@ -664,24 +669,24 @@ func TestMountMoreThan42Layers(t *testing.T) {
|
||||||
|
|
||||||
if err := d.Create(current, parent); err != nil {
|
if err := d.Create(current, parent); err != nil {
|
||||||
t.Logf("Current layer %d", i)
|
t.Logf("Current layer %d", i)
|
||||||
t.Fatal(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
point, err := d.Get(current, "")
|
point, err := d.Get(current, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Logf("Current layer %d", i)
|
t.Logf("Current layer %d", i)
|
||||||
t.Fatal(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
f, err := os.Create(path.Join(point, current))
|
f, err := os.Create(path.Join(point, current))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Logf("Current layer %d", i)
|
t.Logf("Current layer %d", i)
|
||||||
t.Fatal(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
||||||
if i%10 == 0 {
|
if i%10 == 0 {
|
||||||
if err := os.Remove(path.Join(point, parent)); err != nil {
|
if err := os.Remove(path.Join(point, parent)); err != nil {
|
||||||
t.Logf("Current layer %d", i)
|
t.Logf("Current layer %d", i)
|
||||||
t.Fatal(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
expected--
|
expected--
|
||||||
}
|
}
|
||||||
|
@ -691,13 +696,37 @@ func TestMountMoreThan42Layers(t *testing.T) {
|
||||||
// Perform the actual mount for the top most image
|
// Perform the actual mount for the top most image
|
||||||
point, err := d.Get(last, "")
|
point, err := d.Get(last, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
files, err := ioutil.ReadDir(point)
|
files, err := ioutil.ReadDir(point)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
if len(files) != expected {
|
if len(files) != expected {
|
||||||
t.Fatalf("Expected %d got %d", expected, len(files))
|
t.Errorf("Expected %d got %d", expected, len(files))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMountMoreThan42Layers(t *testing.T) {
|
||||||
|
os.RemoveAll(tmpOuter)
|
||||||
|
testMountMoreThan42Layers(t, tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMountMoreThan42LayersMatchingPathLength(t *testing.T) {
|
||||||
|
defer os.RemoveAll(tmpOuter)
|
||||||
|
zeroes := "0"
|
||||||
|
for {
|
||||||
|
// This finds a mount path so that when combined into aufs mount options
|
||||||
|
// 4096 byte boundary would be in between the paths or in permission
|
||||||
|
// section. For '/tmp' it will use '/tmp/aufs-tests/00000000/aufs'
|
||||||
|
mountPath := path.Join(tmpOuter, zeroes, "aufs")
|
||||||
|
pathLength := 77 + len(mountPath)
|
||||||
|
|
||||||
|
if mod := 4095 % pathLength; mod == 0 || mod > pathLength-2 {
|
||||||
|
t.Logf("Using path: %s", mountPath)
|
||||||
|
testMountMoreThan42Layers(t, mountPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
zeroes += "0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/log"
|
log "github.com/Sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Unmount(target string) error {
|
func Unmount(target string) error {
|
||||||
|
|
|
@ -40,7 +40,7 @@ func Init(home string, options []string) (graphdriver.Driver, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := graphdriver.MakePrivate(home); err != nil {
|
if err := mount.MakePrivate(home); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +60,14 @@ func (d *Driver) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Status() [][2]string {
|
func (d *Driver) Status() [][2]string {
|
||||||
return nil
|
status := [][2]string{}
|
||||||
|
if bv := BtrfsBuildVersion(); bv != "-" {
|
||||||
|
status = append(status, [2]string{"Build Version", bv})
|
||||||
|
}
|
||||||
|
if lv := BtrfsLibVersion(); lv != -1 {
|
||||||
|
status = append(status, [2]string{"Library Version", fmt.Sprintf("%d", lv)})
|
||||||
|
}
|
||||||
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Cleanup() error {
|
func (d *Driver) Cleanup() error {
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
// +build linux,!btrfs_noversion
|
||||||
|
|
||||||
|
package btrfs
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <btrfs/version.h>
|
||||||
|
|
||||||
|
// because around version 3.16, they did not define lib version yet
|
||||||
|
int my_btrfs_lib_version() {
|
||||||
|
#ifdef BTRFS_LIB_VERSION
|
||||||
|
return BTRFS_LIB_VERSION;
|
||||||
|
#else
|
||||||
|
return -1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
func BtrfsBuildVersion() string {
|
||||||
|
return string(C.BTRFS_BUILD_VERSION)
|
||||||
|
}
|
||||||
|
func BtrfsLibVersion() int {
|
||||||
|
return int(C.BTRFS_LIB_VERSION)
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
// +build linux,btrfs_noversion
|
||||||
|
|
||||||
|
package btrfs
|
||||||
|
|
||||||
|
// TODO(vbatts) remove this work-around once supported linux distros are on
|
||||||
|
// btrfs utililties of >= 3.16.1
|
||||||
|
|
||||||
|
func BtrfsBuildVersion() string {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
func BtrfsLibVersion() int {
|
||||||
|
return -1
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package btrfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildVersion(t *testing.T) {
|
||||||
|
if len(BtrfsBuildVersion()) == 0 {
|
||||||
|
t.Errorf("expected output from btrfs build version, but got empty string")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
Alexander Larsson <alexl@redhat.com> (@alexlarsson)
|
Alexander Larsson <alexl@redhat.com> (@alexlarsson)
|
||||||
|
Vincent Batts <vbatts@redhat.com> (@vbatts)
|
||||||
|
|
|
@ -100,6 +100,25 @@ Here is the list of supported options:
|
||||||
|
|
||||||
``docker -d --storage-opt dm.mountopt=nodiscard``
|
``docker -d --storage-opt dm.mountopt=nodiscard``
|
||||||
|
|
||||||
|
* `dm.thinpooldev`
|
||||||
|
|
||||||
|
Specifies a custom blockdevice to use for the thin pool.
|
||||||
|
|
||||||
|
If using a block device for device mapper storage, ideally lvm2
|
||||||
|
would be used to create/manage the thin-pool volume that is then
|
||||||
|
handed to docker to exclusively create/manage the thin and thin
|
||||||
|
snapshot volumes needed for it's containers. Managing the thin-pool
|
||||||
|
outside of docker makes for the most feature-rich method of having
|
||||||
|
docker utilize device mapper thin provisioning as the backing
|
||||||
|
storage for docker's containers. lvm2-based thin-pool management
|
||||||
|
feature highlights include: automatic or interactive thin-pool
|
||||||
|
resize support, dynamically change thin-pool features, automatic
|
||||||
|
thinp metadata checking when lvm2 activates the thin-pool, etc.
|
||||||
|
|
||||||
|
Example use:
|
||||||
|
|
||||||
|
``docker -d --storage-opt dm.thinpooldev=/dev/mapper/thin-pool``
|
||||||
|
|
||||||
* `dm.datadev`
|
* `dm.datadev`
|
||||||
|
|
||||||
Specifies a custom blockdevice to use for data for the thin pool.
|
Specifies a custom blockdevice to use for data for the thin pool.
|
||||||
|
|
|
@ -18,8 +18,9 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/daemon/graphdriver"
|
"github.com/docker/docker/daemon/graphdriver"
|
||||||
"github.com/docker/docker/pkg/log"
|
"github.com/docker/docker/pkg/devicemapper"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/pkg/units"
|
"github.com/docker/docker/pkg/units"
|
||||||
"github.com/docker/libcontainer/label"
|
"github.com/docker/libcontainer/label"
|
||||||
|
@ -29,9 +30,20 @@ var (
|
||||||
DefaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024
|
DefaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024
|
||||||
DefaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024
|
DefaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024
|
||||||
DefaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024
|
DefaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024
|
||||||
DefaultThinpBlockSize uint32 = 128 // 64K = 128 512b sectors
|
DefaultThinpBlockSize uint32 = 128 // 64K = 128 512b sectors
|
||||||
|
MaxDeviceId int = 0xffffff // 24 bit, pool limit
|
||||||
|
DeviceIdMapSz int = (MaxDeviceId + 1) / 8
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const deviceSetMetaFile string = "deviceset-metadata"
|
||||||
|
const transactionMetaFile string = "transaction-metadata"
|
||||||
|
|
||||||
|
type Transaction struct {
|
||||||
|
OpenTransactionId uint64 `json:"open_transaction_id"`
|
||||||
|
DeviceIdHash string `json:"device_hash"`
|
||||||
|
DeviceId int `json:"device_id"`
|
||||||
|
}
|
||||||
|
|
||||||
type DevInfo struct {
|
type DevInfo struct {
|
||||||
Hash string `json:"-"`
|
Hash string `json:"-"`
|
||||||
DeviceId int `json:"device_id"`
|
DeviceId int `json:"device_id"`
|
||||||
|
@ -62,13 +74,13 @@ type MetaData struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeviceSet struct {
|
type DeviceSet struct {
|
||||||
MetaData
|
MetaData `json:"-"`
|
||||||
sync.Mutex // Protects Devices map and serializes calls into libdevmapper
|
sync.Mutex `json:"-"` // Protects Devices map and serializes calls into libdevmapper
|
||||||
root string
|
root string
|
||||||
devicePrefix string
|
devicePrefix string
|
||||||
TransactionId uint64
|
TransactionId uint64 `json:"-"`
|
||||||
NewTransactionId uint64
|
NextDeviceId int `json:"next_device_id"`
|
||||||
nextDeviceId int
|
deviceIdMap []byte
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
dataLoopbackSize int64
|
dataLoopbackSize int64
|
||||||
|
@ -81,6 +93,8 @@ type DeviceSet struct {
|
||||||
metadataDevice string
|
metadataDevice string
|
||||||
doBlkDiscard bool
|
doBlkDiscard bool
|
||||||
thinpBlockSize uint32
|
thinpBlockSize uint32
|
||||||
|
thinPoolDevice string
|
||||||
|
Transaction `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DiskUsage struct {
|
type DiskUsage struct {
|
||||||
|
@ -138,12 +152,23 @@ func (devices *DeviceSet) metadataFile(info *DevInfo) string {
|
||||||
return path.Join(devices.metadataDir(), file)
|
return path.Join(devices.metadataDir(), file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) transactionMetaFile() string {
|
||||||
|
return path.Join(devices.metadataDir(), transactionMetaFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) deviceSetMetaFile() string {
|
||||||
|
return path.Join(devices.metadataDir(), deviceSetMetaFile)
|
||||||
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) oldMetadataFile() string {
|
func (devices *DeviceSet) oldMetadataFile() string {
|
||||||
return path.Join(devices.loopbackDir(), "json")
|
return path.Join(devices.loopbackDir(), "json")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) getPoolName() string {
|
func (devices *DeviceSet) getPoolName() string {
|
||||||
return devices.devicePrefix + "-pool"
|
if devices.thinPoolDevice == "" {
|
||||||
|
return devices.devicePrefix + "-pool"
|
||||||
|
}
|
||||||
|
return devices.thinPoolDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) getPoolDevName() string {
|
func (devices *DeviceSet) getPoolDevName() string {
|
||||||
|
@ -189,8 +214,16 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) allocateTransactionId() uint64 {
|
func (devices *DeviceSet) allocateTransactionId() uint64 {
|
||||||
devices.NewTransactionId = devices.NewTransactionId + 1
|
devices.OpenTransactionId = devices.TransactionId + 1
|
||||||
return devices.NewTransactionId
|
return devices.OpenTransactionId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) updatePoolTransactionId() error {
|
||||||
|
if err := devicemapper.SetTransactionId(devices.getPoolDevName(), devices.TransactionId, devices.OpenTransactionId); err != nil {
|
||||||
|
return fmt.Errorf("Error setting devmapper transaction ID: %s", err)
|
||||||
|
}
|
||||||
|
devices.TransactionId = devices.OpenTransactionId
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) removeMetadata(info *DevInfo) error {
|
func (devices *DeviceSet) removeMetadata(info *DevInfo) error {
|
||||||
|
@ -200,11 +233,8 @@ func (devices *DeviceSet) removeMetadata(info *DevInfo) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) saveMetadata(info *DevInfo) error {
|
// Given json data and file path, write it to disk
|
||||||
jsonData, err := json.Marshal(info)
|
func (devices *DeviceSet) writeMetaFile(jsonData []byte, filePath string) error {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error encoding metadata to json: %s", err)
|
|
||||||
}
|
|
||||||
tmpFile, err := ioutil.TempFile(devices.metadataDir(), ".tmp")
|
tmpFile, err := ioutil.TempFile(devices.metadataDir(), ".tmp")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error creating metadata file: %s", err)
|
return fmt.Errorf("Error creating metadata file: %s", err)
|
||||||
|
@ -223,19 +253,48 @@ func (devices *DeviceSet) saveMetadata(info *DevInfo) error {
|
||||||
if err := tmpFile.Close(); err != nil {
|
if err := tmpFile.Close(); err != nil {
|
||||||
return fmt.Errorf("Error closing metadata file %s: %s", tmpFile.Name(), err)
|
return fmt.Errorf("Error closing metadata file %s: %s", tmpFile.Name(), err)
|
||||||
}
|
}
|
||||||
if err := os.Rename(tmpFile.Name(), devices.metadataFile(info)); err != nil {
|
if err := os.Rename(tmpFile.Name(), filePath); err != nil {
|
||||||
return fmt.Errorf("Error committing metadata file %s: %s", tmpFile.Name(), err)
|
return fmt.Errorf("Error committing metadata file %s: %s", tmpFile.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if devices.NewTransactionId != devices.TransactionId {
|
return nil
|
||||||
if err = setTransactionId(devices.getPoolDevName(), devices.TransactionId, devices.NewTransactionId); err != nil {
|
}
|
||||||
return fmt.Errorf("Error setting devmapper transition ID: %s", err)
|
|
||||||
}
|
func (devices *DeviceSet) saveMetadata(info *DevInfo) error {
|
||||||
devices.TransactionId = devices.NewTransactionId
|
jsonData, err := json.Marshal(info)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error encoding metadata to json: %s", err)
|
||||||
|
}
|
||||||
|
if err := devices.writeMetaFile(jsonData, devices.metadataFile(info)); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) markDeviceIdUsed(deviceId int) {
|
||||||
|
var mask byte
|
||||||
|
i := deviceId % 8
|
||||||
|
mask = 1 << uint(i)
|
||||||
|
devices.deviceIdMap[deviceId/8] = devices.deviceIdMap[deviceId/8] | mask
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) markDeviceIdFree(deviceId int) {
|
||||||
|
var mask byte
|
||||||
|
i := deviceId % 8
|
||||||
|
mask = ^(1 << uint(i))
|
||||||
|
devices.deviceIdMap[deviceId/8] = devices.deviceIdMap[deviceId/8] & mask
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) isDeviceIdFree(deviceId int) bool {
|
||||||
|
var mask byte
|
||||||
|
i := deviceId % 8
|
||||||
|
mask = (1 << uint(i))
|
||||||
|
if (devices.deviceIdMap[deviceId/8] & mask) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) lookupDevice(hash string) (*DevInfo, error) {
|
func (devices *DeviceSet) lookupDevice(hash string) (*DevInfo, error) {
|
||||||
devices.devicesLock.Lock()
|
devices.devicesLock.Lock()
|
||||||
defer devices.devicesLock.Unlock()
|
defer devices.devicesLock.Unlock()
|
||||||
|
@ -251,13 +310,91 @@ func (devices *DeviceSet) lookupDevice(hash string) (*DevInfo, error) {
|
||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) registerDevice(id int, hash string, size uint64) (*DevInfo, error) {
|
func (devices *DeviceSet) deviceFileWalkFunction(path string, finfo os.FileInfo) error {
|
||||||
|
|
||||||
|
// Skip some of the meta files which are not device files.
|
||||||
|
if strings.HasSuffix(finfo.Name(), ".migrated") {
|
||||||
|
log.Debugf("Skipping file %s", path)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if finfo.Name() == deviceSetMetaFile {
|
||||||
|
log.Debugf("Skipping file %s", path)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Loading data for file %s", path)
|
||||||
|
|
||||||
|
hash := finfo.Name()
|
||||||
|
if hash == "base" {
|
||||||
|
hash = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
dinfo := devices.loadMetadata(hash)
|
||||||
|
if dinfo == nil {
|
||||||
|
return fmt.Errorf("Error loading device metadata file %s", hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dinfo.DeviceId > MaxDeviceId {
|
||||||
|
log.Errorf("Warning: Ignoring Invalid DeviceId=%d", dinfo.DeviceId)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
devices.Lock()
|
||||||
|
devices.markDeviceIdUsed(dinfo.DeviceId)
|
||||||
|
devices.Unlock()
|
||||||
|
|
||||||
|
log.Debugf("Added deviceId=%d to DeviceIdMap", dinfo.DeviceId)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) constructDeviceIdMap() error {
|
||||||
|
log.Debugf("[deviceset] constructDeviceIdMap()")
|
||||||
|
defer log.Debugf("[deviceset] constructDeviceIdMap() END")
|
||||||
|
|
||||||
|
var scan = func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("Can't walk the file %s", path)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip any directories
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices.deviceFileWalkFunction(path, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Walk(devices.metadataDir(), scan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) unregisterDevice(id int, hash string) error {
|
||||||
|
log.Debugf("unregisterDevice(%v, %v)", id, hash)
|
||||||
|
info := &DevInfo{
|
||||||
|
Hash: hash,
|
||||||
|
DeviceId: id,
|
||||||
|
}
|
||||||
|
|
||||||
|
devices.devicesLock.Lock()
|
||||||
|
delete(devices.Devices, hash)
|
||||||
|
devices.devicesLock.Unlock()
|
||||||
|
|
||||||
|
if err := devices.removeMetadata(info); err != nil {
|
||||||
|
log.Debugf("Error removing meta data: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) registerDevice(id int, hash string, size uint64, transactionId uint64) (*DevInfo, error) {
|
||||||
log.Debugf("registerDevice(%v, %v)", id, hash)
|
log.Debugf("registerDevice(%v, %v)", id, hash)
|
||||||
info := &DevInfo{
|
info := &DevInfo{
|
||||||
Hash: hash,
|
Hash: hash,
|
||||||
DeviceId: id,
|
DeviceId: id,
|
||||||
Size: size,
|
Size: size,
|
||||||
TransactionId: devices.allocateTransactionId(),
|
TransactionId: transactionId,
|
||||||
Initialized: false,
|
Initialized: false,
|
||||||
devices: devices,
|
devices: devices,
|
||||||
}
|
}
|
||||||
|
@ -280,11 +417,11 @@ func (devices *DeviceSet) registerDevice(id int, hash string, size uint64) (*Dev
|
||||||
func (devices *DeviceSet) activateDeviceIfNeeded(info *DevInfo) error {
|
func (devices *DeviceSet) activateDeviceIfNeeded(info *DevInfo) error {
|
||||||
log.Debugf("activateDeviceIfNeeded(%v)", info.Hash)
|
log.Debugf("activateDeviceIfNeeded(%v)", info.Hash)
|
||||||
|
|
||||||
if devinfo, _ := getInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 {
|
if devinfo, _ := devicemapper.GetInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return activateDevice(devices.getPoolDevName(), info.Name(), info.DeviceId, info.Size)
|
return devicemapper.ActivateDevice(devices.getPoolDevName(), info.Name(), info.DeviceId, info.Size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) createFilesystem(info *DevInfo) error {
|
func (devices *DeviceSet) createFilesystem(info *DevInfo) error {
|
||||||
|
@ -320,19 +457,8 @@ func (devices *DeviceSet) createFilesystem(info *DevInfo) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) initMetaData() error {
|
func (devices *DeviceSet) migrateOldMetaData() error {
|
||||||
_, _, _, params, err := getStatus(devices.getPoolName())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := fmt.Sscanf(params, "%d", &devices.TransactionId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
devices.NewTransactionId = devices.TransactionId
|
|
||||||
|
|
||||||
// Migrate old metadatafile
|
// Migrate old metadatafile
|
||||||
|
|
||||||
jsonData, err := ioutil.ReadFile(devices.oldMetadataFile())
|
jsonData, err := ioutil.ReadFile(devices.oldMetadataFile())
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
|
@ -347,11 +473,7 @@ func (devices *DeviceSet) initMetaData() error {
|
||||||
|
|
||||||
for hash, info := range m.Devices {
|
for hash, info := range m.Devices {
|
||||||
info.Hash = hash
|
info.Hash = hash
|
||||||
|
devices.saveMetadata(info)
|
||||||
// If the transaction id is larger than the actual one we lost the device due to some crash
|
|
||||||
if info.TransactionId <= devices.TransactionId {
|
|
||||||
devices.saveMetadata(info)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err := os.Rename(devices.oldMetadataFile(), devices.oldMetadataFile()+".migrated"); err != nil {
|
if err := os.Rename(devices.oldMetadataFile(), devices.oldMetadataFile()+".migrated"); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -362,6 +484,149 @@ func (devices *DeviceSet) initMetaData() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) initMetaData() error {
|
||||||
|
if err := devices.migrateOldMetaData(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, transactionId, _, _, _, _, err := devices.poolStatus()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
devices.TransactionId = transactionId
|
||||||
|
|
||||||
|
if err := devices.constructDeviceIdMap(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := devices.processPendingTransaction(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) incNextDeviceId() {
|
||||||
|
// Ids are 24bit, so wrap around
|
||||||
|
devices.NextDeviceId = (devices.NextDeviceId + 1) & MaxDeviceId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) getNextFreeDeviceId() (int, error) {
|
||||||
|
devices.incNextDeviceId()
|
||||||
|
for i := 0; i <= MaxDeviceId; i++ {
|
||||||
|
if devices.isDeviceIdFree(devices.NextDeviceId) {
|
||||||
|
devices.markDeviceIdUsed(devices.NextDeviceId)
|
||||||
|
return devices.NextDeviceId, nil
|
||||||
|
}
|
||||||
|
devices.incNextDeviceId()
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, fmt.Errorf("Unable to find a free device Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) createRegisterDevice(hash string) (*DevInfo, error) {
|
||||||
|
deviceId, err := devices.getNextFreeDeviceId()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := devices.openTransaction(hash, deviceId); err != nil {
|
||||||
|
log.Debugf("Error opening transaction hash = %s deviceId = %d", hash, deviceId)
|
||||||
|
devices.markDeviceIdFree(deviceId)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if err := devicemapper.CreateDevice(devices.getPoolDevName(), deviceId); err != nil {
|
||||||
|
if devicemapper.DeviceIdExists(err) {
|
||||||
|
// Device Id already exists. This should not
|
||||||
|
// happen. Now we have a mechianism to find
|
||||||
|
// a free device Id. So something is not right.
|
||||||
|
// Give a warning and continue.
|
||||||
|
log.Errorf("Warning: Device Id %d exists in pool but it is supposed to be unused", deviceId)
|
||||||
|
deviceId, err = devices.getNextFreeDeviceId()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Save new device id into transaction
|
||||||
|
devices.refreshTransaction(deviceId)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Debugf("Error creating device: %s", err)
|
||||||
|
devices.markDeviceIdFree(deviceId)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Registering device (id %v) with FS size %v", deviceId, devices.baseFsSize)
|
||||||
|
info, err := devices.registerDevice(deviceId, hash, devices.baseFsSize, devices.OpenTransactionId)
|
||||||
|
if err != nil {
|
||||||
|
_ = devicemapper.DeleteDevice(devices.getPoolDevName(), deviceId)
|
||||||
|
devices.markDeviceIdFree(deviceId)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := devices.closeTransaction(); err != nil {
|
||||||
|
devices.unregisterDevice(deviceId, hash)
|
||||||
|
devicemapper.DeleteDevice(devices.getPoolDevName(), deviceId)
|
||||||
|
devices.markDeviceIdFree(deviceId)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *DevInfo) error {
|
||||||
|
deviceId, err := devices.getNextFreeDeviceId()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := devices.openTransaction(hash, deviceId); err != nil {
|
||||||
|
log.Debugf("Error opening transaction hash = %s deviceId = %d", hash, deviceId)
|
||||||
|
devices.markDeviceIdFree(deviceId)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if err := devicemapper.CreateSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil {
|
||||||
|
if devicemapper.DeviceIdExists(err) {
|
||||||
|
// Device Id already exists. This should not
|
||||||
|
// happen. Now we have a mechianism to find
|
||||||
|
// a free device Id. So something is not right.
|
||||||
|
// Give a warning and continue.
|
||||||
|
log.Errorf("Warning: Device Id %d exists in pool but it is supposed to be unused", deviceId)
|
||||||
|
deviceId, err = devices.getNextFreeDeviceId()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Save new device id into transaction
|
||||||
|
devices.refreshTransaction(deviceId)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Debugf("Error creating snap device: %s", err)
|
||||||
|
devices.markDeviceIdFree(deviceId)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size, devices.OpenTransactionId); err != nil {
|
||||||
|
devicemapper.DeleteDevice(devices.getPoolDevName(), deviceId)
|
||||||
|
devices.markDeviceIdFree(deviceId)
|
||||||
|
log.Debugf("Error registering device: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := devices.closeTransaction(); err != nil {
|
||||||
|
devices.unregisterDevice(deviceId, hash)
|
||||||
|
devicemapper.DeleteDevice(devices.getPoolDevName(), deviceId)
|
||||||
|
devices.markDeviceIdFree(deviceId)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) loadMetadata(hash string) *DevInfo {
|
func (devices *DeviceSet) loadMetadata(hash string) *DevInfo {
|
||||||
info := &DevInfo{Hash: hash, devices: devices}
|
info := &DevInfo{Hash: hash, devices: devices}
|
||||||
|
|
||||||
|
@ -374,11 +639,6 @@ func (devices *DeviceSet) loadMetadata(hash string) *DevInfo {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the transaction id is larger than the actual one we lost the device due to some crash
|
|
||||||
if info.TransactionId > devices.TransactionId {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,31 +650,35 @@ func (devices *DeviceSet) setupBaseImage() error {
|
||||||
|
|
||||||
if oldInfo != nil && !oldInfo.Initialized {
|
if oldInfo != nil && !oldInfo.Initialized {
|
||||||
log.Debugf("Removing uninitialized base image")
|
log.Debugf("Removing uninitialized base image")
|
||||||
if err := devices.deleteDevice(oldInfo); err != nil {
|
if err := devices.DeleteDevice(""); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Initializing base device-manager snapshot")
|
if devices.thinPoolDevice != "" && oldInfo == nil {
|
||||||
|
_, transactionId, dataUsed, _, _, _, err := devices.poolStatus()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if dataUsed != 0 {
|
||||||
|
return fmt.Errorf("Unable to take ownership of thin-pool (%s) that already has used data blocks",
|
||||||
|
devices.thinPoolDevice)
|
||||||
|
}
|
||||||
|
if transactionId != 0 {
|
||||||
|
return fmt.Errorf("Unable to take ownership of thin-pool (%s) with non-zero transaction Id",
|
||||||
|
devices.thinPoolDevice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
id := devices.nextDeviceId
|
log.Debugf("Initializing base device-mapper thin volume")
|
||||||
|
|
||||||
// Create initial device
|
// Create initial device
|
||||||
if err := createDevice(devices.getPoolDevName(), &id); err != nil {
|
info, err := devices.createRegisterDevice("")
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ids are 24bit, so wrap around
|
|
||||||
devices.nextDeviceId = (id + 1) & 0xffffff
|
|
||||||
|
|
||||||
log.Debugf("Registering base device (id %v) with FS size %v", id, devices.baseFsSize)
|
|
||||||
info, err := devices.registerDevice(id, "", devices.baseFsSize)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = deleteDevice(devices.getPoolDevName(), id)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Creating filesystem on base device-manager snapshot")
|
log.Debugf("Creating filesystem on base device-mapper thin volume")
|
||||||
|
|
||||||
if err = devices.activateDeviceIfNeeded(info); err != nil {
|
if err = devices.activateDeviceIfNeeded(info); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -447,11 +711,12 @@ func setCloseOnExec(name string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) log(level int, file string, line int, dmError int, message string) {
|
func (devices *DeviceSet) DMLog(level int, file string, line int, dmError int, message string) {
|
||||||
if level >= 7 {
|
if level >= 7 {
|
||||||
return // Ignore _LOG_DEBUG
|
return // Ignore _LOG_DEBUG
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME(vbatts) push this back into ./pkg/devicemapper/
|
||||||
log.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
|
log.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -489,7 +754,7 @@ func (devices *DeviceSet) ResizePool(size int64) error {
|
||||||
return fmt.Errorf("Can't shrink file")
|
return fmt.Errorf("Can't shrink file")
|
||||||
}
|
}
|
||||||
|
|
||||||
dataloopback := FindLoopDeviceFor(datafile)
|
dataloopback := devicemapper.FindLoopDeviceFor(datafile)
|
||||||
if dataloopback == nil {
|
if dataloopback == nil {
|
||||||
return fmt.Errorf("Unable to find loopback mount for: %s", datafilename)
|
return fmt.Errorf("Unable to find loopback mount for: %s", datafilename)
|
||||||
}
|
}
|
||||||
|
@ -501,7 +766,7 @@ func (devices *DeviceSet) ResizePool(size int64) error {
|
||||||
}
|
}
|
||||||
defer metadatafile.Close()
|
defer metadatafile.Close()
|
||||||
|
|
||||||
metadataloopback := FindLoopDeviceFor(metadatafile)
|
metadataloopback := devicemapper.FindLoopDeviceFor(metadatafile)
|
||||||
if metadataloopback == nil {
|
if metadataloopback == nil {
|
||||||
return fmt.Errorf("Unable to find loopback mount for: %s", metadatafilename)
|
return fmt.Errorf("Unable to find loopback mount for: %s", metadatafilename)
|
||||||
}
|
}
|
||||||
|
@ -513,32 +778,166 @@ func (devices *DeviceSet) ResizePool(size int64) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload size for loopback device
|
// Reload size for loopback device
|
||||||
if err := LoopbackSetCapacity(dataloopback); err != nil {
|
if err := devicemapper.LoopbackSetCapacity(dataloopback); err != nil {
|
||||||
return fmt.Errorf("Unable to update loopback capacity: %s", err)
|
return fmt.Errorf("Unable to update loopback capacity: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suspend the pool
|
// Suspend the pool
|
||||||
if err := suspendDevice(devices.getPoolName()); err != nil {
|
if err := devicemapper.SuspendDevice(devices.getPoolName()); err != nil {
|
||||||
return fmt.Errorf("Unable to suspend pool: %s", err)
|
return fmt.Errorf("Unable to suspend pool: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload with the new block sizes
|
// Reload with the new block sizes
|
||||||
if err := reloadPool(devices.getPoolName(), dataloopback, metadataloopback, devices.thinpBlockSize); err != nil {
|
if err := devicemapper.ReloadPool(devices.getPoolName(), dataloopback, metadataloopback, devices.thinpBlockSize); err != nil {
|
||||||
return fmt.Errorf("Unable to reload pool: %s", err)
|
return fmt.Errorf("Unable to reload pool: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resume the pool
|
// Resume the pool
|
||||||
if err := resumeDevice(devices.getPoolName()); err != nil {
|
if err := devicemapper.ResumeDevice(devices.getPoolName()); err != nil {
|
||||||
return fmt.Errorf("Unable to resume pool: %s", err)
|
return fmt.Errorf("Unable to resume pool: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) initDevmapper(doInit bool) error {
|
func (devices *DeviceSet) loadTransactionMetaData() error {
|
||||||
logInit(devices)
|
jsonData, err := ioutil.ReadFile(devices.transactionMetaFile())
|
||||||
|
if err != nil {
|
||||||
|
// There is no active transaction. This will be the case
|
||||||
|
// during upgrade.
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
devices.OpenTransactionId = devices.TransactionId
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
_, err := getDriverVersion()
|
json.Unmarshal(jsonData, &devices.Transaction)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) saveTransactionMetaData() error {
|
||||||
|
jsonData, err := json.Marshal(&devices.Transaction)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error encoding metadata to json: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices.writeMetaFile(jsonData, devices.transactionMetaFile())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) removeTransactionMetaData() error {
|
||||||
|
if err := os.RemoveAll(devices.transactionMetaFile()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) rollbackTransaction() error {
|
||||||
|
log.Debugf("Rolling back open transaction: TransactionId=%d hash=%s device_id=%d", devices.OpenTransactionId, devices.DeviceIdHash, devices.DeviceId)
|
||||||
|
|
||||||
|
// A device id might have already been deleted before transaction
|
||||||
|
// closed. In that case this call will fail. Just leave a message
|
||||||
|
// in case of failure.
|
||||||
|
if err := devicemapper.DeleteDevice(devices.getPoolDevName(), devices.DeviceId); err != nil {
|
||||||
|
log.Errorf("Warning: Unable to delete device: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dinfo := &DevInfo{Hash: devices.DeviceIdHash}
|
||||||
|
if err := devices.removeMetadata(dinfo); err != nil {
|
||||||
|
log.Errorf("Warning: Unable to remove meta data: %s", err)
|
||||||
|
} else {
|
||||||
|
devices.markDeviceIdFree(devices.DeviceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := devices.removeTransactionMetaData(); err != nil {
|
||||||
|
log.Errorf("Warning: Unable to remove transaction meta file %s: %s", devices.transactionMetaFile(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) processPendingTransaction() error {
|
||||||
|
if err := devices.loadTransactionMetaData(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there was open transaction but pool transaction Id is same
|
||||||
|
// as open transaction Id, nothing to roll back.
|
||||||
|
if devices.TransactionId == devices.OpenTransactionId {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If open transaction Id is less than pool transaction Id, something
|
||||||
|
// is wrong. Bail out.
|
||||||
|
if devices.OpenTransactionId < devices.TransactionId {
|
||||||
|
log.Errorf("Warning: Open Transaction id %d is less than pool transaction id %d", devices.OpenTransactionId, devices.TransactionId)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pool transaction Id is not same as open transaction. There is
|
||||||
|
// a transaction which was not completed.
|
||||||
|
if err := devices.rollbackTransaction(); err != nil {
|
||||||
|
return fmt.Errorf("Rolling back open transaction failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
devices.OpenTransactionId = devices.TransactionId
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) loadDeviceSetMetaData() error {
|
||||||
|
jsonData, err := ioutil.ReadFile(devices.deviceSetMetaFile())
|
||||||
|
if err != nil {
|
||||||
|
// For backward compatibility return success if file does
|
||||||
|
// not exist.
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(jsonData, devices)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) saveDeviceSetMetaData() error {
|
||||||
|
jsonData, err := json.Marshal(devices)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error encoding metadata to json: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices.writeMetaFile(jsonData, devices.deviceSetMetaFile())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) openTransaction(hash string, DeviceId int) error {
|
||||||
|
devices.allocateTransactionId()
|
||||||
|
devices.DeviceIdHash = hash
|
||||||
|
devices.DeviceId = DeviceId
|
||||||
|
if err := devices.saveTransactionMetaData(); err != nil {
|
||||||
|
return fmt.Errorf("Error saving transaction meta data: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) refreshTransaction(DeviceId int) error {
|
||||||
|
devices.DeviceId = DeviceId
|
||||||
|
if err := devices.saveTransactionMetaData(); err != nil {
|
||||||
|
return fmt.Errorf("Error saving transaction meta data: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) closeTransaction() error {
|
||||||
|
if err := devices.updatePoolTransactionId(); err != nil {
|
||||||
|
log.Debugf("Failed to close Transaction")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (devices *DeviceSet) initDevmapper(doInit bool) error {
|
||||||
|
// give ourselves to libdm as a log handler
|
||||||
|
devicemapper.LogInit(devices)
|
||||||
|
|
||||||
|
_, err := devicemapper.GetDriverVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Can't even get driver version, assume not supported
|
// Can't even get driver version, assume not supported
|
||||||
return graphdriver.ErrNotSupported
|
return graphdriver.ErrNotSupported
|
||||||
|
@ -564,11 +963,11 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
|
||||||
devices.devicePrefix = fmt.Sprintf("docker-%d:%d-%d", major(sysSt.Dev), minor(sysSt.Dev), sysSt.Ino)
|
devices.devicePrefix = fmt.Sprintf("docker-%d:%d-%d", major(sysSt.Dev), minor(sysSt.Dev), sysSt.Ino)
|
||||||
log.Debugf("Generated prefix: %s", devices.devicePrefix)
|
log.Debugf("Generated prefix: %s", devices.devicePrefix)
|
||||||
|
|
||||||
// Check for the existence of the device <prefix>-pool
|
// Check for the existence of the thin-pool device
|
||||||
log.Debugf("Checking for existence of the pool '%s'", devices.getPoolName())
|
log.Debugf("Checking for existence of the pool '%s'", devices.getPoolName())
|
||||||
info, err := getInfo(devices.getPoolName())
|
info, err := devicemapper.GetInfo(devices.getPoolName())
|
||||||
if info == nil {
|
if info == nil {
|
||||||
log.Debugf("Error device getInfo: %s", err)
|
log.Debugf("Error device devicemapper.GetInfo: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -583,7 +982,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
|
||||||
createdLoopback := false
|
createdLoopback := false
|
||||||
|
|
||||||
// If the pool doesn't exist, create it
|
// If the pool doesn't exist, create it
|
||||||
if info.Exists == 0 {
|
if info.Exists == 0 && devices.thinPoolDevice == "" {
|
||||||
log.Debugf("Pool doesn't exist. Creating it.")
|
log.Debugf("Pool doesn't exist. Creating it.")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -610,7 +1009,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dataFile, err = attachLoopDevice(data)
|
dataFile, err = devicemapper.AttachLoopDevice(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -641,7 +1040,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
metadataFile, err = attachLoopDevice(metadata)
|
metadataFile, err = devicemapper.AttachLoopDevice(metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -653,7 +1052,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
|
||||||
}
|
}
|
||||||
defer metadataFile.Close()
|
defer metadataFile.Close()
|
||||||
|
|
||||||
if err := createPool(devices.getPoolName(), dataFile, metadataFile, devices.thinpBlockSize); err != nil {
|
if err := devicemapper.CreatePool(devices.getPoolName(), dataFile, metadataFile, devices.thinpBlockSize); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -666,6 +1065,12 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Right now this loads only NextDeviceId. If there is more metatadata
|
||||||
|
// down the line, we might have to move it earlier.
|
||||||
|
if err = devices.loadDeviceSetMetaData(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Setup the base image
|
// Setup the base image
|
||||||
if doInit {
|
if doInit {
|
||||||
if err := devices.setupBaseImage(); err != nil {
|
if err := devices.setupBaseImage(); err != nil {
|
||||||
|
@ -678,6 +1083,9 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) AddDevice(hash, baseHash string) error {
|
func (devices *DeviceSet) AddDevice(hash, baseHash string) error {
|
||||||
|
log.Debugf("[deviceset] AddDevice() hash=%s basehash=%s", hash, baseHash)
|
||||||
|
defer log.Debugf("[deviceset] AddDevice END")
|
||||||
|
|
||||||
baseInfo, err := devices.lookupDevice(baseHash)
|
baseInfo, err := devices.lookupDevice(baseHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -693,21 +1101,10 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error {
|
||||||
return fmt.Errorf("device %s already exists", hash)
|
return fmt.Errorf("device %s already exists", hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
deviceId := devices.nextDeviceId
|
if err := devices.createRegisterSnapDevice(hash, baseInfo); err != nil {
|
||||||
|
|
||||||
if err := createSnapDevice(devices.getPoolDevName(), &deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil {
|
|
||||||
log.Debugf("Error creating snap device: %s", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ids are 24bit, so wrap around
|
|
||||||
devices.nextDeviceId = (deviceId + 1) & 0xffffff
|
|
||||||
|
|
||||||
if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size); err != nil {
|
|
||||||
deleteDevice(devices.getPoolDevName(), deviceId)
|
|
||||||
log.Debugf("Error registering device: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -717,13 +1114,13 @@ func (devices *DeviceSet) deleteDevice(info *DevInfo) error {
|
||||||
// on the thin pool when we remove a thinp device, so we do it
|
// on the thin pool when we remove a thinp device, so we do it
|
||||||
// manually
|
// manually
|
||||||
if err := devices.activateDeviceIfNeeded(info); err == nil {
|
if err := devices.activateDeviceIfNeeded(info); err == nil {
|
||||||
if err := BlockDeviceDiscard(info.DevName()); err != nil {
|
if err := devicemapper.BlockDeviceDiscard(info.DevName()); err != nil {
|
||||||
log.Debugf("Error discarding block on device: %s (ignoring)", err)
|
log.Debugf("Error discarding block on device: %s (ignoring)", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
devinfo, _ := getInfo(info.Name())
|
devinfo, _ := devicemapper.GetInfo(info.Name())
|
||||||
if devinfo != nil && devinfo.Exists != 0 {
|
if devinfo != nil && devinfo.Exists != 0 {
|
||||||
if err := devices.removeDeviceAndWait(info.Name()); err != nil {
|
if err := devices.removeDeviceAndWait(info.Name()); err != nil {
|
||||||
log.Debugf("Error removing device: %s", err)
|
log.Debugf("Error removing device: %s", err)
|
||||||
|
@ -731,24 +1128,26 @@ func (devices *DeviceSet) deleteDevice(info *DevInfo) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deleteDevice(devices.getPoolDevName(), info.DeviceId); err != nil {
|
if err := devices.openTransaction(info.Hash, info.DeviceId); err != nil {
|
||||||
|
log.Debugf("Error opening transaction hash = %s deviceId = %d", "", info.DeviceId)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := devicemapper.DeleteDevice(devices.getPoolDevName(), info.DeviceId); err != nil {
|
||||||
log.Debugf("Error deleting device: %s", err)
|
log.Debugf("Error deleting device: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
devices.allocateTransactionId()
|
if err := devices.unregisterDevice(info.DeviceId, info.Hash); err != nil {
|
||||||
devices.devicesLock.Lock()
|
|
||||||
delete(devices.Devices, info.Hash)
|
|
||||||
devices.devicesLock.Unlock()
|
|
||||||
|
|
||||||
if err := devices.removeMetadata(info); err != nil {
|
|
||||||
devices.devicesLock.Lock()
|
|
||||||
devices.Devices[info.Hash] = info
|
|
||||||
devices.devicesLock.Unlock()
|
|
||||||
log.Debugf("Error removing meta data: %s", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := devices.closeTransaction(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
devices.markDeviceIdFree(info.DeviceId)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -771,12 +1170,17 @@ func (devices *DeviceSet) deactivatePool() error {
|
||||||
log.Debugf("[devmapper] deactivatePool()")
|
log.Debugf("[devmapper] deactivatePool()")
|
||||||
defer log.Debugf("[devmapper] deactivatePool END")
|
defer log.Debugf("[devmapper] deactivatePool END")
|
||||||
devname := devices.getPoolDevName()
|
devname := devices.getPoolDevName()
|
||||||
devinfo, err := getInfo(devname)
|
|
||||||
|
devinfo, err := devicemapper.GetInfo(devname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if d, err := devicemapper.GetDeps(devname); err == nil {
|
||||||
|
// Access to more Debug output
|
||||||
|
log.Debugf("[devmapper] devicemapper.GetDeps() %s: %#v", devname, d)
|
||||||
|
}
|
||||||
if devinfo.Exists != 0 {
|
if devinfo.Exists != 0 {
|
||||||
return removeDevice(devname)
|
return devicemapper.RemoveDevice(devname)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -792,7 +1196,7 @@ func (devices *DeviceSet) deactivateDevice(info *DevInfo) error {
|
||||||
log.Errorf("Warning: error waiting for device %s to close: %s", info.Hash, err)
|
log.Errorf("Warning: error waiting for device %s to close: %s", info.Hash, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
devinfo, err := getInfo(info.Name())
|
devinfo, err := devicemapper.GetInfo(info.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -811,11 +1215,11 @@ func (devices *DeviceSet) removeDeviceAndWait(devname string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
err = removeDevice(devname)
|
err = devicemapper.RemoveDevice(devname)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err != ErrBusy {
|
if err != devicemapper.ErrBusy {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -843,7 +1247,7 @@ func (devices *DeviceSet) waitRemove(devname string) error {
|
||||||
defer log.Debugf("[deviceset %s] waitRemove(%s) END", devices.devicePrefix, devname)
|
defer log.Debugf("[deviceset %s] waitRemove(%s) END", devices.devicePrefix, devname)
|
||||||
i := 0
|
i := 0
|
||||||
for ; i < 1000; i++ {
|
for ; i < 1000; i++ {
|
||||||
devinfo, err := getInfo(devname)
|
devinfo, err := devicemapper.GetInfo(devname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If there is an error we assume the device doesn't exist.
|
// If there is an error we assume the device doesn't exist.
|
||||||
// The error might actually be something else, but we can't differentiate.
|
// The error might actually be something else, but we can't differentiate.
|
||||||
|
@ -872,7 +1276,7 @@ func (devices *DeviceSet) waitRemove(devname string) error {
|
||||||
func (devices *DeviceSet) waitClose(info *DevInfo) error {
|
func (devices *DeviceSet) waitClose(info *DevInfo) error {
|
||||||
i := 0
|
i := 0
|
||||||
for ; i < 1000; i++ {
|
for ; i < 1000; i++ {
|
||||||
devinfo, err := getInfo(info.Name())
|
devinfo, err := devicemapper.GetInfo(info.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -893,7 +1297,6 @@ func (devices *DeviceSet) waitClose(info *DevInfo) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (devices *DeviceSet) Shutdown() error {
|
func (devices *DeviceSet) Shutdown() error {
|
||||||
|
|
||||||
log.Debugf("[deviceset %s] shutdown()", devices.devicePrefix)
|
log.Debugf("[deviceset %s] shutdown()", devices.devicePrefix)
|
||||||
log.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root)
|
log.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root)
|
||||||
defer log.Debugf("[deviceset %s] shutdown END", devices.devicePrefix)
|
defer log.Debugf("[deviceset %s] shutdown END", devices.devicePrefix)
|
||||||
|
@ -937,9 +1340,13 @@ func (devices *DeviceSet) Shutdown() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
devices.Lock()
|
devices.Lock()
|
||||||
if err := devices.deactivatePool(); err != nil {
|
if devices.thinPoolDevice == "" {
|
||||||
log.Debugf("Shutdown deactivate pool , error: %s", err)
|
if err := devices.deactivatePool(); err != nil {
|
||||||
|
log.Debugf("Shutdown deactivate pool , error: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
devices.saveDeviceSetMetaData()
|
||||||
devices.Unlock()
|
devices.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1060,7 +1467,7 @@ func (devices *DeviceSet) HasActivatedDevice(hash string) bool {
|
||||||
devices.Lock()
|
devices.Lock()
|
||||||
defer devices.Unlock()
|
defer devices.Unlock()
|
||||||
|
|
||||||
devinfo, _ := getInfo(info.Name())
|
devinfo, _ := devicemapper.GetInfo(info.Name())
|
||||||
return devinfo != nil && devinfo.Exists != 0
|
return devinfo != nil && devinfo.Exists != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1082,7 +1489,7 @@ func (devices *DeviceSet) List() []string {
|
||||||
|
|
||||||
func (devices *DeviceSet) deviceStatus(devName string) (sizeInSectors, mappedSectors, highestMappedSector uint64, err error) {
|
func (devices *DeviceSet) deviceStatus(devName string) (sizeInSectors, mappedSectors, highestMappedSector uint64, err error) {
|
||||||
var params string
|
var params string
|
||||||
_, sizeInSectors, _, params, err = getStatus(devName)
|
_, sizeInSectors, _, params, err = devicemapper.GetStatus(devName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1127,7 +1534,7 @@ func (devices *DeviceSet) GetDeviceStatus(hash string) (*DevStatus, error) {
|
||||||
|
|
||||||
func (devices *DeviceSet) poolStatus() (totalSizeInSectors, transactionId, dataUsed, dataTotal, metadataUsed, metadataTotal uint64, err error) {
|
func (devices *DeviceSet) poolStatus() (totalSizeInSectors, transactionId, dataUsed, dataTotal, metadataUsed, metadataTotal uint64, err error) {
|
||||||
var params string
|
var params string
|
||||||
if _, totalSizeInSectors, _, params, err = getStatus(devices.getPoolName()); err == nil {
|
if _, totalSizeInSectors, _, params, err = devicemapper.GetStatus(devices.getPoolName()); err == nil {
|
||||||
_, err = fmt.Sscanf(params, "%d %d/%d %d/%d", &transactionId, &metadataUsed, &metadataTotal, &dataUsed, &dataTotal)
|
_, err = fmt.Sscanf(params, "%d %d/%d %d/%d", &transactionId, &metadataUsed, &metadataTotal, &dataUsed, &dataTotal)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -1170,7 +1577,7 @@ func (devices *DeviceSet) Status() *Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error) {
|
func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error) {
|
||||||
SetDevDir("/dev")
|
devicemapper.SetDevDir("/dev")
|
||||||
|
|
||||||
devices := &DeviceSet{
|
devices := &DeviceSet{
|
||||||
root: root,
|
root: root,
|
||||||
|
@ -1181,6 +1588,7 @@ func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error
|
||||||
filesystem: "ext4",
|
filesystem: "ext4",
|
||||||
doBlkDiscard: true,
|
doBlkDiscard: true,
|
||||||
thinpBlockSize: DefaultThinpBlockSize,
|
thinpBlockSize: DefaultThinpBlockSize,
|
||||||
|
deviceIdMap: make([]byte, DeviceIdMapSz),
|
||||||
}
|
}
|
||||||
|
|
||||||
foundBlkDiscard := false
|
foundBlkDiscard := false
|
||||||
|
@ -1222,6 +1630,8 @@ func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error
|
||||||
devices.metadataDevice = val
|
devices.metadataDevice = val
|
||||||
case "dm.datadev":
|
case "dm.datadev":
|
||||||
devices.dataDevice = val
|
devices.dataDevice = val
|
||||||
|
case "dm.thinpooldev":
|
||||||
|
devices.thinPoolDevice = strings.TrimPrefix(val, "/dev/mapper/")
|
||||||
case "dm.blkdiscard":
|
case "dm.blkdiscard":
|
||||||
foundBlkDiscard = true
|
foundBlkDiscard = true
|
||||||
devices.doBlkDiscard, err = strconv.ParseBool(val)
|
devices.doBlkDiscard, err = strconv.ParseBool(val)
|
||||||
|
@ -1241,7 +1651,7 @@ func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error
|
||||||
}
|
}
|
||||||
|
|
||||||
// By default, don't do blk discard hack on raw devices, its rarely useful and is expensive
|
// By default, don't do blk discard hack on raw devices, its rarely useful and is expensive
|
||||||
if !foundBlkDiscard && devices.dataDevice != "" {
|
if !foundBlkDiscard && (devices.dataDevice != "" || devices.thinPoolDevice != "") {
|
||||||
devices.doBlkDiscard = false
|
devices.doBlkDiscard = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
package devmapper
|
package devmapper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/docker/daemon/graphdriver/graphtest"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/daemon/graphdriver/graphtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -12,6 +13,9 @@ func init() {
|
||||||
DefaultDataLoopbackSize = 300 * 1024 * 1024
|
DefaultDataLoopbackSize = 300 * 1024 * 1024
|
||||||
DefaultMetaDataLoopbackSize = 200 * 1024 * 1024
|
DefaultMetaDataLoopbackSize = 200 * 1024 * 1024
|
||||||
DefaultBaseFsSize = 300 * 1024 * 1024
|
DefaultBaseFsSize = 300 * 1024 * 1024
|
||||||
|
if err := graphtest.InitLoopbacks(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This avoids creating a new driver for each test if all tests are run
|
// This avoids creating a new driver for each test if all tests are run
|
||||||
|
|
|
@ -8,8 +8,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/daemon/graphdriver"
|
"github.com/docker/docker/daemon/graphdriver"
|
||||||
"github.com/docker/docker/pkg/log"
|
"github.com/docker/docker/pkg/devicemapper"
|
||||||
"github.com/docker/docker/pkg/mount"
|
"github.com/docker/docker/pkg/mount"
|
||||||
"github.com/docker/docker/pkg/units"
|
"github.com/docker/docker/pkg/units"
|
||||||
)
|
)
|
||||||
|
@ -34,7 +35,7 @@ func Init(home string, options []string) (graphdriver.Driver, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := graphdriver.MakePrivate(home); err != nil {
|
if err := mount.MakePrivate(home); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +64,7 @@ func (d *Driver) Status() [][2]string {
|
||||||
{"Metadata Space Used", fmt.Sprintf("%s", units.HumanSize(int64(s.Metadata.Used)))},
|
{"Metadata Space Used", fmt.Sprintf("%s", units.HumanSize(int64(s.Metadata.Used)))},
|
||||||
{"Metadata Space Total", fmt.Sprintf("%s", units.HumanSize(int64(s.Metadata.Total)))},
|
{"Metadata Space Total", fmt.Sprintf("%s", units.HumanSize(int64(s.Metadata.Total)))},
|
||||||
}
|
}
|
||||||
if vStr, err := GetLibraryVersion(); err == nil {
|
if vStr, err := devicemapper.GetLibraryVersion(); err == nil {
|
||||||
status = append(status, [2]string{"Library Version", vStr})
|
status = append(status, [2]string{"Library Version", vStr})
|
||||||
}
|
}
|
||||||
return status
|
return status
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/mount"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type FsMagic uint64
|
type FsMagic uint64
|
||||||
|
@ -81,6 +80,8 @@ var (
|
||||||
"btrfs",
|
"btrfs",
|
||||||
"devicemapper",
|
"devicemapper",
|
||||||
"vfs",
|
"vfs",
|
||||||
|
// experimental, has to be enabled manually for now
|
||||||
|
"overlay",
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrNotSupported = errors.New("driver not supported")
|
ErrNotSupported = errors.New("driver not supported")
|
||||||
|
@ -139,18 +140,3 @@ func New(root string, options []string) (driver Driver, err error) {
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("No supported storage backend found")
|
return nil, fmt.Errorf("No supported storage backend found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakePrivate(mountPoint string) error {
|
|
||||||
mounted, err := mount.Mounted(mountPoint)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !mounted {
|
|
||||||
if err := mount.Mount(mountPoint, mountPoint, "none", "bind,rw"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mount.ForceMount("", mountPoint, "none", "private")
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
|
// +build daemon
|
||||||
|
|
||||||
package graphdriver
|
package graphdriver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/chrootarchive"
|
"github.com/docker/docker/pkg/chrootarchive"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package graphtest
|
package graphtest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -20,6 +21,46 @@ type Driver struct {
|
||||||
refCount int
|
refCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitLoopbacks ensures that the loopback devices are properly created within
|
||||||
|
// the system running the device mapper tests.
|
||||||
|
func InitLoopbacks() error {
|
||||||
|
stat_t, err := getBaseLoopStats()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// create atleast 8 loopback files, ya, that is a good number
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
loopPath := fmt.Sprintf("/dev/loop%d", i)
|
||||||
|
// only create new loopback files if they don't exist
|
||||||
|
if _, err := os.Stat(loopPath); err != nil {
|
||||||
|
if mkerr := syscall.Mknod(loopPath,
|
||||||
|
uint32(stat_t.Mode|syscall.S_IFBLK), int((7<<8)|(i&0xff)|((i&0xfff00)<<12))); mkerr != nil {
|
||||||
|
return mkerr
|
||||||
|
}
|
||||||
|
os.Chown(loopPath, int(stat_t.Uid), int(stat_t.Gid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBaseLoopStats inspects /dev/loop0 to collect uid,gid, and mode for the
|
||||||
|
// loop0 device on the system. If it does not exist we assume 0,0,0660 for the
|
||||||
|
// stat data
|
||||||
|
func getBaseLoopStats() (*syscall.Stat_t, error) {
|
||||||
|
loop0, err := os.Stat("/dev/loop0")
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return &syscall.Stat_t{
|
||||||
|
Uid: 0,
|
||||||
|
Gid: 0,
|
||||||
|
Mode: 0660,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return loop0.Sys().(*syscall.Stat_t), nil
|
||||||
|
}
|
||||||
|
|
||||||
func newDriver(t *testing.T, name string) *Driver {
|
func newDriver(t *testing.T, name string) *Driver {
|
||||||
root, err := ioutil.TempDir("/var/tmp", "docker-graphtest-")
|
root, err := ioutil.TempDir("/var/tmp", "docker-graphtest-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -33,7 +74,7 @@ func newDriver(t *testing.T, name string) *Driver {
|
||||||
d, err := graphdriver.GetDriver(name, root, nil)
|
d, err := graphdriver.GetDriver(name, root, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == graphdriver.ErrNotSupported || err == graphdriver.ErrPrerequisites {
|
if err == graphdriver.ErrNotSupported || err == graphdriver.ErrPrerequisites {
|
||||||
t.Skip("Driver %s not supported", name)
|
t.Skipf("Driver %s not supported", name)
|
||||||
}
|
}
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package overlay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CopyFlags int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CopyHardlink CopyFlags = 1 << iota
|
||||||
|
)
|
||||||
|
|
||||||
|
func copyRegular(srcPath, dstPath string, mode os.FileMode) error {
|
||||||
|
srcFile, err := os.Open(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcFile.Close()
|
||||||
|
|
||||||
|
dstFile, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE, mode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dstFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(dstFile, srcFile)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyXattr(srcPath, dstPath, attr string) error {
|
||||||
|
data, err := system.Lgetxattr(srcPath, attr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if data != nil {
|
||||||
|
if err := system.Lsetxattr(dstPath, attr, data, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDir(srcDir, dstDir string, flags CopyFlags) error {
|
||||||
|
err := filepath.Walk(srcDir, func(srcPath string, f os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebase path
|
||||||
|
relPath, err := filepath.Rel(srcDir, srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dstPath := filepath.Join(dstDir, relPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, ok := f.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Unable to get raw syscall.Stat_t data for %s", srcPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch f.Mode() & os.ModeType {
|
||||||
|
case 0: // Regular file
|
||||||
|
if flags&CopyHardlink != 0 {
|
||||||
|
if err := os.Link(srcPath, dstPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := copyRegular(srcPath, dstPath, f.Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case os.ModeDir:
|
||||||
|
if err := os.Mkdir(dstPath, f.Mode()); err != nil && !os.IsExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case os.ModeSymlink:
|
||||||
|
link, err := os.Readlink(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Symlink(link, dstPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case os.ModeNamedPipe:
|
||||||
|
fallthrough
|
||||||
|
case os.ModeSocket:
|
||||||
|
if err := syscall.Mkfifo(dstPath, stat.Mode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
case os.ModeDevice:
|
||||||
|
if err := syscall.Mknod(dstPath, stat.Mode, int(stat.Rdev)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unknown file type for %s\n", srcPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Lchown(dstPath, int(stat.Uid), int(stat.Gid)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyXattr(srcPath, dstPath, "security.capability"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to copy this attribute if it appears in an overlay upper layer, as
|
||||||
|
// this function is used to copy those. It is set by overlay if a directory
|
||||||
|
// is removed and then re-created and should not inherit anything from the
|
||||||
|
// same dir in the lower dir.
|
||||||
|
if err := copyXattr(srcPath, dstPath, "trusted.overlay.opaque"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
isSymlink := f.Mode()&os.ModeSymlink != 0
|
||||||
|
|
||||||
|
// There is no LChmod, so ignore mode for symlink. Also, this
|
||||||
|
// must happen after chown, as that can modify the file mode
|
||||||
|
if !isSymlink {
|
||||||
|
if err := os.Chmod(dstPath, f.Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := []syscall.Timespec{stat.Atim, stat.Mtim}
|
||||||
|
// syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and
|
||||||
|
if !isSymlink {
|
||||||
|
if err := system.UtimesNano(dstPath, ts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := system.LUtimesNano(dstPath, ts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,370 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package overlay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/daemon/graphdriver"
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
|
"github.com/docker/docker/pkg/chrootarchive"
|
||||||
|
"github.com/docker/libcontainer/label"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a small wrapper over the NaiveDiffWriter that lets us have a custom
|
||||||
|
// implementation of ApplyDiff()
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrApplyDiffFallback = fmt.Errorf("Fall back to normal ApplyDiff")
|
||||||
|
)
|
||||||
|
|
||||||
|
type ApplyDiffProtoDriver interface {
|
||||||
|
graphdriver.ProtoDriver
|
||||||
|
ApplyDiff(id, parent string, diff archive.ArchiveReader) (bytes int64, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type naiveDiffDriverWithApply struct {
|
||||||
|
graphdriver.Driver
|
||||||
|
applyDiff ApplyDiffProtoDriver
|
||||||
|
}
|
||||||
|
|
||||||
|
func NaiveDiffDriverWithApply(driver ApplyDiffProtoDriver) graphdriver.Driver {
|
||||||
|
return &naiveDiffDriverWithApply{
|
||||||
|
Driver: graphdriver.NaiveDiffDriver(driver),
|
||||||
|
applyDiff: driver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *naiveDiffDriverWithApply) ApplyDiff(id, parent string, diff archive.ArchiveReader) (int64, error) {
|
||||||
|
b, err := d.applyDiff.ApplyDiff(id, parent, diff)
|
||||||
|
if err == ErrApplyDiffFallback {
|
||||||
|
return d.Driver.ApplyDiff(id, parent, diff)
|
||||||
|
}
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// This backend uses the overlay union filesystem for containers
|
||||||
|
// plus hard link file sharing for images.
|
||||||
|
|
||||||
|
// Each container/image can have a "root" subdirectory which is a plain
|
||||||
|
// filesystem hierarchy, or they can use overlay.
|
||||||
|
|
||||||
|
// If they use overlay there is a "upper" directory and a "lower-id"
|
||||||
|
// file, as well as "merged" and "work" directories. The "upper"
|
||||||
|
// directory has the upper layer of the overlay, and "lower-id" contains
|
||||||
|
// the id of the parent whose "root" directory shall be used as the lower
|
||||||
|
// layer in the overlay. The overlay itself is mounted in the "merged"
|
||||||
|
// directory, and the "work" dir is needed for overlay to work.
|
||||||
|
|
||||||
|
// When a overlay layer is created there are two cases, either the
|
||||||
|
// parent has a "root" dir, then we start out with a empty "upper"
|
||||||
|
// directory overlaid on the parents root. This is typically the
|
||||||
|
// case with the init layer of a container which is based on an image.
|
||||||
|
// If there is no "root" in the parent, we inherit the lower-id from
|
||||||
|
// the parent and start by making a copy if the parents "upper" dir.
|
||||||
|
// This is typically the case for a container layer which copies
|
||||||
|
// its parent -init upper layer.
|
||||||
|
|
||||||
|
// Additionally we also have a custom implementation of ApplyLayer
|
||||||
|
// which makes a recursive copy of the parent "root" layer using
|
||||||
|
// hardlinks to share file data, and then applies the layer on top
|
||||||
|
// of that. This means all child images share file (but not directory)
|
||||||
|
// data with the parent.
|
||||||
|
|
||||||
|
type ActiveMount struct {
|
||||||
|
count int
|
||||||
|
path string
|
||||||
|
mounted bool
|
||||||
|
}
|
||||||
|
type Driver struct {
|
||||||
|
home string
|
||||||
|
sync.Mutex // Protects concurrent modification to active
|
||||||
|
active map[string]*ActiveMount
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
graphdriver.Register("overlay", Init)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(home string, options []string) (graphdriver.Driver, error) {
|
||||||
|
if err := supportsOverlay(); err != nil {
|
||||||
|
return nil, graphdriver.ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the driver home dir
|
||||||
|
if err := os.MkdirAll(home, 0755); err != nil && !os.IsExist(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
d := &Driver{
|
||||||
|
home: home,
|
||||||
|
active: make(map[string]*ActiveMount),
|
||||||
|
}
|
||||||
|
|
||||||
|
return NaiveDiffDriverWithApply(d), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func supportsOverlay() error {
|
||||||
|
// We can try to modprobe overlay first before looking at
|
||||||
|
// proc/filesystems for when overlay is supported
|
||||||
|
exec.Command("modprobe", "overlay").Run()
|
||||||
|
|
||||||
|
f, err := os.Open("/proc/filesystems")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
s := bufio.NewScanner(f)
|
||||||
|
for s.Scan() {
|
||||||
|
if s.Text() == "nodev\toverlay" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Error("'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.")
|
||||||
|
return graphdriver.ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) String() string {
|
||||||
|
return "overlay"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) Status() [][2]string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) Cleanup() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) Create(id string, parent string) (retErr error) {
|
||||||
|
dir := d.dir(id)
|
||||||
|
if err := os.MkdirAll(path.Dir(dir), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.Mkdir(dir, 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// Clean up on failure
|
||||||
|
if retErr != nil {
|
||||||
|
os.RemoveAll(dir)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Toplevel images are just a "root" dir
|
||||||
|
if parent == "" {
|
||||||
|
if err := os.Mkdir(path.Join(dir, "root"), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parentDir := d.dir(parent)
|
||||||
|
|
||||||
|
// Ensure parent exists
|
||||||
|
if _, err := os.Lstat(parentDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If parent has a root, just do a overlay to it
|
||||||
|
parentRoot := path.Join(parentDir, "root")
|
||||||
|
|
||||||
|
if s, err := os.Lstat(parentRoot); err == nil {
|
||||||
|
if err := os.Mkdir(path.Join(dir, "upper"), s.Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.Mkdir(path.Join(dir, "work"), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.Mkdir(path.Join(dir, "merged"), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(path.Join(dir, "lower-id"), []byte(parent), 0666); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, copy the upper and the lower-id from the parent
|
||||||
|
|
||||||
|
lowerId, err := ioutil.ReadFile(path.Join(parentDir, "lower-id"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(path.Join(dir, "lower-id"), lowerId, 0666); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
parentUpperDir := path.Join(parentDir, "upper")
|
||||||
|
s, err := os.Lstat(parentUpperDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
upperDir := path.Join(dir, "upper")
|
||||||
|
if err := os.Mkdir(upperDir, s.Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.Mkdir(path.Join(dir, "work"), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.Mkdir(path.Join(dir, "merged"), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyDir(parentUpperDir, upperDir, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) dir(id string) string {
|
||||||
|
return path.Join(d.home, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) Remove(id string) error {
|
||||||
|
dir := d.dir(id)
|
||||||
|
if _, err := os.Stat(dir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.RemoveAll(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) Get(id string, mountLabel string) (string, error) {
|
||||||
|
// Protect the d.active from concurrent access
|
||||||
|
d.Lock()
|
||||||
|
defer d.Unlock()
|
||||||
|
|
||||||
|
mount := d.active[id]
|
||||||
|
if mount != nil {
|
||||||
|
mount.count++
|
||||||
|
return mount.path, nil
|
||||||
|
} else {
|
||||||
|
mount = &ActiveMount{count: 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := d.dir(id)
|
||||||
|
if _, err := os.Stat(dir); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If id has a root, just return it
|
||||||
|
rootDir := path.Join(dir, "root")
|
||||||
|
if _, err := os.Stat(rootDir); err == nil {
|
||||||
|
mount.path = rootDir
|
||||||
|
d.active[id] = mount
|
||||||
|
return mount.path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lowerId, err := ioutil.ReadFile(path.Join(dir, "lower-id"))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
lowerDir := path.Join(d.dir(string(lowerId)), "root")
|
||||||
|
upperDir := path.Join(dir, "upper")
|
||||||
|
workDir := path.Join(dir, "work")
|
||||||
|
mergedDir := path.Join(dir, "merged")
|
||||||
|
|
||||||
|
opts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lowerDir, upperDir, workDir)
|
||||||
|
if err := syscall.Mount("overlay", mergedDir, "overlay", 0, label.FormatMountLabel(opts, mountLabel)); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
mount.path = mergedDir
|
||||||
|
mount.mounted = true
|
||||||
|
d.active[id] = mount
|
||||||
|
|
||||||
|
return mount.path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) Put(id string) {
|
||||||
|
// Protect the d.active from concurrent access
|
||||||
|
d.Lock()
|
||||||
|
defer d.Unlock()
|
||||||
|
|
||||||
|
mount := d.active[id]
|
||||||
|
if mount == nil {
|
||||||
|
log.Debugf("Put on a non-mounted device %s", id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mount.count--
|
||||||
|
if mount.count > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if mount.mounted {
|
||||||
|
if err := syscall.Unmount(mount.path, 0); err != nil {
|
||||||
|
log.Debugf("Failed to unmount %s overlay: %v", id, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(d.active, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) ApplyDiff(id string, parent string, diff archive.ArchiveReader) (bytes int64, err error) {
|
||||||
|
dir := d.dir(id)
|
||||||
|
|
||||||
|
if parent == "" {
|
||||||
|
return 0, ErrApplyDiffFallback
|
||||||
|
}
|
||||||
|
|
||||||
|
parentRootDir := path.Join(d.dir(parent), "root")
|
||||||
|
if _, err := os.Stat(parentRootDir); err != nil {
|
||||||
|
return 0, ErrApplyDiffFallback
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now know there is a parent, and it has a "root" directory containing
|
||||||
|
// the full root filesystem. We can just hardlink it and apply the
|
||||||
|
// layer. This relies on two things:
|
||||||
|
// 1) ApplyDiff is only run once on a clean (no writes to upper layer) container
|
||||||
|
// 2) ApplyDiff doesn't do any in-place writes to files (would break hardlinks)
|
||||||
|
// These are all currently true and are not expected to break
|
||||||
|
|
||||||
|
tmpRootDir, err := ioutil.TempDir(dir, "tmproot")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
os.RemoveAll(tmpRootDir)
|
||||||
|
} else {
|
||||||
|
os.RemoveAll(path.Join(dir, "upper"))
|
||||||
|
os.RemoveAll(path.Join(dir, "work"))
|
||||||
|
os.RemoveAll(path.Join(dir, "merged"))
|
||||||
|
os.RemoveAll(path.Join(dir, "lower-id"))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = copyDir(parentRootDir, tmpRootDir, CopyHardlink); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := chrootarchive.ApplyLayer(tmpRootDir, diff); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rootDir := path.Join(dir, "root")
|
||||||
|
if err := os.Rename(tmpRootDir, rootDir); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
changes, err := archive.ChangesDirs(rootDir, parentRootDir)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return archive.ChangesSize(rootDir, changes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Driver) Exists(id string) bool {
|
||||||
|
_, err := os.Stat(d.dir(id))
|
||||||
|
return err == nil
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package overlay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/docker/daemon/graphdriver/graphtest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This avoids creating a new driver for each test if all tests are run
|
||||||
|
// Make sure to put new tests between TestOverlaySetup and TestOverlayTeardown
|
||||||
|
func TestOverlaySetup(t *testing.T) {
|
||||||
|
graphtest.GetDriver(t, "overlay")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverlayCreateEmpty(t *testing.T) {
|
||||||
|
graphtest.DriverTestCreateEmpty(t, "overlay")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverlayCreateBase(t *testing.T) {
|
||||||
|
graphtest.DriverTestCreateBase(t, "overlay")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverlayCreateSnap(t *testing.T) {
|
||||||
|
graphtest.DriverTestCreateSnap(t, "overlay")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverlayTeardown(t *testing.T) {
|
||||||
|
graphtest.PutDriver(t)
|
||||||
|
}
|
|
@ -133,6 +133,9 @@ func (daemon *Daemon) canDeleteImage(imgID string, force bool) error {
|
||||||
for _, container := range daemon.List() {
|
for _, container := range daemon.List() {
|
||||||
parent, err := daemon.Repositories().LookupImage(container.Image)
|
parent, err := daemon.Repositories().LookupImage(container.Image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if daemon.Graph().IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,12 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/dockerversion"
|
"github.com/docker/docker/dockerversion"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
"github.com/docker/docker/pkg/parsers/kernel"
|
"github.com/docker/docker/pkg/parsers/kernel"
|
||||||
"github.com/docker/docker/pkg/parsers/operatingsystem"
|
"github.com/docker/docker/pkg/parsers/operatingsystem"
|
||||||
|
"github.com/docker/docker/pkg/system"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
@ -37,6 +38,11 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) engine.Status {
|
||||||
operatingSystem += " (containerized)"
|
operatingSystem += " (containerized)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
meminfo, err := system.ReadMemInfo()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Could not read system memory info: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// if we still have the original dockerinit binary from before we copied it locally, let's return the path to that, since that's more intuitive (the copied path is trivial to derive by hand given VERSION)
|
// if we still have the original dockerinit binary from before we copied it locally, let's return the path to that, since that's more intuitive (the copied path is trivial to derive by hand given VERSION)
|
||||||
initPath := utils.DockerInitPath("")
|
initPath := utils.DockerInitPath("")
|
||||||
if initPath == "" {
|
if initPath == "" {
|
||||||
|
@ -50,6 +56,7 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) engine.Status {
|
||||||
return job.Error(err)
|
return job.Error(err)
|
||||||
}
|
}
|
||||||
v := &engine.Env{}
|
v := &engine.Env{}
|
||||||
|
v.Set("ID", daemon.ID)
|
||||||
v.SetInt("Containers", len(daemon.List()))
|
v.SetInt("Containers", len(daemon.List()))
|
||||||
v.SetInt("Images", imgcount)
|
v.SetInt("Images", imgcount)
|
||||||
v.Set("Driver", daemon.GraphDriver().String())
|
v.Set("Driver", daemon.GraphDriver().String())
|
||||||
|
@ -67,6 +74,13 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) engine.Status {
|
||||||
v.Set("IndexServerAddress", registry.IndexServerAddress())
|
v.Set("IndexServerAddress", registry.IndexServerAddress())
|
||||||
v.Set("InitSha1", dockerversion.INITSHA1)
|
v.Set("InitSha1", dockerversion.INITSHA1)
|
||||||
v.Set("InitPath", initPath)
|
v.Set("InitPath", initPath)
|
||||||
|
v.SetInt("NCPU", runtime.NumCPU())
|
||||||
|
v.SetInt64("MemTotal", meminfo.MemTotal)
|
||||||
|
v.Set("DockerRootDir", daemon.Config().Root)
|
||||||
|
if hostname, err := os.Hostname(); err == nil {
|
||||||
|
v.Set("Name", hostname)
|
||||||
|
}
|
||||||
|
v.SetList("Labels", daemon.Config().Labels)
|
||||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
if _, err := v.WriteTo(job.Stdout); err != nil {
|
||||||
return job.Error(err)
|
return job.Error(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,3 +65,21 @@ func (daemon *Daemon) ContainerInspect(job *engine.Job) engine.Status {
|
||||||
}
|
}
|
||||||
return job.Errorf("No such container: %s", name)
|
return job.Errorf("No such container: %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (daemon *Daemon) ContainerExecInspect(job *engine.Job) engine.Status {
|
||||||
|
if len(job.Args) != 1 {
|
||||||
|
return job.Errorf("usage: %s ID", job.Name)
|
||||||
|
}
|
||||||
|
id := job.Args[0]
|
||||||
|
eConfig, err := daemon.getExecConfig(id)
|
||||||
|
if err != nil {
|
||||||
|
return job.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(*eConfig)
|
||||||
|
if err != nil {
|
||||||
|
return job.Error(err)
|
||||||
|
}
|
||||||
|
job.Stdout.Write(b)
|
||||||
|
return engine.StatusOK
|
||||||
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status {
|
||||||
size = job.GetenvBool("size")
|
size = job.GetenvBool("size")
|
||||||
psFilters filters.Args
|
psFilters filters.Args
|
||||||
filt_exited []int
|
filt_exited []int
|
||||||
filt_status []string
|
|
||||||
)
|
)
|
||||||
outs := engine.NewTable("Created", 0)
|
outs := engine.NewTable("Created", 0)
|
||||||
|
|
||||||
|
@ -46,8 +45,6 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filt_status, _ = psFilters["status"]
|
|
||||||
|
|
||||||
names := map[string][]string{}
|
names := map[string][]string{}
|
||||||
daemon.ContainerGraph().Walk("/", func(p string, e *graphdb.Entity) error {
|
daemon.ContainerGraph().Walk("/", func(p string, e *graphdb.Entity) error {
|
||||||
names[e.ID()] = append(names[e.ID()], p)
|
names[e.ID()] = append(names[e.ID()], p)
|
||||||
|
@ -76,6 +73,15 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status {
|
||||||
if !container.Running && !all && n <= 0 && since == "" && before == "" {
|
if !container.Running && !all && n <= 0 && since == "" && before == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !psFilters.Match("name", container.Name) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !psFilters.Match("id", container.ID) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if before != "" && !foundBefore {
|
if before != "" && !foundBefore {
|
||||||
if container.ID == beforeCont.ID {
|
if container.ID == beforeCont.ID {
|
||||||
foundBefore = true
|
foundBefore = true
|
||||||
|
@ -102,10 +108,9 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, status := range filt_status {
|
|
||||||
if container.State.StateString() != strings.ToLower(status) {
|
if !psFilters.Match("status", container.State.StateString()) {
|
||||||
return nil
|
return nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
displayed++
|
displayed++
|
||||||
out := &engine.Env{}
|
out := &engine.Env{}
|
||||||
|
|
|
@ -7,10 +7,11 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/pkg/jsonlog"
|
"github.com/docker/docker/pkg/jsonlog"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
"github.com/docker/docker/pkg/tailfile"
|
"github.com/docker/docker/pkg/tailfile"
|
||||||
"github.com/docker/docker/pkg/timeutils"
|
"github.com/docker/docker/pkg/timeutils"
|
||||||
)
|
)
|
||||||
|
@ -112,24 +113,36 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) engine.Status {
|
||||||
}
|
}
|
||||||
if follow && container.IsRunning() {
|
if follow && container.IsRunning() {
|
||||||
errors := make(chan error, 2)
|
errors := make(chan error, 2)
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
|
||||||
if stdout {
|
if stdout {
|
||||||
|
wg.Add(1)
|
||||||
stdoutPipe := container.StdoutLogPipe()
|
stdoutPipe := container.StdoutLogPipe()
|
||||||
defer stdoutPipe.Close()
|
defer stdoutPipe.Close()
|
||||||
go func() {
|
go func() {
|
||||||
errors <- jsonlog.WriteLog(stdoutPipe, job.Stdout, format)
|
errors <- jsonlog.WriteLog(stdoutPipe, job.Stdout, format)
|
||||||
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
if stderr {
|
if stderr {
|
||||||
|
wg.Add(1)
|
||||||
stderrPipe := container.StderrLogPipe()
|
stderrPipe := container.StderrLogPipe()
|
||||||
defer stderrPipe.Close()
|
defer stderrPipe.Close()
|
||||||
go func() {
|
go func() {
|
||||||
errors <- jsonlog.WriteLog(stderrPipe, job.Stderr, format)
|
errors <- jsonlog.WriteLog(stderrPipe, job.Stderr, format)
|
||||||
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
err := <-errors
|
|
||||||
if err != nil {
|
wg.Wait()
|
||||||
log.Errorf("%s", err)
|
close(errors)
|
||||||
|
|
||||||
|
for err := range errors {
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("%s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return engine.StatusOK
|
return engine.StatusOK
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/daemon/execdriver"
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ func (m *containerMonitor) Close() error {
|
||||||
func (m *containerMonitor) Start() error {
|
func (m *containerMonitor) Start() error {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
exitStatus int
|
exitStatus execdriver.ExitStatus
|
||||||
// this variable indicates where we in execution flow:
|
// this variable indicates where we in execution flow:
|
||||||
// before Run or after
|
// before Run or after
|
||||||
afterRun bool
|
afterRun bool
|
||||||
|
@ -110,7 +110,7 @@ func (m *containerMonitor) Start() error {
|
||||||
defer func() {
|
defer func() {
|
||||||
if afterRun {
|
if afterRun {
|
||||||
m.container.Lock()
|
m.container.Lock()
|
||||||
m.container.setStopped(exitStatus)
|
m.container.setStopped(&exitStatus)
|
||||||
defer m.container.Unlock()
|
defer m.container.Unlock()
|
||||||
}
|
}
|
||||||
m.Close()
|
m.Close()
|
||||||
|
@ -138,6 +138,7 @@ func (m *containerMonitor) Start() error {
|
||||||
// if we receive an internal error from the initial start of a container then lets
|
// if we receive an internal error from the initial start of a container then lets
|
||||||
// return it instead of entering the restart loop
|
// return it instead of entering the restart loop
|
||||||
if m.container.RestartCount == 0 {
|
if m.container.RestartCount == 0 {
|
||||||
|
m.container.ExitCode = -1
|
||||||
m.resetContainer(false)
|
m.resetContainer(false)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -149,10 +150,10 @@ func (m *containerMonitor) Start() error {
|
||||||
// here container.Lock is already lost
|
// here container.Lock is already lost
|
||||||
afterRun = true
|
afterRun = true
|
||||||
|
|
||||||
m.resetMonitor(err == nil && exitStatus == 0)
|
m.resetMonitor(err == nil && exitStatus.ExitCode == 0)
|
||||||
|
|
||||||
if m.shouldRestart(exitStatus) {
|
if m.shouldRestart(exitStatus.ExitCode) {
|
||||||
m.container.SetRestarting(exitStatus)
|
m.container.SetRestarting(&exitStatus)
|
||||||
m.container.LogEvent("die")
|
m.container.LogEvent("die")
|
||||||
m.resetContainer(true)
|
m.resetContainer(true)
|
||||||
|
|
||||||
|
@ -163,10 +164,12 @@ func (m *containerMonitor) Start() error {
|
||||||
// we need to check this before reentering the loop because the waitForNextRestart could have
|
// we need to check this before reentering the loop because the waitForNextRestart could have
|
||||||
// been terminated by a request from a user
|
// been terminated by a request from a user
|
||||||
if m.shouldStop {
|
if m.shouldStop {
|
||||||
|
m.container.ExitCode = exitStatus.ExitCode
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
m.container.ExitCode = exitStatus.ExitCode
|
||||||
m.container.LogEvent("die")
|
m.container.LogEvent("die")
|
||||||
m.resetContainer(true)
|
m.resetContainer(true)
|
||||||
return err
|
return err
|
||||||
|
@ -206,7 +209,7 @@ func (m *containerMonitor) waitForNextRestart() {
|
||||||
|
|
||||||
// shouldRestart checks the restart policy and applies the rules to determine if
|
// shouldRestart checks the restart policy and applies the rules to determine if
|
||||||
// the container's process should be restarted
|
// the container's process should be restarted
|
||||||
func (m *containerMonitor) shouldRestart(exitStatus int) bool {
|
func (m *containerMonitor) shouldRestart(exitCode int) bool {
|
||||||
m.mux.Lock()
|
m.mux.Lock()
|
||||||
defer m.mux.Unlock()
|
defer m.mux.Unlock()
|
||||||
|
|
||||||
|
@ -225,7 +228,7 @@ func (m *containerMonitor) shouldRestart(exitStatus int) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return exitStatus != 0
|
return exitCode != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -4,16 +4,17 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"os"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/daemon/networkdriver"
|
"github.com/docker/docker/daemon/networkdriver"
|
||||||
"github.com/docker/docker/daemon/networkdriver/ipallocator"
|
"github.com/docker/docker/daemon/networkdriver/ipallocator"
|
||||||
"github.com/docker/docker/daemon/networkdriver/portallocator"
|
|
||||||
"github.com/docker/docker/daemon/networkdriver/portmapper"
|
"github.com/docker/docker/daemon/networkdriver/portmapper"
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
|
"github.com/docker/docker/nat"
|
||||||
"github.com/docker/docker/pkg/iptables"
|
"github.com/docker/docker/pkg/iptables"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
"github.com/docker/docker/pkg/networkfs/resolvconf"
|
"github.com/docker/docker/pkg/networkfs/resolvconf"
|
||||||
"github.com/docker/docker/pkg/parsers/kernel"
|
"github.com/docker/docker/pkg/parsers/kernel"
|
||||||
"github.com/docker/libcontainer/netlink"
|
"github.com/docker/libcontainer/netlink"
|
||||||
|
@ -104,8 +105,8 @@ func InitDriver(job *engine.Job) engine.Status {
|
||||||
if !usingDefaultBridge {
|
if !usingDefaultBridge {
|
||||||
return job.Error(err)
|
return job.Error(err)
|
||||||
}
|
}
|
||||||
// If the iface is not found, try to create it
|
// If the bridge interface is not found (or has no address), try to create it and/or add an address
|
||||||
if err := createBridge(bridgeIP); err != nil {
|
if err := configureBridge(bridgeIP); err != nil {
|
||||||
return job.Error(err)
|
return job.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +194,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error {
|
||||||
if output, err := iptables.Raw(append([]string{"-I"}, natArgs...)...); err != nil {
|
if output, err := iptables.Raw(append([]string{"-I"}, natArgs...)...); err != nil {
|
||||||
return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
|
return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
|
||||||
} else if len(output) != 0 {
|
} else if len(output) != 0 {
|
||||||
return fmt.Errorf("Error iptables postrouting: %s", output)
|
return &iptables.ChainError{Chain: "POSTROUTING", Output: output}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,7 +235,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error {
|
||||||
if output, err := iptables.Raw(append([]string{"-I"}, outgoingArgs...)...); err != nil {
|
if output, err := iptables.Raw(append([]string{"-I"}, outgoingArgs...)...); err != nil {
|
||||||
return fmt.Errorf("Unable to allow outgoing packets: %s", err)
|
return fmt.Errorf("Unable to allow outgoing packets: %s", err)
|
||||||
} else if len(output) != 0 {
|
} else if len(output) != 0 {
|
||||||
return fmt.Errorf("Error iptables allow outgoing: %s", output)
|
return &iptables.ChainError{Chain: "FORWARD outgoing", Output: output}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,16 +246,18 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error {
|
||||||
if output, err := iptables.Raw(append([]string{"-I"}, existingArgs...)...); err != nil {
|
if output, err := iptables.Raw(append([]string{"-I"}, existingArgs...)...); err != nil {
|
||||||
return fmt.Errorf("Unable to allow incoming packets: %s", err)
|
return fmt.Errorf("Unable to allow incoming packets: %s", err)
|
||||||
} else if len(output) != 0 {
|
} else if len(output) != 0 {
|
||||||
return fmt.Errorf("Error iptables allow incoming: %s", output)
|
return &iptables.ChainError{Chain: "FORWARD incoming", Output: output}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`,
|
// configureBridge attempts to create and configure a network bridge interface named `bridgeIface` on the host
|
||||||
// and attempts to configure it with an address which doesn't conflict with any other interface on the host.
|
// If bridgeIP is empty, it will try to find a non-conflicting IP from the Docker-specified private ranges
|
||||||
// If it can't find an address which doesn't conflict, it will return an error.
|
// If the bridge `bridgeIface` already exists, it will only perform the IP address association with the existing
|
||||||
func createBridge(bridgeIP string) error {
|
// bridge (fixes issue #8444)
|
||||||
|
// If an address which doesn't conflict with existing interfaces can't be found, an error is returned.
|
||||||
|
func configureBridge(bridgeIP string) error {
|
||||||
nameservers := []string{}
|
nameservers := []string{}
|
||||||
resolvConf, _ := resolvconf.Get()
|
resolvConf, _ := resolvconf.Get()
|
||||||
// we don't check for an error here, because we don't really care
|
// we don't check for an error here, because we don't really care
|
||||||
|
@ -295,7 +298,10 @@ func createBridge(bridgeIP string) error {
|
||||||
log.Debugf("Creating bridge %s with network %s", bridgeIface, ifaceAddr)
|
log.Debugf("Creating bridge %s with network %s", bridgeIface, ifaceAddr)
|
||||||
|
|
||||||
if err := createBridgeIface(bridgeIface); err != nil {
|
if err := createBridgeIface(bridgeIface); err != nil {
|
||||||
return err
|
// the bridge may already exist, therefore we can ignore an "exists" error
|
||||||
|
if !os.IsExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
iface, err := net.InterfaceByName(bridgeIface)
|
iface, err := net.InterfaceByName(bridgeIface)
|
||||||
|
@ -461,22 +467,13 @@ func AllocatePort(job *engine.Job) engine.Status {
|
||||||
if host, err = portmapper.Map(container, ip, hostPort); err == nil {
|
if host, err = portmapper.Map(container, ip, hostPort); err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
// There is no point in immediately retrying to map an explicitly
|
||||||
if allocerr, ok := err.(portallocator.ErrPortAlreadyAllocated); ok {
|
// chosen port.
|
||||||
// There is no point in immediately retrying to map an explicitly
|
if hostPort != 0 {
|
||||||
// chosen port.
|
job.Logf("Failed to allocate and map port %d: %s", hostPort, err)
|
||||||
if hostPort != 0 {
|
|
||||||
job.Logf("Failed to bind %s for container address %s: %s", allocerr.IPPort(), container.String(), allocerr.Error())
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Automatically chosen 'free' port failed to bind: move on the next.
|
|
||||||
job.Logf("Failed to bind %s for container address %s. Trying another port.", allocerr.IPPort(), container.String())
|
|
||||||
} else {
|
|
||||||
// some other error during mapping
|
|
||||||
job.Logf("Received an unexpected error during port allocation: %s", err.Error())
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
job.Logf("Failed to allocate and map port: %s, retry: %d", err, i+1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -509,18 +506,13 @@ func LinkContainers(job *engine.Job) engine.Status {
|
||||||
ignoreErrors = job.GetenvBool("IgnoreErrors")
|
ignoreErrors = job.GetenvBool("IgnoreErrors")
|
||||||
ports = job.GetenvList("Ports")
|
ports = job.GetenvList("Ports")
|
||||||
)
|
)
|
||||||
split := func(p string) (string, string) {
|
for _, value := range ports {
|
||||||
parts := strings.Split(p, "/")
|
port := nat.Port(value)
|
||||||
return parts[0], parts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range ports {
|
|
||||||
port, proto := split(p)
|
|
||||||
if output, err := iptables.Raw(action, "FORWARD",
|
if output, err := iptables.Raw(action, "FORWARD",
|
||||||
"-i", bridgeIface, "-o", bridgeIface,
|
"-i", bridgeIface, "-o", bridgeIface,
|
||||||
"-p", proto,
|
"-p", port.Proto(),
|
||||||
"-s", parentIP,
|
"-s", parentIP,
|
||||||
"--dport", port,
|
"--dport", strconv.Itoa(port.Int()),
|
||||||
"-d", childIP,
|
"-d", childIP,
|
||||||
"-j", "ACCEPT"); !ignoreErrors && err != nil {
|
"-j", "ACCEPT"); !ignoreErrors && err != nil {
|
||||||
return job.Error(err)
|
return job.Error(err)
|
||||||
|
@ -530,9 +522,9 @@ func LinkContainers(job *engine.Job) engine.Status {
|
||||||
|
|
||||||
if output, err := iptables.Raw(action, "FORWARD",
|
if output, err := iptables.Raw(action, "FORWARD",
|
||||||
"-i", bridgeIface, "-o", bridgeIface,
|
"-i", bridgeIface, "-o", bridgeIface,
|
||||||
"-p", proto,
|
"-p", port.Proto(),
|
||||||
"-s", childIP,
|
"-s", childIP,
|
||||||
"--sport", port,
|
"--sport", strconv.Itoa(port.Int()),
|
||||||
"-d", parentIP,
|
"-d", parentIP,
|
||||||
"-j", "ACCEPT"); !ignoreErrors && err != nil {
|
"-j", "ACCEPT"); !ignoreErrors && err != nil {
|
||||||
return job.Error(err)
|
return job.Error(err)
|
||||||
|
|
|
@ -1,31 +1,38 @@
|
||||||
package ipallocator
|
package ipallocator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/daemon/networkdriver"
|
"github.com/docker/docker/daemon/networkdriver"
|
||||||
)
|
)
|
||||||
|
|
||||||
// allocatedMap is thread-unsafe set of allocated IP
|
// allocatedMap is thread-unsafe set of allocated IP
|
||||||
type allocatedMap struct {
|
type allocatedMap struct {
|
||||||
p map[uint32]struct{}
|
p map[string]struct{}
|
||||||
last uint32
|
last *big.Int
|
||||||
begin uint32
|
begin *big.Int
|
||||||
end uint32
|
end *big.Int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAllocatedMap(network *net.IPNet) *allocatedMap {
|
func newAllocatedMap(network *net.IPNet) *allocatedMap {
|
||||||
firstIP, lastIP := networkdriver.NetworkRange(network)
|
firstIP, lastIP := networkdriver.NetworkRange(network)
|
||||||
begin := ipToInt(firstIP) + 2
|
begin := big.NewInt(0).Add(ipToBigInt(firstIP), big.NewInt(1))
|
||||||
end := ipToInt(lastIP) - 1
|
end := big.NewInt(0).Sub(ipToBigInt(lastIP), big.NewInt(1))
|
||||||
|
|
||||||
|
// if IPv4 network, then allocation range starts at begin + 1 because begin is bridge IP
|
||||||
|
if len(firstIP) == 4 {
|
||||||
|
begin = begin.Add(begin, big.NewInt(1))
|
||||||
|
}
|
||||||
|
|
||||||
return &allocatedMap{
|
return &allocatedMap{
|
||||||
p: make(map[uint32]struct{}),
|
p: make(map[string]struct{}),
|
||||||
begin: begin,
|
begin: begin,
|
||||||
end: end,
|
end: end,
|
||||||
last: begin - 1, // so first allocated will be begin
|
last: big.NewInt(0).Sub(begin, big.NewInt(1)), // so first allocated will be begin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,13 +63,16 @@ func RegisterSubnet(network *net.IPNet, subnet *net.IPNet) error {
|
||||||
}
|
}
|
||||||
n := newAllocatedMap(network)
|
n := newAllocatedMap(network)
|
||||||
beginIP, endIP := networkdriver.NetworkRange(subnet)
|
beginIP, endIP := networkdriver.NetworkRange(subnet)
|
||||||
begin, end := ipToInt(beginIP)+1, ipToInt(endIP)-1
|
begin := big.NewInt(0).Add(ipToBigInt(beginIP), big.NewInt(1))
|
||||||
if !(begin >= n.begin && end <= n.end && begin < end) {
|
end := big.NewInt(0).Sub(ipToBigInt(endIP), big.NewInt(1))
|
||||||
|
|
||||||
|
// Check that subnet is within network
|
||||||
|
if !(begin.Cmp(n.begin) >= 0 && end.Cmp(n.end) <= 0 && begin.Cmp(end) == -1) {
|
||||||
return ErrBadSubnet
|
return ErrBadSubnet
|
||||||
}
|
}
|
||||||
n.begin = begin
|
n.begin.Set(begin)
|
||||||
n.end = end
|
n.end.Set(end)
|
||||||
n.last = begin - 1
|
n.last.Sub(begin, big.NewInt(1))
|
||||||
allocatedIPs[key] = n
|
allocatedIPs[key] = n
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -93,28 +103,25 @@ func ReleaseIP(network *net.IPNet, ip net.IP) error {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
if allocated, exists := allocatedIPs[network.String()]; exists {
|
if allocated, exists := allocatedIPs[network.String()]; exists {
|
||||||
pos := ipToInt(ip)
|
delete(allocated.p, ip.String())
|
||||||
delete(allocated.p, pos)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (allocated *allocatedMap) checkIP(ip net.IP) (net.IP, error) {
|
func (allocated *allocatedMap) checkIP(ip net.IP) (net.IP, error) {
|
||||||
pos := ipToInt(ip)
|
if _, ok := allocated.p[ip.String()]; ok {
|
||||||
|
|
||||||
// Verify that the IP address has not been already allocated.
|
|
||||||
if _, ok := allocated.p[pos]; ok {
|
|
||||||
return nil, ErrIPAlreadyAllocated
|
return nil, ErrIPAlreadyAllocated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pos := ipToBigInt(ip)
|
||||||
// Verify that the IP address is within our network range.
|
// Verify that the IP address is within our network range.
|
||||||
if pos < allocated.begin || pos > allocated.end {
|
if pos.Cmp(allocated.begin) == -1 || pos.Cmp(allocated.end) == 1 {
|
||||||
return nil, ErrIPOutOfRange
|
return nil, ErrIPOutOfRange
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the IP.
|
// Register the IP.
|
||||||
allocated.p[pos] = struct{}{}
|
allocated.p[ip.String()] = struct{}{}
|
||||||
allocated.last = pos
|
allocated.last.Set(pos)
|
||||||
|
|
||||||
return ip, nil
|
return ip, nil
|
||||||
}
|
}
|
||||||
|
@ -122,29 +129,38 @@ func (allocated *allocatedMap) checkIP(ip net.IP) (net.IP, error) {
|
||||||
// return an available ip if one is currently available. If not,
|
// return an available ip if one is currently available. If not,
|
||||||
// return the next available ip for the nextwork
|
// return the next available ip for the nextwork
|
||||||
func (allocated *allocatedMap) getNextIP() (net.IP, error) {
|
func (allocated *allocatedMap) getNextIP() (net.IP, error) {
|
||||||
for pos := allocated.last + 1; pos != allocated.last; pos++ {
|
pos := big.NewInt(0).Set(allocated.last)
|
||||||
if pos > allocated.end {
|
allRange := big.NewInt(0).Sub(allocated.end, allocated.begin)
|
||||||
pos = allocated.begin
|
for i := big.NewInt(0); i.Cmp(allRange) <= 0; i.Add(i, big.NewInt(1)) {
|
||||||
|
pos.Add(pos, big.NewInt(1))
|
||||||
|
if pos.Cmp(allocated.end) == 1 {
|
||||||
|
pos.Set(allocated.begin)
|
||||||
}
|
}
|
||||||
if _, ok := allocated.p[pos]; ok {
|
if _, ok := allocated.p[bigIntToIP(pos).String()]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
allocated.p[pos] = struct{}{}
|
allocated.p[bigIntToIP(pos).String()] = struct{}{}
|
||||||
allocated.last = pos
|
allocated.last.Set(pos)
|
||||||
return intToIP(pos), nil
|
return bigIntToIP(pos), nil
|
||||||
}
|
}
|
||||||
return nil, ErrNoAvailableIPs
|
return nil, ErrNoAvailableIPs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts a 4 bytes IP into a 32 bit integer
|
// Converts a 4 bytes IP into a 128 bit integer
|
||||||
func ipToInt(ip net.IP) uint32 {
|
func ipToBigInt(ip net.IP) *big.Int {
|
||||||
return binary.BigEndian.Uint32(ip.To4())
|
x := big.NewInt(0)
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
return x.SetBytes(ip4)
|
||||||
|
}
|
||||||
|
if ip6 := ip.To16(); ip6 != nil {
|
||||||
|
return x.SetBytes(ip6)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Errorf("ipToBigInt: Wrong IP length! %s", ip)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts 32 bit integer into a 4 bytes IP address
|
// Converts 128 bit integer into a 4 bytes IP address
|
||||||
func intToIP(n uint32) net.IP {
|
func bigIntToIP(v *big.Int) net.IP {
|
||||||
b := make([]byte, 4)
|
return net.IP(v.Bytes())
|
||||||
binary.BigEndian.PutUint32(b, n)
|
|
||||||
ip := net.IP(b)
|
|
||||||
return ip
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ipallocator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -10,6 +11,46 @@ func reset() {
|
||||||
allocatedIPs = networkSet{}
|
allocatedIPs = networkSet{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConversion(t *testing.T) {
|
||||||
|
ip := net.ParseIP("127.0.0.1")
|
||||||
|
i := ipToBigInt(ip)
|
||||||
|
if i.Cmp(big.NewInt(0x7f000001)) != 0 {
|
||||||
|
t.Fatal("incorrect conversion")
|
||||||
|
}
|
||||||
|
conv := bigIntToIP(i)
|
||||||
|
if !ip.Equal(conv) {
|
||||||
|
t.Error(conv.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConversionIPv6(t *testing.T) {
|
||||||
|
ip := net.ParseIP("2a00:1450::1")
|
||||||
|
ip2 := net.ParseIP("2a00:1450::2")
|
||||||
|
ip3 := net.ParseIP("2a00:1450::1:1")
|
||||||
|
i := ipToBigInt(ip)
|
||||||
|
val, success := big.NewInt(0).SetString("2a001450000000000000000000000001", 16)
|
||||||
|
if !success {
|
||||||
|
t.Fatal("Hex-String to BigInt conversion failed.")
|
||||||
|
}
|
||||||
|
if i.Cmp(val) != 0 {
|
||||||
|
t.Fatal("incorrent conversion")
|
||||||
|
}
|
||||||
|
|
||||||
|
conv := bigIntToIP(i)
|
||||||
|
conv2 := bigIntToIP(big.NewInt(0).Add(i, big.NewInt(1)))
|
||||||
|
conv3 := bigIntToIP(big.NewInt(0).Add(i, big.NewInt(0x10000)))
|
||||||
|
|
||||||
|
if !ip.Equal(conv) {
|
||||||
|
t.Error("2a00:1450::1 should be equal to " + conv.String())
|
||||||
|
}
|
||||||
|
if !ip2.Equal(conv2) {
|
||||||
|
t.Error("2a00:1450::2 should be equal to " + conv2.String())
|
||||||
|
}
|
||||||
|
if !ip3.Equal(conv3) {
|
||||||
|
t.Error("2a00:1450::1:1 should be equal to " + conv3.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRequestNewIps(t *testing.T) {
|
func TestRequestNewIps(t *testing.T) {
|
||||||
defer reset()
|
defer reset()
|
||||||
network := &net.IPNet{
|
network := &net.IPNet{
|
||||||
|
@ -19,6 +60,7 @@ func TestRequestNewIps(t *testing.T) {
|
||||||
|
|
||||||
var ip net.IP
|
var ip net.IP
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
for i := 2; i < 10; i++ {
|
for i := 2; i < 10; i++ {
|
||||||
ip, err = RequestIP(network, nil)
|
ip, err = RequestIP(network, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -29,7 +71,39 @@ func TestRequestNewIps(t *testing.T) {
|
||||||
t.Fatalf("Expected ip %s got %s", expected, ip.String())
|
t.Fatalf("Expected ip %s got %s", expected, ip.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value := intToIP(ipToInt(ip) + 1).String()
|
value := bigIntToIP(big.NewInt(0).Add(ipToBigInt(ip), big.NewInt(1))).String()
|
||||||
|
if err := ReleaseIP(network, ip); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ip, err = RequestIP(network, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if ip.String() != value {
|
||||||
|
t.Fatalf("Expected to receive the next ip %s got %s", value, ip.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequestNewIpV6(t *testing.T) {
|
||||||
|
defer reset()
|
||||||
|
network := &net.IPNet{
|
||||||
|
IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||||
|
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
|
||||||
|
}
|
||||||
|
|
||||||
|
var ip net.IP
|
||||||
|
var err error
|
||||||
|
for i := 1; i < 10; i++ {
|
||||||
|
ip, err = RequestIP(network, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected := fmt.Sprintf("2a00:1450::%d", i); ip.String() != expected {
|
||||||
|
t.Fatalf("Expected ip %s got %s", expected, ip.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value := bigIntToIP(big.NewInt(0).Add(ipToBigInt(ip), big.NewInt(1))).String()
|
||||||
if err := ReleaseIP(network, ip); err != nil {
|
if err := ReleaseIP(network, ip); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -59,6 +133,23 @@ func TestReleaseIp(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReleaseIpV6(t *testing.T) {
|
||||||
|
defer reset()
|
||||||
|
network := &net.IPNet{
|
||||||
|
IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||||
|
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := RequestIP(network, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ReleaseIP(network, ip); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetReleasedIp(t *testing.T) {
|
func TestGetReleasedIp(t *testing.T) {
|
||||||
defer reset()
|
defer reset()
|
||||||
network := &net.IPNet{
|
network := &net.IPNet{
|
||||||
|
@ -97,6 +188,44 @@ func TestGetReleasedIp(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetReleasedIpV6(t *testing.T) {
|
||||||
|
defer reset()
|
||||||
|
network := &net.IPNet{
|
||||||
|
IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||||
|
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := RequestIP(network, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
value := ip.String()
|
||||||
|
if err := ReleaseIP(network, ip); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 253; i++ {
|
||||||
|
_, err = RequestIP(network, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = ReleaseIP(network, ip)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err = RequestIP(network, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip.String() != value {
|
||||||
|
t.Fatalf("Expected to receive same ip %s got %s", value, ip.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRequestSpecificIp(t *testing.T) {
|
func TestRequestSpecificIp(t *testing.T) {
|
||||||
defer reset()
|
defer reset()
|
||||||
network := &net.IPNet{
|
network := &net.IPNet{
|
||||||
|
@ -122,15 +251,28 @@ func TestRequestSpecificIp(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConversion(t *testing.T) {
|
func TestRequestSpecificIpV6(t *testing.T) {
|
||||||
ip := net.ParseIP("127.0.0.1")
|
defer reset()
|
||||||
i := ipToInt(ip)
|
network := &net.IPNet{
|
||||||
if i == 0 {
|
IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||||
t.Fatal("converted to zero")
|
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
|
||||||
}
|
}
|
||||||
conv := intToIP(i)
|
|
||||||
if !ip.Equal(conv) {
|
ip := net.ParseIP("2a00:1450::5")
|
||||||
t.Error(conv.String())
|
|
||||||
|
// Request a "good" IP.
|
||||||
|
if _, err := RequestIP(network, ip); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request the same IP again.
|
||||||
|
if _, err := RequestIP(network, ip); err != ErrIPAlreadyAllocated {
|
||||||
|
t.Fatalf("Got the same IP twice: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request an out of range IP.
|
||||||
|
if _, err := RequestIP(network, net.ParseIP("2a00:1500::1")); err != ErrIPOutOfRange {
|
||||||
|
t.Fatalf("Got an out of range IP: %#v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +286,7 @@ func TestIPAllocator(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
gwIP, n, _ := net.ParseCIDR("127.0.0.1/29")
|
gwIP, n, _ := net.ParseCIDR("127.0.0.1/29")
|
||||||
|
|
||||||
network := &net.IPNet{IP: gwIP, Mask: n.Mask}
|
network := &net.IPNet{IP: gwIP, Mask: n.Mask}
|
||||||
// Pool after initialisation (f = free, u = used)
|
// Pool after initialisation (f = free, u = used)
|
||||||
// 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
|
// 2(f) - 3(f) - 4(f) - 5(f) - 6(f)
|
||||||
|
@ -237,13 +380,13 @@ func TestAllocateFirstIP(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
firstIP := network.IP.To4().Mask(network.Mask)
|
firstIP := network.IP.To4().Mask(network.Mask)
|
||||||
first := ipToInt(firstIP) + 1
|
first := big.NewInt(0).Add(ipToBigInt(firstIP), big.NewInt(1))
|
||||||
|
|
||||||
ip, err := RequestIP(network, nil)
|
ip, err := RequestIP(network, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
allocated := ipToInt(ip)
|
allocated := ipToBigInt(ip)
|
||||||
|
|
||||||
if allocated == first {
|
if allocated == first {
|
||||||
t.Fatalf("allocated ip should not equal first ip: %d == %d", first, allocated)
|
t.Fatalf("allocated ip should not equal first ip: %d == %d", first, allocated)
|
||||||
|
@ -289,6 +432,65 @@ func TestAllocateAllIps(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
assertIPEquals(t, first, again)
|
assertIPEquals(t, first, again)
|
||||||
|
|
||||||
|
// ensure that alloc.last == alloc.begin won't result in dead loop
|
||||||
|
if _, err := RequestIP(network, nil); err != ErrNoAvailableIPs {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test by making alloc.last the only free ip and ensure we get it back
|
||||||
|
// #1. first of the range, (alloc.last == ipToInt(first) already)
|
||||||
|
if err := ReleaseIP(network, first); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := RequestIP(network, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertIPEquals(t, first, ret)
|
||||||
|
|
||||||
|
// #2. last of the range, note that current is the last one
|
||||||
|
last := net.IPv4(192, 168, 0, 254)
|
||||||
|
setLastTo(t, network, last)
|
||||||
|
|
||||||
|
ret, err = RequestIP(network, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertIPEquals(t, last, ret)
|
||||||
|
|
||||||
|
// #3. middle of the range
|
||||||
|
mid := net.IPv4(192, 168, 0, 7)
|
||||||
|
setLastTo(t, network, mid)
|
||||||
|
|
||||||
|
ret, err = RequestIP(network, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertIPEquals(t, mid, ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the pool is full when calling setLastTo.
|
||||||
|
// we don't cheat here
|
||||||
|
func setLastTo(t *testing.T, network *net.IPNet, ip net.IP) {
|
||||||
|
if err := ReleaseIP(network, ip); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := RequestIP(network, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertIPEquals(t, ip, ret)
|
||||||
|
|
||||||
|
if err := ReleaseIP(network, ip); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAllocateDifferentSubnets(t *testing.T) {
|
func TestAllocateDifferentSubnets(t *testing.T) {
|
||||||
|
@ -301,11 +503,24 @@ func TestAllocateDifferentSubnets(t *testing.T) {
|
||||||
IP: []byte{127, 0, 0, 1},
|
IP: []byte{127, 0, 0, 1},
|
||||||
Mask: []byte{255, 255, 255, 0},
|
Mask: []byte{255, 255, 255, 0},
|
||||||
}
|
}
|
||||||
|
network3 := &net.IPNet{
|
||||||
|
IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||||
|
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
|
||||||
|
}
|
||||||
|
network4 := &net.IPNet{
|
||||||
|
IP: []byte{0x2a, 0x00, 0x16, 0x32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||||
|
Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask
|
||||||
|
}
|
||||||
expectedIPs := []net.IP{
|
expectedIPs := []net.IP{
|
||||||
0: net.IPv4(192, 168, 0, 2),
|
0: net.IPv4(192, 168, 0, 2),
|
||||||
1: net.IPv4(192, 168, 0, 3),
|
1: net.IPv4(192, 168, 0, 3),
|
||||||
2: net.IPv4(127, 0, 0, 2),
|
2: net.IPv4(127, 0, 0, 2),
|
||||||
3: net.IPv4(127, 0, 0, 3),
|
3: net.IPv4(127, 0, 0, 3),
|
||||||
|
4: net.ParseIP("2a00:1450::1"),
|
||||||
|
5: net.ParseIP("2a00:1450::2"),
|
||||||
|
6: net.ParseIP("2a00:1450::3"),
|
||||||
|
7: net.ParseIP("2a00:1632::1"),
|
||||||
|
8: net.ParseIP("2a00:1632::2"),
|
||||||
}
|
}
|
||||||
|
|
||||||
ip11, err := RequestIP(network1, nil)
|
ip11, err := RequestIP(network1, nil)
|
||||||
|
@ -324,11 +539,37 @@ func TestAllocateDifferentSubnets(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
ip31, err := RequestIP(network3, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ip32, err := RequestIP(network3, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ip33, err := RequestIP(network3, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ip41, err := RequestIP(network4, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ip42, err := RequestIP(network4, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
assertIPEquals(t, expectedIPs[0], ip11)
|
assertIPEquals(t, expectedIPs[0], ip11)
|
||||||
assertIPEquals(t, expectedIPs[1], ip12)
|
assertIPEquals(t, expectedIPs[1], ip12)
|
||||||
assertIPEquals(t, expectedIPs[2], ip21)
|
assertIPEquals(t, expectedIPs[2], ip21)
|
||||||
assertIPEquals(t, expectedIPs[3], ip22)
|
assertIPEquals(t, expectedIPs[3], ip22)
|
||||||
|
assertIPEquals(t, expectedIPs[4], ip31)
|
||||||
|
assertIPEquals(t, expectedIPs[5], ip32)
|
||||||
|
assertIPEquals(t, expectedIPs[6], ip33)
|
||||||
|
assertIPEquals(t, expectedIPs[7], ip41)
|
||||||
|
assertIPEquals(t, expectedIPs[8], ip42)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRegisterBadTwice(t *testing.T) {
|
func TestRegisterBadTwice(t *testing.T) {
|
||||||
defer reset()
|
defer reset()
|
||||||
network := &net.IPNet{
|
network := &net.IPNet{
|
||||||
|
@ -378,6 +619,7 @@ func TestAllocateFromRange(t *testing.T) {
|
||||||
IP: []byte{192, 168, 0, 8},
|
IP: []byte{192, 168, 0, 8},
|
||||||
Mask: []byte{255, 255, 255, 248},
|
Mask: []byte{255, 255, 255, 248},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := RegisterSubnet(network, subnet); err != nil {
|
if err := RegisterSubnet(network, subnet); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,9 +122,6 @@ func TestNetworkRange(t *testing.T) {
|
||||||
if !last.Equal(net.ParseIP("192.168.0.255")) {
|
if !last.Equal(net.ParseIP("192.168.0.255")) {
|
||||||
t.Error(last.String())
|
t.Error(last.String())
|
||||||
}
|
}
|
||||||
if size := NetworkSize(network.Mask); size != 256 {
|
|
||||||
t.Error(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Class A test
|
// Class A test
|
||||||
_, network, _ = net.ParseCIDR("10.0.0.1/8")
|
_, network, _ = net.ParseCIDR("10.0.0.1/8")
|
||||||
|
@ -135,9 +132,6 @@ func TestNetworkRange(t *testing.T) {
|
||||||
if !last.Equal(net.ParseIP("10.255.255.255")) {
|
if !last.Equal(net.ParseIP("10.255.255.255")) {
|
||||||
t.Error(last.String())
|
t.Error(last.String())
|
||||||
}
|
}
|
||||||
if size := NetworkSize(network.Mask); size != 16777216 {
|
|
||||||
t.Error(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Class A, random IP address
|
// Class A, random IP address
|
||||||
_, network, _ = net.ParseCIDR("10.1.2.3/8")
|
_, network, _ = net.ParseCIDR("10.1.2.3/8")
|
||||||
|
@ -158,9 +152,6 @@ func TestNetworkRange(t *testing.T) {
|
||||||
if !last.Equal(net.ParseIP("10.1.2.3")) {
|
if !last.Equal(net.ParseIP("10.1.2.3")) {
|
||||||
t.Error(last.String())
|
t.Error(last.String())
|
||||||
}
|
}
|
||||||
if size := NetworkSize(network.Mask); size != 1 {
|
|
||||||
t.Error(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 31bit mask
|
// 31bit mask
|
||||||
_, network, _ = net.ParseCIDR("10.1.2.3/31")
|
_, network, _ = net.ParseCIDR("10.1.2.3/31")
|
||||||
|
@ -171,9 +162,6 @@ func TestNetworkRange(t *testing.T) {
|
||||||
if !last.Equal(net.ParseIP("10.1.2.3")) {
|
if !last.Equal(net.ParseIP("10.1.2.3")) {
|
||||||
t.Error(last.String())
|
t.Error(last.String())
|
||||||
}
|
}
|
||||||
if size := NetworkSize(network.Mask); size != 2 {
|
|
||||||
t.Error(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 26bit mask
|
// 26bit mask
|
||||||
_, network, _ = net.ParseCIDR("10.1.2.3/26")
|
_, network, _ = net.ParseCIDR("10.1.2.3/26")
|
||||||
|
@ -184,7 +172,4 @@ func TestNetworkRange(t *testing.T) {
|
||||||
if !last.Equal(net.ParseIP("10.1.2.63")) {
|
if !last.Equal(net.ParseIP("10.1.2.63")) {
|
||||||
t.Error(last.String())
|
t.Error(last.String())
|
||||||
}
|
}
|
||||||
if size := NetworkSize(network.Mask); size != 64 {
|
|
||||||
t.Error(size)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,8 @@ type portMap struct {
|
||||||
|
|
||||||
func newPortMap() *portMap {
|
func newPortMap() *portMap {
|
||||||
return &portMap{
|
return &portMap{
|
||||||
p: map[int]struct{}{},
|
p: map[int]struct{}{},
|
||||||
|
last: EndPortRange,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,13 +136,9 @@ func ReleaseAll() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm *portMap) findPort() (int, error) {
|
func (pm *portMap) findPort() (int, error) {
|
||||||
if pm.last == 0 {
|
port := pm.last
|
||||||
pm.p[BeginPortRange] = struct{}{}
|
for i := 0; i <= EndPortRange-BeginPortRange; i++ {
|
||||||
pm.last = BeginPortRange
|
port++
|
||||||
return BeginPortRange, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for port := pm.last + 1; port != pm.last; port++ {
|
|
||||||
if port > EndPortRange {
|
if port > EndPortRange {
|
||||||
port = BeginPortRange
|
port = BeginPortRange
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,6 +134,19 @@ func TestAllocateAllPorts(t *testing.T) {
|
||||||
if newPort != port {
|
if newPort != port {
|
||||||
t.Fatalf("Expected port %d got %d", port, newPort)
|
t.Fatalf("Expected port %d got %d", port, newPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// now pm.last == newPort, release it so that it's the only free port of
|
||||||
|
// the range, and ensure we get it back
|
||||||
|
if err := ReleasePort(defaultIP, "tcp", newPort); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
port, err = RequestPort(defaultIP, "tcp", 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if newPort != port {
|
||||||
|
t.Fatalf("Expected port %d got %d", newPort, port)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkAllocatePorts(b *testing.B) {
|
func BenchmarkAllocatePorts(b *testing.B) {
|
||||||
|
@ -214,3 +227,19 @@ func TestPortAllocation(t *testing.T) {
|
||||||
t.Fatal("Requesting a dynamic port should never allocate a used port")
|
t.Fatal("Requesting a dynamic port should never allocate a used port")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNoDuplicateBPR(t *testing.T) {
|
||||||
|
defer reset()
|
||||||
|
|
||||||
|
if port, err := RequestPort(defaultIP, "tcp", BeginPortRange); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if port != BeginPortRange {
|
||||||
|
t.Fatalf("Expected port %d got %d", BeginPortRange, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if port, err := RequestPort(defaultIP, "tcp", 0); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if port == BeginPortRange {
|
||||||
|
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/daemon/networkdriver/portallocator"
|
"github.com/docker/docker/daemon/networkdriver/portallocator"
|
||||||
"github.com/docker/docker/pkg/iptables"
|
"github.com/docker/docker/pkg/iptables"
|
||||||
"github.com/docker/docker/pkg/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type mapping struct {
|
type mapping struct {
|
||||||
|
|
|
@ -130,7 +130,12 @@ func (p *proxyCommand) Start() error {
|
||||||
r.Read(buf)
|
r.Read(buf)
|
||||||
|
|
||||||
if string(buf) != "0\n" {
|
if string(buf) != "0\n" {
|
||||||
errStr, _ := ioutil.ReadAll(r)
|
errStr, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
errchan <- fmt.Errorf("Error reading exit status from userland proxy: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
errchan <- fmt.Errorf("Error starting userland proxy: %s", errStr)
|
errchan <- fmt.Errorf("Error starting userland proxy: %s", errStr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -140,7 +145,7 @@ func (p *proxyCommand) Start() error {
|
||||||
select {
|
select {
|
||||||
case err := <-errchan:
|
case err := <-errchan:
|
||||||
return err
|
return err
|
||||||
case <-time.After(1 * time.Second):
|
case <-time.After(16 * time.Second):
|
||||||
return fmt.Errorf("Timed out proxy starting the userland proxy")
|
return fmt.Errorf("Timed out proxy starting the userland proxy")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package networkdriver
|
package networkdriver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
@ -56,25 +55,21 @@ func NetworkOverlaps(netX *net.IPNet, netY *net.IPNet) bool {
|
||||||
|
|
||||||
// Calculates the first and last IP addresses in an IPNet
|
// Calculates the first and last IP addresses in an IPNet
|
||||||
func NetworkRange(network *net.IPNet) (net.IP, net.IP) {
|
func NetworkRange(network *net.IPNet) (net.IP, net.IP) {
|
||||||
var (
|
var netIP net.IP
|
||||||
netIP = network.IP.To4()
|
if network.IP.To4() != nil {
|
||||||
firstIP = netIP.Mask(network.Mask)
|
netIP = network.IP.To4()
|
||||||
lastIP = net.IPv4(0, 0, 0, 0).To4()
|
} else if network.IP.To16() != nil {
|
||||||
)
|
netIP = network.IP.To16()
|
||||||
|
} else {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
for i := 0; i < len(lastIP); i++ {
|
lastIP := make([]byte, len(netIP), len(netIP))
|
||||||
|
|
||||||
|
for i := 0; i < len(netIP); i++ {
|
||||||
lastIP[i] = netIP[i] | ^network.Mask[i]
|
lastIP[i] = netIP[i] | ^network.Mask[i]
|
||||||
}
|
}
|
||||||
return firstIP, lastIP
|
return netIP.Mask(network.Mask), net.IP(lastIP)
|
||||||
}
|
|
||||||
|
|
||||||
// Given a netmask, calculates the number of available hosts
|
|
||||||
func NetworkSize(mask net.IPMask) int32 {
|
|
||||||
m := net.IPv4Mask(0, 0, 0, 0)
|
|
||||||
for i := 0; i < net.IPv4len; i++ {
|
|
||||||
m[i] = ^mask[i]
|
|
||||||
}
|
|
||||||
return int32(binary.BigEndian.Uint32(m)) + 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the IPv4 address of a network interface
|
// Return the IPv4 address of a network interface
|
||||||
|
@ -90,7 +85,7 @@ func GetIfaceAddr(name string) (net.Addr, error) {
|
||||||
var addrs4 []net.Addr
|
var addrs4 []net.Addr
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
ip := (addr.(*net.IPNet)).IP
|
ip := (addr.(*net.IPNet)).IP
|
||||||
if ip4 := ip.To4(); len(ip4) == net.IPv4len {
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
addrs4 = append(addrs4, addr)
|
addrs4 = append(addrs4, addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
"github.com/docker/docker/pkg/units"
|
"github.com/docker/docker/pkg/units"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,8 +14,10 @@ type State struct {
|
||||||
Running bool
|
Running bool
|
||||||
Paused bool
|
Paused bool
|
||||||
Restarting bool
|
Restarting bool
|
||||||
|
OOMKilled bool
|
||||||
Pid int
|
Pid int
|
||||||
ExitCode int
|
ExitCode int
|
||||||
|
Error string // contains last known error when starting the container
|
||||||
StartedAt time.Time
|
StartedAt time.Time
|
||||||
FinishedAt time.Time
|
FinishedAt time.Time
|
||||||
waitChan chan struct{}
|
waitChan chan struct{}
|
||||||
|
@ -137,6 +140,7 @@ func (s *State) SetRunning(pid int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) setRunning(pid int) {
|
func (s *State) setRunning(pid int) {
|
||||||
|
s.Error = ""
|
||||||
s.Running = true
|
s.Running = true
|
||||||
s.Paused = false
|
s.Paused = false
|
||||||
s.Restarting = false
|
s.Restarting = false
|
||||||
|
@ -147,25 +151,26 @@ func (s *State) setRunning(pid int) {
|
||||||
s.waitChan = make(chan struct{})
|
s.waitChan = make(chan struct{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) SetStopped(exitCode int) {
|
func (s *State) SetStopped(exitStatus *execdriver.ExitStatus) {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
s.setStopped(exitCode)
|
s.setStopped(exitStatus)
|
||||||
s.Unlock()
|
s.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) setStopped(exitCode int) {
|
func (s *State) setStopped(exitStatus *execdriver.ExitStatus) {
|
||||||
s.Running = false
|
s.Running = false
|
||||||
s.Restarting = false
|
s.Restarting = false
|
||||||
s.Pid = 0
|
s.Pid = 0
|
||||||
s.FinishedAt = time.Now().UTC()
|
s.FinishedAt = time.Now().UTC()
|
||||||
s.ExitCode = exitCode
|
s.ExitCode = exitStatus.ExitCode
|
||||||
|
s.OOMKilled = exitStatus.OOMKilled
|
||||||
close(s.waitChan) // fire waiters for stop
|
close(s.waitChan) // fire waiters for stop
|
||||||
s.waitChan = make(chan struct{})
|
s.waitChan = make(chan struct{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRestarting is when docker hanldes the auto restart of containers when they are
|
// SetRestarting is when docker hanldes the auto restart of containers when they are
|
||||||
// in the middle of a stop and being restarted again
|
// in the middle of a stop and being restarted again
|
||||||
func (s *State) SetRestarting(exitCode int) {
|
func (s *State) SetRestarting(exitStatus *execdriver.ExitStatus) {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
// we should consider the container running when it is restarting because of
|
// we should consider the container running when it is restarting because of
|
||||||
// all the checks in docker around rm/stop/etc
|
// all the checks in docker around rm/stop/etc
|
||||||
|
@ -173,12 +178,20 @@ func (s *State) SetRestarting(exitCode int) {
|
||||||
s.Restarting = true
|
s.Restarting = true
|
||||||
s.Pid = 0
|
s.Pid = 0
|
||||||
s.FinishedAt = time.Now().UTC()
|
s.FinishedAt = time.Now().UTC()
|
||||||
s.ExitCode = exitCode
|
s.ExitCode = exitStatus.ExitCode
|
||||||
|
s.OOMKilled = exitStatus.OOMKilled
|
||||||
close(s.waitChan) // fire waiters for stop
|
close(s.waitChan) // fire waiters for stop
|
||||||
s.waitChan = make(chan struct{})
|
s.waitChan = make(chan struct{})
|
||||||
s.Unlock()
|
s.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setError sets the container's error state. This is useful when we want to
|
||||||
|
// know the error that occurred when container transits to another state
|
||||||
|
// when inspecting it
|
||||||
|
func (s *State) setError(err error) {
|
||||||
|
s.Error = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *State) IsRestarting() bool {
|
func (s *State) IsRestarting() bool {
|
||||||
s.Lock()
|
s.Lock()
|
||||||
res := s.Restarting
|
res := s.Restarting
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStateRunStop(t *testing.T) {
|
func TestStateRunStop(t *testing.T) {
|
||||||
|
@ -47,7 +49,7 @@ func TestStateRunStop(t *testing.T) {
|
||||||
atomic.StoreInt64(&exit, int64(exitCode))
|
atomic.StoreInt64(&exit, int64(exitCode))
|
||||||
close(stopped)
|
close(stopped)
|
||||||
}()
|
}()
|
||||||
s.SetStopped(i)
|
s.SetStopped(&execdriver.ExitStatus{i, false})
|
||||||
if s.IsRunning() {
|
if s.IsRunning() {
|
||||||
t.Fatal("State is running")
|
t.Fatal("State is running")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package daemon
|
package daemon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -32,9 +33,9 @@ func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostCon
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeLxcConfIntoOptions(hostConfig *runconfig.HostConfig) []string {
|
func mergeLxcConfIntoOptions(hostConfig *runconfig.HostConfig) ([]string, error) {
|
||||||
if hostConfig == nil {
|
if hostConfig == nil {
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
out := []string{}
|
out := []string{}
|
||||||
|
@ -44,10 +45,13 @@ func mergeLxcConfIntoOptions(hostConfig *runconfig.HostConfig) []string {
|
||||||
for _, pair := range lxcConf {
|
for _, pair := range lxcConf {
|
||||||
// because lxc conf gets the driver name lxc.XXXX we need to trim it off
|
// because lxc conf gets the driver name lxc.XXXX we need to trim it off
|
||||||
// and let the lxc driver add it back later if needed
|
// and let the lxc driver add it back later if needed
|
||||||
|
if !strings.Contains(pair.Key, ".") {
|
||||||
|
return nil, errors.New("Illegal Key passed into LXC Configurations")
|
||||||
|
}
|
||||||
parts := strings.SplitN(pair.Key, ".", 2)
|
parts := strings.SplitN(pair.Key, ".", 2)
|
||||||
out = append(out, fmt.Sprintf("%s=%s", parts[1], pair.Value))
|
out = append(out, fmt.Sprintf("%s=%s", parts[1], pair.Value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue