mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-19 12:16:30 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf369bd1b8 |
@@ -1,2 +0,0 @@
|
||||
bundles
|
||||
.gopath
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
image: dockercore/docker
|
||||
env:
|
||||
- AUTO_GOPATH=1
|
||||
- DOCKER_GRAPHDRIVER=vfs
|
||||
- DOCKER_EXECDRIVER=native
|
||||
script:
|
||||
# Setup the DockerInDocker environment.
|
||||
- hack/dind
|
||||
# Tests relying on StartWithBusybox make Drone time out.
|
||||
- rm integration-cli/docker_cli_daemon_test.go
|
||||
- rm integration-cli/docker_cli_exec_test.go
|
||||
# Validate and test.
|
||||
- hack/make.sh validate-dco validate-gofmt
|
||||
- hack/make.sh binary cross test-unit test-integration-cli test-integration
|
||||
+2
-26
@@ -1,30 +1,6 @@
|
||||
# Docker project generated files to ignore
|
||||
# if you want to ignore files created by your editor/tools,
|
||||
# please consider a global .gitignore https://help.github.com/articles/ignoring-files
|
||||
.vagrant*
|
||||
bin
|
||||
.vagrant
|
||||
docker/docker
|
||||
*.exe
|
||||
dockerd/dockerd
|
||||
.*.swp
|
||||
a.out
|
||||
*.orig
|
||||
build_src
|
||||
.flymake*
|
||||
.idea
|
||||
.DS_Store
|
||||
docs/_build
|
||||
docs/_static
|
||||
docs/_templates
|
||||
.gopath/
|
||||
.dotcloud
|
||||
*.test
|
||||
bundles/
|
||||
.hg/
|
||||
.git/
|
||||
vendor/pkg/
|
||||
pyenv
|
||||
Vagrantfile
|
||||
docs/AWS_S3_BUCKET
|
||||
docs/GIT_BRANCH
|
||||
docs/VERSION
|
||||
docs/GITCOMMIT
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
# Generate AUTHORS: project/generate-authors.sh
|
||||
|
||||
# Tip for finding duplicates (besides scanning the output of AUTHORS for name
|
||||
# duplicates that aren't also email duplicates): scan the output of:
|
||||
# git log --format='%aE - %aN' | sort -uf
|
||||
#
|
||||
# For explanation on this file format: man git-shortlog
|
||||
|
||||
<charles.hooper@dotcloud.com> <chooper@plumata.com>
|
||||
<daniel.mizyrycki@dotcloud.com> <daniel@dotcloud.com>
|
||||
<daniel.mizyrycki@dotcloud.com> <mzdaniel@glidelink.net>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com> <charmes.guillaume@gmail.com>
|
||||
<guillaume.charmes@docker.com> <guillaume@dotcloud.com>
|
||||
<guillaume.charmes@docker.com> <guillaume@docker.com>
|
||||
<guillaume.charmes@docker.com> <guillaume.charmes@dotcloud.com>
|
||||
<guillaume.charmes@docker.com> <guillaume@charmes.net>
|
||||
<kencochrane@gmail.com> <KenCochrane@gmail.com>
|
||||
Thatcher Peskens <thatcher@docker.com>
|
||||
Thatcher Peskens <thatcher@docker.com> <thatcher@dotcloud.com>
|
||||
Thatcher Peskens <thatcher@docker.com> dhrp <thatcher@gmx.net>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com> jpetazzo <jerome.petazzoni@dotcloud.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com> <jp@enix.org>
|
||||
Joffrey F <joffrey@docker.com>
|
||||
Joffrey F <joffrey@docker.com> <joffrey@dotcloud.com>
|
||||
Joffrey F <joffrey@docker.com> <f.joffrey@gmail.com>
|
||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||
Andy Smith <github@anarkystic.com>
|
||||
<kalessin@kalessin.fr> <louis@dotcloud.com>
|
||||
<victor.vieux@docker.com> <victor.vieux@dotcloud.com>
|
||||
<victor.vieux@docker.com> <victor@dotcloud.com>
|
||||
<victor.vieux@docker.com> <dev@vvieux.com>
|
||||
<victor.vieux@docker.com> <victor@docker.com>
|
||||
<victor.vieux@docker.com> <vieux@docker.com>
|
||||
<victor.vieux@docker.com> <victorvieux@gmail.com>
|
||||
<dominik@honnef.co> <dominikh@fork-bomb.org>
|
||||
<ehanchrow@ine.com> <eric.hanchrow@gmail.com>
|
||||
Walter Stanish <walter@pratyeka.org>
|
||||
<daniel@gasienica.ch> <dgasienica@zynga.com>
|
||||
Roberto Hashioka <roberto_hashioka@hotmail.com>
|
||||
Konstantin Pelykh <kpelykh@zettaset.com>
|
||||
David Sissitka <me@dsissitka.com>
|
||||
Nolan Darilek <nolan@thewordnerd.info>
|
||||
<mastahyeti@gmail.com> <mastahyeti@users.noreply.github.com>
|
||||
Benoit Chesneau <bchesneau@gmail.com>
|
||||
Jordan Arentsen <blissdev@gmail.com>
|
||||
Daniel Garcia <daniel@danielgarcia.info>
|
||||
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
||||
Bhiraj Butala <abhiraj.butala@gmail.com>
|
||||
Faiz Khan <faizkhan00@gmail.com>
|
||||
Victor Lyuboslavsky <victor@victoreda.com>
|
||||
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
||||
Matthew Mueller <mattmuelle@gmail.com>
|
||||
<mosoni@ebay.com> <mohitsoni1989@gmail.com>
|
||||
Shih-Yuan Lee <fourdollars@gmail.com>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com> root <root@vagrant-ubuntu-12.10.vagrantup.com>
|
||||
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||
<proppy@google.com> <proppy@aminche.com>
|
||||
<michael@docker.com> <michael@crosbymichael.com>
|
||||
<michael@docker.com> <crosby.michael@gmail.com>
|
||||
<michael@docker.com> <crosbymichael@gmail.com>
|
||||
<github@developersupport.net> <github@metaliveblog.com>
|
||||
<brandon@ifup.org> <brandon@ifup.co>
|
||||
<dano@spotify.com> <daniel.norberg@gmail.com>
|
||||
<danny@codeaholics.org> <Danny.Yates@mailonline.co.uk>
|
||||
<gurjeet@singh.im> <singh.gurjeet@gmail.com>
|
||||
<shawn@churchofgit.com> <shawnlandden@gmail.com>
|
||||
<sjoerd-github@linuxonly.nl> <sjoerd@byte.nl>
|
||||
<solomon@docker.com> <solomon.hykes@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> <SvenDowideit@fosiki.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@users.noreply.github.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <sven@t440s.home.gateway>
|
||||
unclejack <unclejacksons@gmail.com> <unclejack@users.noreply.github.com>
|
||||
<alexl@redhat.com> <alexander.larsson@gmail.com>
|
||||
Alexandr Morozov <lk4d4math@gmail.com>
|
||||
<git.nivoc@neverbox.com> <kuehnle@online.de>
|
||||
O.S. Tezer <ostezer@gmail.com>
|
||||
<ostezer@gmail.com> <ostezer@users.noreply.github.com>
|
||||
Roberto G. Hashioka <roberto.hashioka@docker.com> <roberto_hashioka@hotmail.com>
|
||||
<justin.p.simonelis@gmail.com> <justin.simonelis@PTS-JSIMON2.toronto.exclamation.com>
|
||||
<taim@bosboot.org> <maztaim@users.noreply.github.com>
|
||||
<viktor.vojnovski@amadeus.com> <vojnovski@gmail.com>
|
||||
<vbatts@redhat.com> <vbatts@hashbangbash.com>
|
||||
<altsysrq@gmail.com> <iamironbob@gmail.com>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com> <github@srid.name>
|
||||
Liang-Chi Hsieh <viirya@gmail.com>
|
||||
Aleksa Sarai <cyphar@cyphar.com>
|
||||
Will Weaver <monkey@buildingbananas.com>
|
||||
Timothy Hobbs <timothyhobbs@seznam.cz>
|
||||
Nathan LeClaire <nathan.leclaire@docker.com> <nathan.leclaire@gmail.com>
|
||||
Nathan LeClaire <nathan.leclaire@docker.com> <nathanleclaire@gmail.com>
|
||||
<github@hollensbe.org> <erik+github@hollensbe.org>
|
||||
<github@albersweb.de> <albers@users.noreply.github.com>
|
||||
<lsm5@fedoraproject.org> <lsm5@redhat.com>
|
||||
<marc@marc-abramowitz.com> <msabramo@gmail.com>
|
||||
Matthew Heon <mheon@redhat.com> <mheon@mheonlaptop.redhat.com>
|
||||
<bernat@luffy.cx> <vincent@bernat.im>
|
||||
<p@pwaller.net> <peter@scraperwiki.com>
|
||||
<andrew.weiss@outlook.com> <andrew.weiss@microsoft.com>
|
||||
Francisco Carriedo <fcarriedo@gmail.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>
|
||||
@@ -1,718 +0,0 @@
|
||||
# This file lists all individuals having contributed content to the repository.
|
||||
# For how it is generated, see `project/generate-authors.sh`.
|
||||
|
||||
Aanand Prasad <aanand.prasad@gmail.com>
|
||||
Aaron Feng <aaron.feng@gmail.com>
|
||||
Aaron Huslage <huslage@gmail.com>
|
||||
Abel Muiño <amuino@gmail.com>
|
||||
Abhinav Ajgaonkar <abhinav316@gmail.com>
|
||||
Abin Shahab <ashahab@altiscale.com>
|
||||
Adam Miller <admiller@redhat.com>
|
||||
Adam Singer <financeCoding@gmail.com>
|
||||
Aditya <aditya@netroy.in>
|
||||
Adrian Mouat <adrian.mouat@gmail.com>
|
||||
Adrien Folie <folie.adrien@gmail.com>
|
||||
Ahmet Alp Balkan <ahmetb@microsoft.com>
|
||||
AJ Bowen <aj@gandi.net>
|
||||
alambike <alambike@gmail.com>
|
||||
Alan Thompson <cloojure@gmail.com>
|
||||
Albert Callarisa <shark234@gmail.com>
|
||||
Albert Zhang <zhgwenming@gmail.com>
|
||||
Aleksa Sarai <cyphar@cyphar.com>
|
||||
Alexander Larsson <alexl@redhat.com>
|
||||
Alexander Shopov <ash@kambanaria.org>
|
||||
Alexandr Morozov <lk4d4@docker.com>
|
||||
Alexey Kotlyarov <alexey@infoxchange.net.au>
|
||||
Alexey Shamrin <shamrin@gmail.com>
|
||||
Alex Gaynor <alex.gaynor@gmail.com>
|
||||
Alexis THOMAS <fr.alexisthomas@gmail.com>
|
||||
Alex Warhawk <ax.warhawk@gmail.com>
|
||||
almoehi <almoehi@users.noreply.github.com>
|
||||
Al Tobey <al@ooyala.com>
|
||||
Álvaro Lázaro <alvaro.lazaro.g@gmail.com>
|
||||
amangoel <amangoel@gmail.com>
|
||||
Amit Bakshi <ambakshi@gmail.com>
|
||||
AnandkumarPatel <anandkumarpatel@gmail.com>
|
||||
Anand Patil <anand.prabhakar.patil@gmail.com>
|
||||
Andrea Luzzardi <aluzzardi@gmail.com>
|
||||
Andreas Köhler <andi5.py@gmx.net>
|
||||
Andreas Savvides <andreas@editd.com>
|
||||
Andreas Tiefenthaler <at@an-ti.eu>
|
||||
Andrea Turli <andrea.turli@gmail.com>
|
||||
Andre Dublin <81dublin@gmail.com>
|
||||
Andrew Duckworth <grillopress@gmail.com>
|
||||
Andrew France <andrew@avito.co.uk>
|
||||
Andrew Macgregor <andrew.macgregor@agworld.com.au>
|
||||
Andrew Munsell <andrew@wizardapps.net>
|
||||
Andrews Medina <andrewsmedina@gmail.com>
|
||||
Andrew Weiss <andrew.weiss@outlook.com>
|
||||
Andrew Williams <williams.andrew@gmail.com>
|
||||
Andrey Petrov <andrey.petrov@shazow.net>
|
||||
Andrey Stolbovsky <andrey.stolbovsky@gmail.com>
|
||||
Andy Chambers <anchambers@paypal.com>
|
||||
andy diller <dillera@gmail.com>
|
||||
Andy Goldstein <agoldste@redhat.com>
|
||||
Andy Kipp <andy@rstudio.com>
|
||||
Andy Rothfusz <github@developersupport.net>
|
||||
Andy Smith <github@anarkystic.com>
|
||||
Andy Wilson <wilson.andrew.j+github@gmail.com>
|
||||
Anthony Bishopric <git@anthonybishopric.com>
|
||||
Anton Löfgren <anton.lofgren@gmail.com>
|
||||
Anton Nikitin <anton.k.nikitin@gmail.com>
|
||||
Antony Messerli <amesserl@rackspace.com>
|
||||
apocas <petermdias@gmail.com>
|
||||
ArikaChen <eaglesora@gmail.com>
|
||||
Arnaud Porterie <arnaud.porterie@docker.com>
|
||||
Arthur Gautier <baloo@gandi.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>
|
||||
Barry Allard <barry.allard@gmail.com>
|
||||
Bartłomiej Piotrowski <b@bpiotrowski.pl>
|
||||
bdevloed <boris.de.vloed@gmail.com>
|
||||
Ben Firshman <ben@firshman.co.uk>
|
||||
Benjamin Atkin <ben@benatkin.com>
|
||||
Benoit Chesneau <bchesneau@gmail.com>
|
||||
Ben Sargent <ben@brokendigits.com>
|
||||
Ben Toews <mastahyeti@gmail.com>
|
||||
Ben Wiklund <ben@daisyowl.com>
|
||||
Bernerd Schaefer <bj.schaefer@gmail.com>
|
||||
Bert Goethals <bert@bertg.be>
|
||||
Bhiraj Butala <abhiraj.butala@gmail.com>
|
||||
bin liu <liubin0329@users.noreply.github.com>
|
||||
Blake Geno <blakegeno@gmail.com>
|
||||
Bouke Haarsma <bouke@webatoom.nl>
|
||||
Boyd Hemphill <boyd@feedmagnet.com>
|
||||
Brandon Liu <bdon@bdon.org>
|
||||
Brandon Philips <brandon@ifup.org>
|
||||
Brandon Rhodes <brandon@rhodesmill.org>
|
||||
Brett Kochendorfer <brett.kochendorfer@gmail.com>
|
||||
Brian (bex) Exelbierd <bexelbie@redhat.com>
|
||||
Brian Dorsey <brian@dorseys.org>
|
||||
Brian Flad <bflad417@gmail.com>
|
||||
Brian Goff <cpuguy83@gmail.com>
|
||||
Brian McCallister <brianm@skife.org>
|
||||
Brian Olsen <brian@maven-group.org>
|
||||
Brian Shumate <brian@couchbase.com>
|
||||
Brice Jaglin <bjaglin@teads.tv>
|
||||
Briehan Lombaard <briehan.lombaard@gmail.com>
|
||||
Bruno Bigras <bigras.bruno@gmail.com>
|
||||
Bruno Binet <bruno.binet@gmail.com>
|
||||
Bruno Renié <brutasse@gmail.com>
|
||||
Bryan Bess <squarejaw@bsbess.com>
|
||||
Bryan Matsuo <bryan.matsuo@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>
|
||||
Calen Pennington <cale@edx.org>
|
||||
Cameron Boehmer <cameron.boehmer@gmail.com>
|
||||
Carl X. Su <bcbcarl@gmail.com>
|
||||
Charles Hooper <charles.hooper@dotcloud.com>
|
||||
Charles Lindsay <chaz@chazomatic.us>
|
||||
Charles Merriam <charles.merriam@gmail.com>
|
||||
Charlie Lewis <charliel@lab41.org>
|
||||
Chewey <prosto-chewey@users.noreply.github.com>
|
||||
Chia-liang Kao <clkao@clkao.org>
|
||||
Chris Alfonso <calfonso@redhat.com>
|
||||
Chris Armstrong <chris@opdemand.com>
|
||||
chrismckinnel <chris.mckinnel@tangentlabs.co.uk>
|
||||
Chris Snow <chsnow123@gmail.com>
|
||||
Chris St. Pierre <chris.a.st.pierre@gmail.com>
|
||||
Christian Berendt <berendt@b1-systems.de>
|
||||
ChristoperBiscardi <biscarch@sketcht.com>
|
||||
Christopher Currie <codemonkey+github@gmail.com>
|
||||
Christopher Rigor <crigor@gmail.com>
|
||||
Christophe Troestler <christophe.Troestler@umons.ac.be>
|
||||
Ciro S. Costa <ciro.costa@usp.br>
|
||||
Clayton Coleman <ccoleman@redhat.com>
|
||||
Colin Dunklau <colin.dunklau@gmail.com>
|
||||
Colin Rice <colin@daedrum.net>
|
||||
Colin Walters <walters@verbum.org>
|
||||
Cory Forsyth <cory.forsyth@gmail.com>
|
||||
cressie176 <github@stephen-cresswell.net>
|
||||
Cruceru Calin-Cristian <crucerucalincristian@gmail.com>
|
||||
Daan van Berkel <daan.v.berkel.1980@gmail.com>
|
||||
Daehyeok.Mun <daehyeok@gmail.com>
|
||||
Dafydd Crosby <dtcrsby@gmail.com>
|
||||
Dan Buch <d.buch@modcloth.com>
|
||||
Dan Cotora <dan@bluevision.ro>
|
||||
Dan Griffin <dgriffin@peer1.com>
|
||||
Dan Hirsch <thequux@upstandinghackers.com>
|
||||
Daniel, Dao Quang Minh <dqminh89@gmail.com>
|
||||
Daniel Exner <dex@dragonslave.de>
|
||||
Daniel Farrell <dfarrell@redhat.com>
|
||||
Daniel Garcia <daniel@danielgarcia.info>
|
||||
Daniel Gasienica <daniel@gasienica.ch>
|
||||
Daniel Menet <membership@sontags.ch>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
|
||||
Daniel Norberg <dano@spotify.com>
|
||||
Daniel Nordberg <dnordberg@gmail.com>
|
||||
Daniel Robinson <gottagetmac@gmail.com>
|
||||
Daniel Von Fange <daniel@leancoder.com>
|
||||
Daniel YC Lin <dlin.tw@gmail.com>
|
||||
Dan Keder <dan.keder@gmail.com>
|
||||
Dan McPherson <dmcphers@redhat.com>
|
||||
Danny Berger <dpb587@gmail.com>
|
||||
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 Shepherd <darren.s.shepherd@gmail.com>
|
||||
David Anderson <dave@natulte.net>
|
||||
David Calavera <david.calavera@gmail.com>
|
||||
David Corking <dmc-source@dcorking.com>
|
||||
Davide Ceretti <davide.ceretti@hogarthww.com>
|
||||
David Gageot <david@gageot.net>
|
||||
David Gebler <davidgebler@gmail.com>
|
||||
David Mcanulty <github@hellspark.com>
|
||||
David Pelaez <pelaez89@gmail.com>
|
||||
David Röthlisberger <david@rothlis.net>
|
||||
David Sissitka <me@dsissitka.com>
|
||||
Dawn Chen <dawnchen@google.com>
|
||||
decadent <decadent@users.noreply.github.com>
|
||||
Deni Bertovic <deni@kset.org>
|
||||
Derek <crq@kernel.org>
|
||||
Derek McGowan <derek@mcgstyle.net>
|
||||
Deric Crago <deric.crago@gmail.com>
|
||||
Deshi Xiao <dxiao@redhat.com>
|
||||
Dinesh Subhraveti <dineshs@altiscale.com>
|
||||
Djibril Koné <kone.djibril@gmail.com>
|
||||
dkumor <daniel@dkumor.com>
|
||||
Dmitry Demeshchuk <demeshchuk@gmail.com>
|
||||
Dolph Mathews <dolph.mathews@gmail.com>
|
||||
Dominik Honnef <dominik@honnef.co>
|
||||
Don Spaulding <donspauldingii@gmail.com>
|
||||
Doug Davis <dug@us.ibm.com>
|
||||
doug tangren <d.tangren@gmail.com>
|
||||
dragon788 <dragon788@users.noreply.github.com>
|
||||
Dražen Lučanin <kermit666@gmail.com>
|
||||
Dr Nic Williams <drnicwilliams@gmail.com>
|
||||
Dustin Sallings <dustin@spy.net>
|
||||
Edmund Wagner <edmund-wagner@web.de>
|
||||
Eiichi Tsukata <devel@etsukata.com>
|
||||
Eike Herzbach <eike@herzbach.net>
|
||||
Eivind Uggedal <eivind@uggedal.com>
|
||||
Elias Probst <mail@eliasprobst.eu>
|
||||
Emil Hernvall <emil@quench.at>
|
||||
Emily Rose <emily@contactvibe.com>
|
||||
Eric Hanchrow <ehanchrow@ine.com>
|
||||
Eric Lee <thenorthsecedes@gmail.com>
|
||||
Eric Myhre <hash@exultant.us>
|
||||
Eric Paris <eparis@redhat.com>
|
||||
Eric Windisch <ewindisch@docker.com>
|
||||
Erik Hollensbe <github@hollensbe.org>
|
||||
Erik Inge Bolsø <knan@redpill-linpro.com>
|
||||
Erik Kristensen <erik@erikkristensen.com>
|
||||
Erno Hopearuoho <erno.hopearuoho@gmail.com>
|
||||
Eugene Yakubovich <eugene.yakubovich@coreos.com>
|
||||
eugenkrizo <eugen.krizo@gmail.com>
|
||||
evanderkoogh <info@erronis.nl>
|
||||
Evan Hazlett <ejhazlett@gmail.com>
|
||||
Evan Krall <krall@yelp.com>
|
||||
Evan Phoenix <evan@fallingsnow.net>
|
||||
Evan Wies <evan@neomantra.net>
|
||||
Eystein Måløy Stenberg <eystein.maloy.stenberg@cfengine.com>
|
||||
ezbercih <cem.ezberci@gmail.com>
|
||||
Fabio Falci <fabiofalci@gmail.com>
|
||||
Fabio Rehm <fgrehm@gmail.com>
|
||||
Fabrizio Regini <freegenie@gmail.com>
|
||||
Faiz Khan <faizkhan00@gmail.com>
|
||||
Fareed Dudhia <fareeddudhia@googlemail.com>
|
||||
Felix Rabe <felix@rabe.io>
|
||||
Fernando <fermayo@gmail.com>
|
||||
Filipe Brandenburger <filbranden@google.com>
|
||||
Flavio Castelli <fcastelli@suse.com>
|
||||
FLGMwt <ryan.stelly@live.com>
|
||||
Francisco Carriedo <fcarriedo@gmail.com>
|
||||
Francisco Souza <f@souza.cc>
|
||||
Frank Macreery <frank@macreery.com>
|
||||
Frank Rosquin <frank.rosquin+github@gmail.com>
|
||||
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
||||
Frederik Loeffert <frederik@zitrusmedia.de>
|
||||
Fred Lifton <fred.lifton@docker.com>
|
||||
Freek Kalter <freek@kalteronline.org>
|
||||
Gabe Rosenhouse <gabe@missionst.com>
|
||||
Gabor Nagy <mail@aigeruth.hu>
|
||||
Gabriel Monroy <gabriel@opdemand.com>
|
||||
Galen Sampson <galen.sampson@gmail.com>
|
||||
Gareth Rushgrove <gareth@morethanseven.net>
|
||||
gautam, prasanna <prasannagautam@gmail.com>
|
||||
Geoffrey Bachelet <grosfrais@gmail.com>
|
||||
George Xie <georgexsh@gmail.com>
|
||||
Gereon Frey <gereon.frey@dynport.de>
|
||||
German DZ <germ@ndz.com.ar>
|
||||
Gert van Valkenhoef <g.h.m.van.valkenhoef@rug.nl>
|
||||
Giuseppe Mazzotta <gdm85@users.noreply.github.com>
|
||||
Gleb Fotengauer-Malinovskiy <glebfm@altlinux.org>
|
||||
Gleb M Borisov <borisov.gleb@gmail.com>
|
||||
Glyn Normington <gnormington@gopivotal.com>
|
||||
Goffert van Gool <goffert@phusion.nl>
|
||||
golubbe <ben.golub@dotcloud.com>
|
||||
Graydon Hoare <graydon@pobox.com>
|
||||
Greg Thornton <xdissent@me.com>
|
||||
grunny <mwgrunny@gmail.com>
|
||||
Guilherme Salgado <gsalgado@gmail.com>
|
||||
Guillaume Dufour <gdufour.prestataire@voyages-sncf.com>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com>
|
||||
Gurjeet Singh <gurjeet@singh.im>
|
||||
Guruprasad <lgp171188@gmail.com>
|
||||
Hans Rødtang <hansrodtang@gmail.com>
|
||||
Harald Albers <github@albersweb.de>
|
||||
Harley Laue <losinggeneration@gmail.com>
|
||||
Hector Castro <hectcastro@gmail.com>
|
||||
Henning Sprang <henning.sprang@gmail.com>
|
||||
Hobofan <goisser94@gmail.com>
|
||||
Hollie Teal <hollie@docker.com>
|
||||
Huayi Zhang <irachex@gmail.com>
|
||||
Hugo Duncan <hugo@hugoduncan.org>
|
||||
Hunter Blanks <hunter@twilio.com>
|
||||
Hu Tao <hutao@cn.fujitsu.com>
|
||||
Huu Nguyen <huu@prismskylabs.com>
|
||||
hyeongkyu.lee <hyeongkyu.lee@navercorp.com>
|
||||
Ian Babrou <ibobrik@gmail.com>
|
||||
Ian Bull <irbull@gmail.com>
|
||||
Ian Main <imain@redhat.com>
|
||||
Ian Truslove <ian.truslove@gmail.com>
|
||||
Igor Dolzhikov <bluesriverz@gmail.com>
|
||||
ILYA Khlopotov <ilya.khlopotov@gmail.com>
|
||||
inglesp <peter.inglesby@gmail.com>
|
||||
Isaac Dupree <antispam@idupree.com>
|
||||
Isabel Jimenez <contact.isabeljimenez@gmail.com>
|
||||
Isao Jonas <isao.jonas@gmail.com>
|
||||
Ivan Fraixedes <ifcdev@gmail.com>
|
||||
Jack Danger Canty <jackdanger@squareup.com>
|
||||
jakedt <jake@devtable.com>
|
||||
Jake Moshenko <jake@devtable.com>
|
||||
James Allen <jamesallen0108@gmail.com>
|
||||
James Carr <james.r.carr@gmail.com>
|
||||
James DeFelice <james.defelice@ishisystems.com>
|
||||
James Harrison Fisher <jameshfisher@gmail.com>
|
||||
James Kyle <james@jameskyle.org>
|
||||
James Mills <prologic@shortcircuit.net.au>
|
||||
James Turnbull <james@lovedthanlost.net>
|
||||
Jan Keromnes <janx@linux.com>
|
||||
Jan Pazdziora <jpazdziora@redhat.com>
|
||||
Jan Toebes <jan@toebes.info>
|
||||
Jaroslaw Zabiello <hipertracker@gmail.com>
|
||||
jaseg <jaseg@jaseg.net>
|
||||
Jason Giedymin <jasong@apache.org>
|
||||
Jason Hall <imjasonh@gmail.com>
|
||||
Jason Livesay <ithkuil@gmail.com>
|
||||
Jason McVetta <jason.mcvetta@gmail.com>
|
||||
Jason Plum <jplum@devonit.com>
|
||||
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
||||
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||
Jean-Paul Calderone <exarkun@twistedmatrix.com>
|
||||
Jeff Lindsay <progrium@gmail.com>
|
||||
Jeffrey Bolle <jeffreybolle@gmail.com>
|
||||
Jeff Welch <whatthejeff@gmail.com>
|
||||
Jeremy Grosser <jeremy@synack.me>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
|
||||
Jesse Dubay <jesse@thefortytwo.net>
|
||||
Jessica Frazelle <jess@docker.com>
|
||||
Jezeniel Zapanta <jpzapanta22@gmail.com>
|
||||
Jilles Oldenbeuving <ojilles@gmail.com>
|
||||
Jim Alateras <jima@comware.com.au>
|
||||
Jimmy Cuadra <jimmy@jimmycuadra.com>
|
||||
Jim Perrin <jperrin@centos.org>
|
||||
Jiří Župka <jzupka@redhat.com>
|
||||
Joe Beda <joe.github@bedafamily.com>
|
||||
Joe Ferguson <joe@infosiftr.com>
|
||||
Joel Handwell <joelhandwell@gmail.com>
|
||||
Joe Shaw <joe@joeshaw.org>
|
||||
Joe Van Dyk <joe@tanga.com>
|
||||
Joffrey F <joffrey@docker.com>
|
||||
Johan Euphrosine <proppy@google.com>
|
||||
Johannes 'fish' Ziemke <github@freigeist.org>
|
||||
Johan Rydberg <johan.rydberg@gmail.com>
|
||||
John Costa <john.costa@gmail.com>
|
||||
John Feminella <jxf@jxf.me>
|
||||
John Gardiner Myers <jgmyers@proofpoint.com>
|
||||
John Gossman <johngos@microsoft.com>
|
||||
John OBrien III <jobrieniii@yahoo.com>
|
||||
John Warwick <jwarwick@gmail.com>
|
||||
Jonas Pfenniger <jonas@pfenniger.name>
|
||||
Jonathan Boulle <jonathanboulle@gmail.com>
|
||||
Jonathan Camp <jonathan@irondojo.com>
|
||||
Jonathan McCrohan <jmccrohan@gmail.com>
|
||||
Jonathan Mueller <j.mueller@apoveda.ch>
|
||||
Jonathan Pares <jonathanpa@users.noreply.github.com>
|
||||
Jonathan Rudenberg <jonathan@titanous.com>
|
||||
Jon Wedaman <jweede@gmail.com>
|
||||
Joost Cassee <joost@cassee.net>
|
||||
Jordan Arentsen <blissdev@gmail.com>
|
||||
Jordan Sissel <jls@semicomplete.com>
|
||||
Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
|
||||
Joseph Hager <ajhager@gmail.com>
|
||||
Josh Hawn <josh.hawn@docker.com>
|
||||
Josh <jokajak@gmail.com>
|
||||
Josh Poimboeuf <jpoimboe@redhat.com>
|
||||
Josiah Kiehl <jkiehl@riotgames.com>
|
||||
JP <jpellerin@leapfrogonline.com>
|
||||
Julian Taylor <jtaylor.debian@googlemail.com>
|
||||
Julien Barbier <write0@gmail.com>
|
||||
Julien Bordellier <julienbordellier@gmail.com>
|
||||
Julien Dubois <julien.dubois@gmail.com>
|
||||
Justin Force <justin.force@gmail.com>
|
||||
Justin Plock <jplock@users.noreply.github.com>
|
||||
Justin Simonelis <justin.p.simonelis@gmail.com>
|
||||
Jyrki Puttonen <jyrkiput@gmail.com>
|
||||
Karan Lyons <karan@karanlyons.com>
|
||||
Karl Grzeszczak <karlgrz@gmail.com>
|
||||
Kato Kazuyoshi <kato.kazuyoshi@gmail.com>
|
||||
Kawsar Saiyeed <kawsar.saiyeed@projiris.com>
|
||||
Keli Hu <dev@keli.hu>
|
||||
Ken Cochrane <kencochrane@gmail.com>
|
||||
Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com>
|
||||
Kevin Clark <kevin.clark@gmail.com>
|
||||
Kevin J. Lynagh <kevin@keminglabs.com>
|
||||
Kevin Menard <kevin@nirvdrum.com>
|
||||
Kevin "qwazerty" Houdebert <kevin.houdebert@gmail.com>
|
||||
Kevin Wallace <kevin@pentabarf.net>
|
||||
Keyvan Fatehi <keyvanfatehi@gmail.com>
|
||||
kies <lleelm@gmail.com>
|
||||
kim0 <email.ahmedkamal@googlemail.com>
|
||||
Kim BKC Carlbacker <kim.carlbacker@gmail.com>
|
||||
Kimbro Staken <kstaken@kstaken.com>
|
||||
Kiran Gangadharan <kiran.daredevil@gmail.com>
|
||||
knappe <tyler.knappe@gmail.com>
|
||||
Kohei Tsuruta <coheyxyz@gmail.com>
|
||||
Konrad Kleine <konrad.wilhelm.kleine@gmail.com>
|
||||
Konstantin Pelykh <kpelykh@zettaset.com>
|
||||
krrg <krrgithub@gmail.com>
|
||||
Kyle Conroy <kyle.j.conroy@gmail.com>
|
||||
kyu <leehk1227@gmail.com>
|
||||
Lachlan Coote <lcoote@vmware.com>
|
||||
Lajos Papp <lajos.papp@sequenceiq.com>
|
||||
Lakshan Perera <lakshan@laktek.com>
|
||||
lalyos <lalyos@yahoo.com>
|
||||
Lance Chen <cyen0312@gmail.com>
|
||||
Lars R. Damerow <lars@pixar.com>
|
||||
Laurie Voss <github@seldo.com>
|
||||
leeplay <hyeongkyu.lee@navercorp.com>
|
||||
Lei Jitang <leijitang@huawei.com>
|
||||
Len Weincier <len@cloudafrica.net>
|
||||
Leszek Kowalski <github@leszekkowalski.pl>
|
||||
Levi Gross <levi@levigross.com>
|
||||
Lewis Peckover <lew+github@lew.io>
|
||||
Liang-Chi Hsieh <viirya@gmail.com>
|
||||
limsy <seongyeol37@gmail.com>
|
||||
Lokesh Mandvekar <lsm5@fedoraproject.org>
|
||||
Louis Opter <kalessin@kalessin.fr>
|
||||
lukaspustina <lukas.pustina@centerdevice.com>
|
||||
lukemarsden <luke@digital-crocus.com>
|
||||
Madhu Venugopal <madhu@socketplane.io>
|
||||
Mahesh Tiyyagura <tmahesh@gmail.com>
|
||||
Malte Janduda <mail@janduda.net>
|
||||
Manfred Zabarauskas <manfredas@zabarauskas.com>
|
||||
Manuel Meurer <manuel@krautcomputing.com>
|
||||
Manuel Woelker <github@manuel.woelker.org>
|
||||
Marc Abramowitz <marc@marc-abramowitz.com>
|
||||
Marc Kuo <kuomarc2@gmail.com>
|
||||
Marco Hennings <marco.hennings@freiheit.com>
|
||||
Marc Tamsky <mtamsky@gmail.com>
|
||||
Marcus Farkas <toothlessgear@finitebox.com>
|
||||
marcuslinke <marcus.linke@gmx.de>
|
||||
Marcus Ramberg <marcus@nordaaker.com>
|
||||
Marek Goldmann <marek.goldmann@gmail.com>
|
||||
Marius Voila <marius.voila@gmail.com>
|
||||
Mark Allen <mrallen1@yahoo.com>
|
||||
Mark McGranaghan <mmcgrana@gmail.com>
|
||||
Marko Mikulicic <mmikulicic@gmail.com>
|
||||
Marko Tibold <marko@tibold.nl>
|
||||
Markus Fix <lispmeister@gmail.com>
|
||||
Martijn van Oosterhout <kleptog@svana.org>
|
||||
Martin Redmond <martin@tinychat.com>
|
||||
Mason Malone <mason.malone@gmail.com>
|
||||
Mateusz Sulima <sulima.mateusz@gmail.com>
|
||||
Mathias Monnerville <mathias@monnerville.com>
|
||||
Mathieu Le Marec - Pasquet <kiorky@cryptelium.net>
|
||||
Matt Apperson <me@mattapperson.com>
|
||||
Matt Bachmann <bachmann.matt@gmail.com>
|
||||
Matt Haggard <haggardii@gmail.com>
|
||||
Matthew Heon <mheon@redhat.com>
|
||||
Matthew Mueller <mattmuelle@gmail.com>
|
||||
Matthias Klumpp <matthias@tenstral.net>
|
||||
Matthias Kühnle <git.nivoc@neverbox.com>
|
||||
mattymo <raytrac3r@gmail.com>
|
||||
mattyw <mattyw@me.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>
|
||||
Mengdi Gao <usrgdd@gmail.com>
|
||||
Mert Yazıcıoğlu <merty@users.noreply.github.com>
|
||||
Michael Brown <michael@netdirect.ca>
|
||||
Michael Crosby <michael@docker.com>
|
||||
Michael Gorsuch <gorsuch@github.com>
|
||||
Michael Hudson-Doyle <michael.hudson@linaro.org>
|
||||
Michael Neale <michael.neale@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>
|
||||
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
||||
Mike Chelen <michael.chelen@gmail.com>
|
||||
Mike Gaffney <mike@uberu.com>
|
||||
Mike MacCana <mike.maccana@gmail.com>
|
||||
Mike Naberezny <mike@naberezny.com>
|
||||
Mike Snitzer <snitzer@redhat.com>
|
||||
Mikhail Sobolev <mss@mawhrin.net>
|
||||
Mohit Soni <mosoni@ebay.com>
|
||||
Morgante Pell <morgante.pell@morgante.net>
|
||||
Morten Siebuhr <sbhr@sbhr.dk>
|
||||
Mrunal Patel <mrunalp@gmail.com>
|
||||
mschurenko <matt.schurenko@gmail.com>
|
||||
Mustafa Akın <mustafa91@gmail.com>
|
||||
Nan Monnand Deng <monnand@gmail.com>
|
||||
Naoki Orii <norii@cs.cmu.edu>
|
||||
Nate Jones <nate@endot.org>
|
||||
Nathan Hsieh <hsieh.nathan@gmail.com>
|
||||
Nathan Kleyn <nathan@nathankleyn.com>
|
||||
Nathan LeClaire <nathan.leclaire@docker.com>
|
||||
Nelson Chen <crazysim@gmail.com>
|
||||
Niall O'Higgins <niallo@unworkable.org>
|
||||
Nicholas E. Rabenau <nerab@gmx.at>
|
||||
Nick Payne <nick@kurai.co.uk>
|
||||
Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
|
||||
Nick Stinemates <nick@stinemates.org>
|
||||
Nicolas De loof <nicolas.deloof@gmail.com>
|
||||
Nicolas Dudebout <nicolas.dudebout@gatech.edu>
|
||||
Nicolas Goy <kuon@goyman.com>
|
||||
Nicolas Kaiser <nikai@nikai.net>
|
||||
NikolaMandic <mn080202@gmail.com>
|
||||
noducks <onemannoducks@gmail.com>
|
||||
Nolan Darilek <nolan@thewordnerd.info>
|
||||
nzwsch <hi@nzwsch.com>
|
||||
OddBloke <daniel@daniel-watkins.co.uk>
|
||||
odk- <github@odkurzacz.org>
|
||||
Oguz Bilgic <fisyonet@gmail.com>
|
||||
Oh Jinkyun <tintypemolly@gmail.com>
|
||||
Ole Reifschneider <mail@ole-reifschneider.de>
|
||||
Olivier Gambier <dmp42@users.noreply.github.com>
|
||||
O.S. Tezer <ostezer@gmail.com>
|
||||
pandrew <letters@paulnotcom.se>
|
||||
Pascal Borreli <pascal@borreli.com>
|
||||
Pascal Hartig <phartig@rdrei.net>
|
||||
Patrick Hemmer <patrick.hemmer@gmail.com>
|
||||
pattichen <craftsbear@gmail.com>
|
||||
Paul Annesley <paul@annesley.cc>
|
||||
Paul Bowsher <pbowsher@globalpersonals.co.uk>
|
||||
Paul Hammond <paul@paulhammond.org>
|
||||
Paul Jimenez <pj@place.org>
|
||||
Paul Lietar <paul@lietar.net>
|
||||
Paul Morie <pmorie@gmail.com>
|
||||
Paul Nasrat <pnasrat@gmail.com>
|
||||
Paul <paul9869@gmail.com>
|
||||
Paul Weaver <pauweave@cisco.com>
|
||||
Pavlos Ratis <dastergon@gentoo.org>
|
||||
Peter Bourgon <peter@bourgon.org>
|
||||
Peter Braden <peterbraden@peterbraden.co.uk>
|
||||
Peter Ericson <pdericson@gmail.com>
|
||||
Peter Salvatore <peter@psftw.com>
|
||||
Peter Waller <p@pwaller.net>
|
||||
Phil Estes <estesp@linux.vnet.ibm.com>
|
||||
Philipp Weissensteiner <mail@philippweissensteiner.com>
|
||||
Phillip Alexander <git@phillipalexander.io>
|
||||
Phil Spitler <pspitler@gmail.com>
|
||||
Phil <underscorephil@gmail.com>
|
||||
Piergiuliano Bossi <pgbossi@gmail.com>
|
||||
Pierre-Alain RIVIERE <pariviere@ippon.fr>
|
||||
Pierre <py@poujade.org>
|
||||
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>
|
||||
Qiang Huang <h.huangqiang@huawei.com>
|
||||
Quentin Brossard <qbrossard@gmail.com>
|
||||
r0n22 <cameron.regan@gmail.com>
|
||||
Rafal Jeczalik <rjeczalik@gmail.com>
|
||||
Rafe Colton <rafael.colton@gmail.com>
|
||||
Rajat Pandit <rp@rajatpandit.com>
|
||||
Rajdeep Dua <dua_rajdeep@yahoo.com>
|
||||
Ralph Bean <rbean@redhat.com>
|
||||
Ramkumar Ramachandra <artagnon@gmail.com>
|
||||
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>
|
||||
rgstephens <greg@udon.org>
|
||||
Rhys Hiltner <rhys@twitch.tv>
|
||||
Richard Harvey <richard@squarecows.com>
|
||||
Richo Healey <richo@psych0tik.net>
|
||||
Rick Bradley <rick@users.noreply.github.com>
|
||||
Rick van de Loo <rickvandeloo@gmail.com>
|
||||
Robert Bachmann <rb@robertbachmann.at>
|
||||
Robert Bittle <guywithnose@gmail.com>
|
||||
Robert Obryk <robryk@gmail.com>
|
||||
Roberto G. Hashioka <roberto.hashioka@docker.com>
|
||||
Robin Speekenbrink <robin@kingsquare.nl>
|
||||
robpc <rpcann@gmail.com>
|
||||
Rodrigo Vaz <rodrigo.vaz@gmail.com>
|
||||
Roel Van Nyen <roel.vannyen@gmail.com>
|
||||
Roger Peppe <rogpeppe@gmail.com>
|
||||
Rohit Jnagal <jnagal@google.com>
|
||||
Roland Huß <roland@jolokia.org>
|
||||
Roland Moriz <rmoriz@users.noreply.github.com>
|
||||
Ron Smits <ron.smits@gmail.com>
|
||||
Rovanion Luckey <rovanion.luckey@gmail.com>
|
||||
Rudolph Gottesheim <r.gottesheim@loot.at>
|
||||
Ryan Anderson <anderson.ryanc@gmail.com>
|
||||
Ryan Aslett <github@mixologic.com>
|
||||
Ryan Detzel <ryan.detzel@gmail.com>
|
||||
Ryan Fowler <rwfowler@gmail.com>
|
||||
Ryan O'Donnell <odonnellryanc@gmail.com>
|
||||
Ryan Seto <ryanseto@yak.net>
|
||||
Ryan Thomas <rthomas@atlassian.com>
|
||||
Sam Alba <sam.alba@gmail.com>
|
||||
Sam Bailey <cyprix@cyprix.com.au>
|
||||
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
|
||||
Sam Reis <sreis@atlassian.com>
|
||||
Sam Rijs <srijs@airpost.net>
|
||||
Samuel Andaya <samuel@andaya.net>
|
||||
Samuel PHAN <samuel-phan@users.noreply.github.com>
|
||||
satoru <satorulogic@gmail.com>
|
||||
Satoshi Amemiya <satoshi_amemiya@voyagegroup.com>
|
||||
Scott Bessler <scottbessler@gmail.com>
|
||||
Scott Collier <emailscottcollier@gmail.com>
|
||||
Scott Johnston <scott@docker.com>
|
||||
Scott Walls <sawalls@umich.edu>
|
||||
Sean Cronin <seancron@gmail.com>
|
||||
Sean P. Kane <skane@newrelic.com>
|
||||
Sebastiaan van Stijn <github@gone.nl>
|
||||
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>
|
||||
SeongJae Park <sj38.park@gmail.com>
|
||||
Shane Canon <scanon@lbl.gov>
|
||||
shaunol <shaunol@gmail.com>
|
||||
Shawn Landden <shawn@churchofgit.com>
|
||||
Shawn Siefkas <shawn.siefkas@meredith.com>
|
||||
Shih-Yuan Lee <fourdollars@gmail.com>
|
||||
shuai-z <zs.broccoli@gmail.com>
|
||||
Silas Sewell <silas@sewell.org>
|
||||
Simon Taranto <simon.taranto@gmail.com>
|
||||
Sindhu S <sindhus@live.in>
|
||||
Sjoerd Langkemper <sjoerd-github@linuxonly.nl>
|
||||
s-ko <aleks@s-ko.net>
|
||||
Solomon Hykes <solomon@docker.com>
|
||||
Song Gao <song@gao.io>
|
||||
Soulou <leo@unbekandt.eu>
|
||||
soulshake <amy@gandi.net>
|
||||
Sridatta Thatipamala <sthatipamala@gmail.com>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||
Srini Brahmaroutu <sbrahma@us.ibm.com>
|
||||
Steeve Morin <steeve.morin@gmail.com>
|
||||
Stefan Praszalowicz <stefan@greplin.com>
|
||||
Stephen Crosby <stevecrozz@gmail.com>
|
||||
Steven Burgess <steven.a.burgess@hotmail.com>
|
||||
Steven Merrill <steven.merrill@gmail.com>
|
||||
sudosurootdev <sudosurootdev@gmail.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au>
|
||||
Sylvain Bellemare <sylvain.bellemare@ezeep.com>
|
||||
tang0th <tang0th@gmx.com>
|
||||
Tatsuki Sugiura <sugi@nemui.org>
|
||||
Ted M. Young <tedyoung@gmail.com>
|
||||
Tehmasp Chaudhri <tehmasp@gmail.com>
|
||||
Thatcher Peskens <thatcher@docker.com>
|
||||
Thermionix <bond711@gmail.com>
|
||||
Thijs Terlouw <thijsterlouw@gmail.com>
|
||||
Thomas Bikeev <thomas.bikeev@mac.com>
|
||||
Thomas Frössman <thomasf@jossystem.se>
|
||||
Thomas Hansen <thomas.hansen@gmail.com>
|
||||
Thomas LEVEIL <thomasleveil@gmail.com>
|
||||
Thomas Orozco <thomas@orozco.fr>
|
||||
Thomas Schroeter <thomas@cliqz.com>
|
||||
Tianon Gravi <admwiggin@gmail.com>
|
||||
Tibor Vass <teabee89@gmail.com>
|
||||
Tim Bosse <taim@bosboot.org>
|
||||
Tim Hockin <thockin@google.com>
|
||||
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>
|
||||
tjwebb123 <tjwebb123@users.noreply.github.com>
|
||||
tobe <tobegit3hub@gmail.com>
|
||||
Tobias Bieniek <Tobias.Bieniek@gmx.de>
|
||||
Tobias Gesellchen <tobias@gesellix.de>
|
||||
Tobias Schmidt <ts@soundcloud.com>
|
||||
Tobias Schwab <tobias.schwab@dynport.de>
|
||||
Todd Lunter <tlunter@gmail.com>
|
||||
Tomasz Lipinski <tlipinski@users.noreply.github.com>
|
||||
Tom Fotherby <tom+github@peopleperhour.com>
|
||||
Tom Hulihan <hulihan.tom159@gmail.com>
|
||||
Tom Maaswinkel <tom.maaswinkel@12wiki.eu>
|
||||
Tommaso Visconti <tommaso.visconti@gmail.com>
|
||||
Tonis Tiigi <tonistiigi@gmail.com>
|
||||
Tony Daws <tony@daws.ca>
|
||||
Torstein Husebø <torstein@huseboe.net>
|
||||
tpng <benny.tpng@gmail.com>
|
||||
Travis Cline <travis.cline@gmail.com>
|
||||
Trent Ogren <tedwardo2@gmail.com>
|
||||
Tyler Brock <tyler.brock@gmail.com>
|
||||
Tzu-Jung Lee <roylee17@gmail.com>
|
||||
Ulysse Carion <ulyssecarion@gmail.com>
|
||||
unclejack <unclejacksons@gmail.com>
|
||||
vgeta <gopikannan.venugopalsamy@gmail.com>
|
||||
Victor Coisne <victor.coisne@dotcloud.com>
|
||||
Victor Lyuboslavsky <victor@victoreda.com>
|
||||
Victor Marmol <vmarmol@google.com>
|
||||
Victor Vieux <victor.vieux@docker.com>
|
||||
Viktor Vojnovski <viktor.vojnovski@amadeus.com>
|
||||
Vincent Batts <vbatts@redhat.com>
|
||||
Vincent Bernat <bernat@luffy.cx>
|
||||
Vincent Bernat <Vincent.Bernat@exoscale.ch>
|
||||
Vincent Giersch <vincent.giersch@ovh.net>
|
||||
Vincent Mayers <vincent.mayers@inbloom.org>
|
||||
Vincent Woo <me@vincentwoo.com>
|
||||
Vinod Kulkarni <vinod.kulkarni@gmail.com>
|
||||
Vishal Doshi <vishal.doshi@gmail.com>
|
||||
Vishnu Kannan <vishnuk@google.com>
|
||||
Vitor Monteiro <vmrmonteiro@gmail.com>
|
||||
Vivek Agarwal <me@vivek.im>
|
||||
Vivek Dasgupta <vdasgupt@redhat.com>
|
||||
Vivek Goyal <vgoyal@redhat.com>
|
||||
Vladimir Bulyga <xx@ccxx.cc>
|
||||
Vladimir Kirillov <proger@wilab.org.ua>
|
||||
Vladimir Rutsky <altsysrq@gmail.com>
|
||||
Vojtech Vitek (V-Teq) <vvitek@redhat.com>
|
||||
waitingkuo <waitingkuo0527@gmail.com>
|
||||
Walter Leibbrandt <github@wrl.co.za>
|
||||
Walter Stanish <walter@pratyeka.org>
|
||||
Ward Vandewege <ward@jhvc.com>
|
||||
WarheadsSE <max@warheads.net>
|
||||
Wes Morgan <cap10morgan@gmail.com>
|
||||
Will Dietz <w@wdtz.org>
|
||||
William Delanoue <william.delanoue@gmail.com>
|
||||
William Henry <whenry@redhat.com>
|
||||
William Riancho <wr.wllm@gmail.com>
|
||||
William Thurston <thurstw@amazon.com>
|
||||
Will Rouesnel <w.rouesnel@gmail.com>
|
||||
Will Weaver <monkey@buildingbananas.com>
|
||||
wyc <wayne@neverfear.org>
|
||||
Xiuming Chen <cc@cxm.cc>
|
||||
xuzhaokui <cynicholas@gmail.com>
|
||||
Yang Bai <hamo.by@gmail.com>
|
||||
Yasunori Mahata <nori@mahata.net>
|
||||
Yohei Ueda <yohei@jp.ibm.com>
|
||||
Yurii Rashkovskii <yrashk@gmail.com>
|
||||
Zac Dover <zdover@redhat.com>
|
||||
Zach Borboa <zachborboa@gmail.com>
|
||||
Zain Memon <zain@inzain.net>
|
||||
Zaiste! <oh@zaiste.net>
|
||||
Zane DeGraffenried <zane.deg@gmail.com>
|
||||
Zilin Du <zilin.du@gmail.com>
|
||||
zimbatm <zimbatm@zimbatm.com>
|
||||
Zoltan Tombol <zoltan.tombol@gmail.com>
|
||||
zqh <zqhxuyuan@gmail.com>
|
||||
尹吉峰 <jifeng.yin@gmail.com>
|
||||
-1577
File diff suppressed because it is too large
Load Diff
-329
@@ -1,329 +0,0 @@
|
||||
# Contributing to Docker
|
||||
|
||||
Want to hack on Docker? Awesome! Here are instructions to get you
|
||||
started. They are probably not perfect, please let us know if anything
|
||||
feels wrong or incomplete.
|
||||
|
||||
## Topics
|
||||
|
||||
* [Reporting Security Issues](#reporting-security-issues)
|
||||
* [Design and Cleanup Proposals](#design-and-cleanup-proposals)
|
||||
* [Reporting Issues](#reporting-issues)
|
||||
* [Build Environment](#build-environment)
|
||||
* [Contribution Guidelines](#contribution-guidelines)
|
||||
* [Community Guidelines](#docker-community-guidelines)
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
The Docker maintainers take security very seriously. If you discover a security issue,
|
||||
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
|
||||
|
||||
When considering a design proposal, we are looking for:
|
||||
|
||||
* A description of the problem this design proposal solves
|
||||
* 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 review [the existing Proposals](https://github.com/docker/docker/pulls?q=is%3Aopen+is%3Apr+label%3AProposal)
|
||||
before reporting a new one. You can always pair with someone if you both
|
||||
have the same idea.
|
||||
|
||||
When considering a cleanup task, we are looking for:
|
||||
|
||||
* A description of the refactors made
|
||||
* Please note any logic changes if necessary
|
||||
* A pull request with the code
|
||||
* Please prefix your PR's title with `Cleanup:` so we can quickly address it.
|
||||
* Your pull request must remain up to date with master, so rebase as necessary.
|
||||
|
||||
## 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
|
||||
GitHub please include your host OS (Ubuntu 12.04, Fedora 19, etc).
|
||||
Please include:
|
||||
|
||||
* The output of `uname -a`.
|
||||
* The output of `docker version`.
|
||||
* The output of `docker -D info`.
|
||||
|
||||
Please also include the steps required to reproduce the problem if
|
||||
possible and applicable. This information will help us review and fix
|
||||
your issue faster.
|
||||
|
||||
## Build Environment
|
||||
|
||||
For instructions on setting up your development environment, please
|
||||
see our dedicated [dev environment setup
|
||||
docs](http://docs.docker.com/contributing/devenvironment/).
|
||||
|
||||
## Contribution guidelines
|
||||
|
||||
### Pull requests are always welcome
|
||||
|
||||
We are always thrilled to receive pull requests, and do our best to
|
||||
process them as quickly as possible. Not sure if that typo is worth a pull
|
||||
request? Do it! We will appreciate it.
|
||||
|
||||
If your pull request is not accepted on the first try, don't be
|
||||
discouraged! If there's a problem with the implementation, hopefully you
|
||||
received feedback on what to improve.
|
||||
|
||||
We're trying very hard to keep Docker lean and focused. We don't want it
|
||||
to do everything for everybody. This means that we might decide against
|
||||
incorporating a new feature. However, there might be a way to implement
|
||||
that feature *on top of* Docker.
|
||||
|
||||
### Discuss your design on the mailing list
|
||||
|
||||
We recommend discussing your plans [on the mailing
|
||||
list](https://groups.google.com/forum/?fromgroups#!forum/docker-dev)
|
||||
before starting to code - especially for more ambitious contributions.
|
||||
This gives other contributors a chance to point you in the right
|
||||
direction, give feedback on your design, and maybe point out if someone
|
||||
else is working on the same thing.
|
||||
|
||||
### Create issues...
|
||||
|
||||
Any significant improvement should be documented as [a GitHub
|
||||
issue](https://github.com/docker/docker/issues) before anybody
|
||||
starts working on it.
|
||||
|
||||
### ...but check for existing issues first!
|
||||
|
||||
Please take a moment to check that an issue doesn't already exist
|
||||
documenting your bug report or improvement proposal. If it does, it
|
||||
never hurts to add a quick "+1" or "I have this problem too". This will
|
||||
help prioritize the most common problems and requests.
|
||||
|
||||
### Conventions
|
||||
|
||||
Fork the repository and make changes on your fork in a feature branch:
|
||||
|
||||
- If it's a bug fix branch, name it XXXX-something where XXXX is the number of the
|
||||
issue.
|
||||
- If it's a feature branch, create an enhancement issue to announce your
|
||||
intentions, and name it XXXX-something where XXXX is the number of the issue.
|
||||
|
||||
Submit unit tests for your changes. Go has a great test framework built in; use
|
||||
it! Take a look at existing tests for inspiration. Run the full test suite on
|
||||
your branch before submitting a pull request.
|
||||
|
||||
Update the documentation when creating or modifying features. Test
|
||||
your documentation changes for clarity, concision, and correctness, as
|
||||
well as a clean documentation build. See `docs/README.md` for more
|
||||
information on building the docs and how they get released.
|
||||
|
||||
Write clean code. Universally formatted code promotes ease of writing, reading,
|
||||
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
|
||||
committing your changes. Most editors have plug-ins that do this automatically.
|
||||
|
||||
Pull requests descriptions should be as clear as possible and include a
|
||||
reference to all the issues that they address.
|
||||
|
||||
Commit messages must start with a capitalized and short summary (max. 50
|
||||
chars) written in the imperative, followed by an optional, more detailed
|
||||
explanatory text which is separated from the summary by an empty line.
|
||||
|
||||
Code review comments may be added to your pull request. Discuss, then make the
|
||||
suggested modifications and push additional commits to your feature branch. Be
|
||||
sure to post a comment after pushing. The new commits will show up in the pull
|
||||
request automatically, but the reviewers will not be notified unless you
|
||||
comment.
|
||||
|
||||
Pull requests must be cleanly rebased ontop of master without multiple branches
|
||||
mixed into the PR.
|
||||
|
||||
**Git tip**: If your PR no longer merges cleanly, use `rebase master` in your
|
||||
feature branch to update your pull request rather than `merge master`.
|
||||
|
||||
Before the pull request is merged, make sure that you squash your commits into
|
||||
logical units of work using `git rebase -i` and `git push -f`. After every
|
||||
commit the test suite should be passing. Include documentation changes in the
|
||||
same commit so that a revert would remove all traces of the feature or fix.
|
||||
|
||||
Commits that fix or close an issue should include a reference like
|
||||
`Closes #XXXX` or `Fixes #XXXX`, which will automatically close the
|
||||
issue when merged.
|
||||
|
||||
Please do not add yourself to the `AUTHORS` file, as it is regenerated
|
||||
regularly from the Git history.
|
||||
|
||||
### Merge approval
|
||||
|
||||
Docker maintainers use LGTM (Looks Good To Me) in comments on the code review
|
||||
to indicate acceptance.
|
||||
|
||||
A change requires LGTMs from an absolute majority of the maintainers of each
|
||||
component affected. For example, if a change affects `docs/` and `registry/`, it
|
||||
needs an absolute majority from the maintainers of `docs/` AND, separately, an
|
||||
absolute majority of the maintainers of `registry/`.
|
||||
|
||||
For more details see [MAINTAINERS.md](project/MAINTAINERS.md)
|
||||
|
||||
### Sign your work
|
||||
|
||||
The sign-off is a simple line at the end of the explanation for the
|
||||
patch, which certifies that you wrote it or otherwise have the right to
|
||||
pass it on as an open-source patch. The rules are pretty simple: if you
|
||||
can certify the below (from
|
||||
[developercertificate.org](http://developercertificate.org/)):
|
||||
|
||||
```
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
660 York Street, Suite 102,
|
||||
San Francisco, CA 94110 USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
```
|
||||
|
||||
Then you just add a line to every git commit message:
|
||||
|
||||
Signed-off-by: Joe Smith <joe.smith@email.com>
|
||||
|
||||
Using your real name (sorry, no pseudonyms or anonymous contributions.)
|
||||
|
||||
If you set your `user.name` and `user.email` git configs, you can sign your
|
||||
commit automatically with `git commit -s`.
|
||||
|
||||
Note that the old-style `Docker-DCO-1.1-Signed-off-by: ...` format is still
|
||||
accepted, so there is no need to update outstanding pull requests to the new
|
||||
format right away, but please do adjust your processes for future contributions.
|
||||
|
||||
#### Small patch exception
|
||||
|
||||
There are several exceptions to the signing requirement. Currently these are:
|
||||
|
||||
* Your patch fixes spelling or grammar errors.
|
||||
* Your patch is a single line change to documentation contained in the
|
||||
`docs` directory.
|
||||
* Your patch fixes Markdown formatting or syntax errors in the
|
||||
documentation contained in the `docs` directory.
|
||||
|
||||
If you have any questions, please refer to the FAQ in the [docs](http://docs.docker.com)
|
||||
|
||||
### How can I become a maintainer?
|
||||
|
||||
* Step 1: Learn the component inside out
|
||||
* Step 2: Make yourself useful by contributing code, bug fixes, support etc.
|
||||
* Step 3: Volunteer on the IRC channel (#docker at Freenode)
|
||||
* Step 4: Propose yourself at a scheduled docker meeting in #docker-dev
|
||||
|
||||
Don't forget: being a maintainer is a time investment. Make sure you
|
||||
will have time to make yourself available. You don't have to be a
|
||||
maintainer to make a difference on the project!
|
||||
|
||||
### IRC Meetings
|
||||
|
||||
There are two monthly meetings taking place on #docker-dev IRC to accomodate all timezones.
|
||||
Anybody can ask for a topic to be discussed prior to the meeting.
|
||||
|
||||
If you feel the conversation is going off-topic, feel free to point it out.
|
||||
|
||||
For the exact dates and times, have a look at [the irc-minutes repo](https://github.com/docker/irc-minutes).
|
||||
They also contain all the notes from previous meetings.
|
||||
|
||||
## Docker Community Guidelines
|
||||
|
||||
We want to keep the Docker community awesome, growing and collaborative. We
|
||||
need your help to keep it that way. To help with this we've come up with some
|
||||
general guidelines for the community as a whole:
|
||||
|
||||
* Be nice: Be courteous, respectful and polite to fellow community members: no
|
||||
regional, racial, gender, or other abuse will be tolerated. We like nice people
|
||||
way better than mean ones!
|
||||
|
||||
* Encourage diversity and participation: Make everyone in our community
|
||||
feel welcome, regardless of their background and the extent of their
|
||||
contributions, and do everything possible to encourage participation in
|
||||
our community.
|
||||
|
||||
* Keep it legal: Basically, don't get us in trouble. Share only content that
|
||||
you own, do not share private or sensitive information, and don't break the
|
||||
law.
|
||||
|
||||
* Stay on topic: Make sure that you are posting to the correct channel
|
||||
and avoid off-topic discussions. Remember when you update an issue or
|
||||
respond to an email you are potentially sending to a large number of
|
||||
people. Please consider this before you update. Also remember that
|
||||
nobody likes spam.
|
||||
|
||||
### Guideline Violations — 3 Strikes Method
|
||||
|
||||
The point of this section is not to find opportunities to punish people, but we
|
||||
do need a fair way to deal with people who are making our community suck.
|
||||
|
||||
1. First occurrence: We'll give you a friendly, but public reminder that the
|
||||
behavior is inappropriate according to our guidelines.
|
||||
|
||||
2. Second occurrence: We will send you a private message with a warning that
|
||||
any additional violations will result in removal from the community.
|
||||
|
||||
3. Third occurrence: Depending on the violation, we may need to delete or ban
|
||||
your account.
|
||||
|
||||
**Notes:**
|
||||
|
||||
* Obvious spammers are banned on first occurrence. If we don't do this, we'll
|
||||
have spam all over the place.
|
||||
|
||||
* Violations are forgiven after 6 months of good behavior, and we won't
|
||||
hold a grudge.
|
||||
|
||||
* People who commit minor infractions will get some education,
|
||||
rather than hammering them in the 3 strikes process.
|
||||
|
||||
* The rules apply equally to everyone in the community, no matter how
|
||||
much you've contributed.
|
||||
|
||||
* Extreme violations of a threatening, abusive, destructive or illegal nature
|
||||
will be addressed immediately and are not subject to 3 strikes or
|
||||
forgiveness.
|
||||
|
||||
* 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
|
||||
a fair solution if there has been a misunderstanding.
|
||||
|
||||
-115
@@ -1,115 +0,0 @@
|
||||
# This file describes the standard way to build Docker, using docker
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# # Assemble the full dev environment. This is slow the first time.
|
||||
# docker build -t docker .
|
||||
#
|
||||
# # Mount your source in an interactive container for quick testing:
|
||||
# docker run -v `pwd`:/go/src/github.com/docker/docker --privileged -i -t docker bash
|
||||
#
|
||||
# # Run the test suite:
|
||||
# docker run --privileged docker hack/make.sh test
|
||||
#
|
||||
# # Publish a release:
|
||||
# docker run --privileged \
|
||||
# -e AWS_S3_BUCKET=baz \
|
||||
# -e AWS_ACCESS_KEY=foo \
|
||||
# -e AWS_SECRET_KEY=bar \
|
||||
# -e GPG_PASSPHRASE=gloubiboulga \
|
||||
# docker hack/release.sh
|
||||
#
|
||||
# Note: Apparmor used to mess with privileged mode, but this is no longer
|
||||
# the case. Therefore, you don't have to disable it anymore.
|
||||
#
|
||||
|
||||
FROM ubuntu:14.04
|
||||
MAINTAINER Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
|
||||
# Packaged dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
aufs-tools \
|
||||
automake \
|
||||
btrfs-tools \
|
||||
build-essential \
|
||||
curl \
|
||||
dpkg-sig \
|
||||
git \
|
||||
iptables \
|
||||
libapparmor-dev \
|
||||
libcap-dev \
|
||||
libsqlite3-dev \
|
||||
lxc=1.0* \
|
||||
mercurial \
|
||||
parallel \
|
||||
reprepro \
|
||||
ruby1.9.1 \
|
||||
ruby1.9.1-dev \
|
||||
s3cmd=1.1.0* \
|
||||
--no-install-recommends
|
||||
|
||||
# Get lvm2 source for compiling statically
|
||||
RUN git clone --no-checkout https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout -q v2_02_103
|
||||
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
|
||||
# note: we don't use "git clone -b" above because it then spews big nasty warnings about 'detached HEAD' state that we can't silence as easily as we can silence them using "git checkout" directly
|
||||
|
||||
# Compile and install lvm2
|
||||
RUN cd /usr/local/lvm2 && ./configure --enable-static_link && make device-mapper && make install_device-mapper
|
||||
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
|
||||
|
||||
# Install Go
|
||||
RUN curl -sSL https://golang.org/dl/go1.3.3.src.tar.gz | tar -v -C /usr/local -xz
|
||||
ENV PATH /usr/local/go/bin:$PATH
|
||||
ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
|
||||
ENV PATH /go/bin:$PATH
|
||||
RUN cd /usr/local/go/src && ./make.bash --no-clean 2>&1
|
||||
|
||||
# Compile Go for cross compilation
|
||||
ENV DOCKER_CROSSPLATFORMS \
|
||||
linux/386 linux/arm \
|
||||
darwin/amd64 darwin/386 \
|
||||
freebsd/amd64 freebsd/386 freebsd/arm
|
||||
# windows is experimental for now
|
||||
# windows/amd64 windows/386
|
||||
|
||||
# (set an explicit GOARM of 5 for maximum compatibility)
|
||||
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'
|
||||
|
||||
# Grab Go's cover tool for dead-simple code coverage testing
|
||||
RUN go get golang.org/x/tools/cmd/cover
|
||||
|
||||
# TODO replace FPM with some very minimal debhelper stuff
|
||||
RUN gem install --no-rdoc --no-ri fpm --version 1.3.2
|
||||
|
||||
# Install man page generator
|
||||
RUN mkdir -p /go/src/github.com/cpuguy83 \
|
||||
&& git clone -b v1 https://github.com/cpuguy83/go-md2man.git /go/src/github.com/cpuguy83/go-md2man \
|
||||
&& cd /go/src/github.com/cpuguy83/go-md2man \
|
||||
&& go get -v ./...
|
||||
|
||||
# Get the "busybox" image source so we can build locally instead of pulling
|
||||
RUN git clone -b buildroot-2014.02 https://github.com/jpetazzo/docker-busybox.git /docker-busybox
|
||||
|
||||
# Get the "cirros" image source so we can import it instead of fetching it during tests
|
||||
RUN curl -sSL -o /cirros.tar.gz https://github.com/ewindisch/docker-cirros/raw/1cded459668e8b9dbf4ef976c94c05add9bbd8e9/cirros-0.3.0-x86_64-lxc.tar.gz
|
||||
|
||||
# Setup s3cmd config
|
||||
RUN /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY' > $HOME/.s3cfg
|
||||
|
||||
# Set user.email so crosbymichael's in-container merge commits go smoothly
|
||||
RUN git config --global user.email 'docker-dummy@example.com'
|
||||
|
||||
# Add an unprivileged user to be used for tests which need it
|
||||
RUN groupadd -r docker
|
||||
RUN useradd --create-home --gid docker unprivilegeduser
|
||||
|
||||
VOLUME /var/lib/docker
|
||||
WORKDIR /go/src/github.com/docker/docker
|
||||
ENV DOCKER_BUILDTAGS apparmor selinux btrfs_noversion
|
||||
|
||||
# Wrap all commands in the "docker-in-docker" script to allow nested containers
|
||||
ENTRYPOINT ["hack/dind"]
|
||||
|
||||
# Upload docker source
|
||||
COPY . /go/src/github.com/docker/docker
|
||||
@@ -176,7 +176,18 @@
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2014 Docker, Inc.
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
Solomon Hykes <solomon@docker.com> (@shykes)
|
||||
Victor Vieux <vieux@docker.com> (@vieux)
|
||||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||
.mailmap: Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
.travis.yml: Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
AUTHORS: Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
Dockerfile: Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
Makefile: Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
.dockerignore: Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
@@ -1,87 +1,23 @@
|
||||
.PHONY: all binary build cross default docs docs-build docs-shell shell test test-unit test-integration test-integration-cli validate
|
||||
GOPATH := $(PWD)/env
|
||||
BUILDPATH := $(PWD)/build
|
||||
|
||||
# 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
|
||||
all: docker.o dockerd.o
|
||||
|
||||
# to allow `make BINDDIR=. shell` or `make BINDDIR= test`
|
||||
# (default to no bind mount if DOCKER_HOST is set)
|
||||
BINDDIR := $(if $(DOCKER_HOST),,bundles)
|
||||
DOCKER_MOUNT := $(if $(BINDDIR),-v "$(CURDIR)/$(BINDDIR):/go/src/github.com/docker/docker/$(BINDDIR)")
|
||||
env:
|
||||
mkdir -p ${BUILDPATH} ${GOPATH}/src/github.com/dotcloud/
|
||||
ln -s $(PWD) ${GOPATH}/src/github.com/dotcloud/docker
|
||||
|
||||
# to allow `make DOCSDIR=docs docs-shell` (to create a bind mount in docs)
|
||||
DOCS_MOUNT := $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR))
|
||||
deps:
|
||||
GOPATH=${GOPATH} go get github.com/kr/pty
|
||||
GOPATH=${GOPATH} go get github.com/mattn/go-sqlite3
|
||||
GOPATH=${GOPATH} go get github.com/shykes/gorp
|
||||
|
||||
# to allow `make DOCSPORT=9000 docs`
|
||||
DOCSPORT := 8000
|
||||
clean:
|
||||
go clean
|
||||
rm -rf env build
|
||||
|
||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
||||
DOCKER_IMAGE := docker$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||
DOCKER_DOCS_IMAGE := docker-docs$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||
docker.o: env deps
|
||||
GOPATH=${GOPATH} go build -o ${BUILDPATH}/docker docker/docker.go
|
||||
|
||||
DOCKER_RUN_DOCKER := docker run --rm -it --privileged $(DOCKER_ENVS) $(DOCKER_MOUNT) "$(DOCKER_IMAGE)"
|
||||
|
||||
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
|
||||
|
||||
all: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh
|
||||
|
||||
binary: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary
|
||||
|
||||
cross: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross
|
||||
|
||||
docs: docs-build
|
||||
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" mkdocs serve
|
||||
|
||||
docs-shell: docs-build
|
||||
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" bash
|
||||
|
||||
docs-release: docs-build
|
||||
$(DOCKER_RUN_DOCS) -e OPTIONS -e BUILD_ROOT "$(DOCKER_DOCS_IMAGE)" ./release.sh
|
||||
|
||||
test: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross test-unit test-integration test-integration-cli
|
||||
|
||||
test-unit: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh test-unit
|
||||
|
||||
test-integration: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh test-integration
|
||||
|
||||
test-integration-cli: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary test-integration-cli
|
||||
|
||||
validate: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh validate-gofmt validate-dco
|
||||
|
||||
shell: build
|
||||
$(DOCKER_RUN_DOCKER) bash
|
||||
|
||||
build: bundles
|
||||
docker build -t "$(DOCKER_IMAGE)" .
|
||||
|
||||
docs-build:
|
||||
cp ./VERSION docs/VERSION
|
||||
echo "$(GIT_BRANCH)" > docs/GIT_BRANCH
|
||||
echo "$(AWS_S3_BUCKET)" > docs/AWS_S3_BUCKET
|
||||
echo "$(GITCOMMIT)" > docs/GITCOMMIT
|
||||
docker build -t "$(DOCKER_DOCS_IMAGE)" docs
|
||||
|
||||
bundles:
|
||||
mkdir bundles
|
||||
dockerd.o: env deps
|
||||
GOPATH=${GOPATH} go build -o ${BUILDPATH}/dockerd dockerd/dockerd.go
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
Docker
|
||||
Copyright 2012-2014 Docker, Inc.
|
||||
Copyright 2012-2013 dotCloud, inc.
|
||||
|
||||
This product includes software developed at Docker, Inc. (http://www.docker.com).
|
||||
This product includes software developed at dotCloud, inc. (http://www.dotcloud.com).
|
||||
|
||||
This product contains software (https://github.com/kr/pty) developed
|
||||
by Keith Rarick, licensed under the MIT License.
|
||||
|
||||
The following is courtesy of our legal counsel:
|
||||
|
||||
|
||||
Use and transfer of Docker may be subject to certain restrictions by the
|
||||
United States and other governments.
|
||||
It is your responsibility to ensure that your use and/or transfer does not
|
||||
violate applicable laws.
|
||||
|
||||
For more information, please see http://www.bis.doc.gov
|
||||
|
||||
See also http://www.apache.org/dev/crypto.html and/or seek legal counsel.
|
||||
This product contains software (https://github.com/kr/pty) developed by Keith Rarick, licensed under the MIT License.
|
||||
@@ -1,208 +1,173 @@
|
||||
Docker: the Linux container engine
|
||||
==================================
|
||||
Docker is a process manager with superpowers
|
||||
============================================
|
||||
|
||||
Docker is an open source project to pack, ship and run any application
|
||||
as a lightweight container
|
||||
It encapsulates heterogeneous payloads in Standard Containers, and runs them on any server with strong guarantees of isolation and repeatability.
|
||||
|
||||
Docker containers are both *hardware-agnostic* and *platform-agnostic*.
|
||||
This means that they can run anywhere, from your laptop to the largest
|
||||
EC2 compute instance and everything in between - and they don't require
|
||||
that you use a particular language, framework or packaging system. That
|
||||
makes them great building blocks for deploying and scaling web apps,
|
||||
databases and backend services without depending on a particular stack
|
||||
or provider.
|
||||
Is is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
|
||||
|
||||
Docker is an open-source implementation of the deployment engine which
|
||||
powers [dotCloud](http://dotcloud.com), a popular Platform-as-a-Service.
|
||||
It benefits directly from the experience accumulated over several years
|
||||
of large-scale operation and support of hundreds of thousands of
|
||||
applications and databases.
|
||||
<img src="http://bricks.argz.com/bricksfiles/lego/07000/7823/012.jpg"/>
|
||||
|
||||

|
||||
* *Heterogeneous payloads*: any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.
|
||||
|
||||
## Security Disclosure
|
||||
* *Any server*: docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.
|
||||
|
||||
Security is very important to us. If you have any issue regarding security,
|
||||
please disclose the information responsibly by sending an email to
|
||||
security@docker.com and not by creating a github issue.
|
||||
* *Isolation*: docker isolates processes from each other and from the underlying host, using lightweight containers.
|
||||
|
||||
## Better than VMs
|
||||
|
||||
A common method for distributing applications and sandboxing their
|
||||
execution is to use virtual machines, or VMs. Typical VM formats are
|
||||
VMWare's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In theory
|
||||
these formats should allow every developer to automatically package
|
||||
their application into a "machine" for easy distribution and deployment.
|
||||
In practice, that almost never happens, for a few reasons:
|
||||
|
||||
* *Size*: VMs are very large which makes them impractical to store
|
||||
and transfer.
|
||||
* *Performance*: running VMs consumes significant CPU and memory,
|
||||
which makes them impractical in many scenarios, for example local
|
||||
development of multi-tier applications, and large-scale deployment
|
||||
of cpu and memory-intensive applications on large numbers of
|
||||
machines.
|
||||
* *Portability*: competing VM environments don't play well with each
|
||||
other. Although conversion tools do exist, they are limited and
|
||||
add even more overhead.
|
||||
* *Hardware-centric*: VMs were designed with machine operators in
|
||||
mind, not software developers. As a result, they offer very
|
||||
limited tooling for what developers need most: building, testing
|
||||
and running their software. For example, VMs offer no facilities
|
||||
for application versioning, monitoring, configuration, logging or
|
||||
service discovery.
|
||||
|
||||
By contrast, Docker relies on a different sandboxing method known as
|
||||
*containerization*. Unlike traditional virtualization, containerization
|
||||
takes place at the kernel level. Most modern operating system kernels
|
||||
now support the primitives necessary for containerization, including
|
||||
Linux with [openvz](http://openvz.org),
|
||||
[vserver](http://linux-vserver.org) and more recently
|
||||
[lxc](http://lxc.sourceforge.net), Solaris with
|
||||
[zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc)
|
||||
and FreeBSD with
|
||||
[Jails](http://www.freebsd.org/doc/handbook/jails.html).
|
||||
|
||||
Docker builds on top of these low-level primitives to offer developers a
|
||||
portable format and runtime environment that solves all 4 problems.
|
||||
Docker containers are small (and their transfer can be optimized with
|
||||
layers), they have basically zero memory and cpu overhead, they are
|
||||
completely portable and are designed from the ground up with an
|
||||
application-centric design.
|
||||
|
||||
The best part: because Docker operates at the OS level, it can still be
|
||||
run inside a VM!
|
||||
|
||||
## Plays well with others
|
||||
|
||||
Docker does not require that you buy into a particular programming
|
||||
language, framework, packaging system or configuration language.
|
||||
|
||||
Is your application a Unix process? Does it use files, tcp connections,
|
||||
environment variables, standard Unix streams and command-line arguments
|
||||
as inputs and outputs? Then Docker can run it.
|
||||
|
||||
Can your application's build be expressed as a sequence of such
|
||||
commands? Then Docker can build it.
|
||||
|
||||
## Escape dependency hell
|
||||
|
||||
A common problem for developers is the difficulty of managing all
|
||||
their application's dependencies in a simple and automated way.
|
||||
|
||||
This is usually difficult for several reasons:
|
||||
|
||||
* *Cross-platform dependencies*. Modern applications often depend on
|
||||
a combination of system libraries and binaries, language-specific
|
||||
packages, framework-specific modules, internal components
|
||||
developed for another project, etc. These dependencies live in
|
||||
different "worlds" and require different tools - these tools
|
||||
typically don't work well with each other, requiring awkward
|
||||
custom integrations.
|
||||
|
||||
* Conflicting dependencies. Different applications may depend on
|
||||
different versions of the same dependency. Packaging tools handle
|
||||
these situations with various degrees of ease - but they all
|
||||
handle them in different and incompatible ways, which again forces
|
||||
the developer to do extra work.
|
||||
|
||||
* Custom dependencies. A developer may need to prepare a custom
|
||||
version of their application's dependency. Some packaging systems
|
||||
can handle custom versions of a dependency, others can't - and all
|
||||
of them handle it differently.
|
||||
* *Repeatability*: because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.
|
||||
|
||||
|
||||
Docker solves dependency hell by giving the developer a simple way to
|
||||
express *all* their application's dependencies in one place, and
|
||||
streamline the process of assembling them. If this makes you think of
|
||||
[XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't
|
||||
*replace* your favorite packaging systems. It simply orchestrates
|
||||
their use in a simple and repeatable way. How does it do that? With
|
||||
layers.
|
||||
Notable features
|
||||
-----------------
|
||||
|
||||
Docker defines a build as running a sequence of Unix commands, one
|
||||
after the other, in the same container. Build commands modify the
|
||||
contents of the container (usually by installing new files on the
|
||||
filesystem), the next command modifies it some more, etc. Since each
|
||||
build command inherits the result of the previous commands, the
|
||||
*order* in which the commands are executed expresses *dependencies*.
|
||||
* Filesystem isolation: each process container runs in a completely separate root filesystem.
|
||||
|
||||
Here's a typical Docker build process:
|
||||
* Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.
|
||||
|
||||
```bash
|
||||
FROM ubuntu:12.04
|
||||
RUN apt-get update && apt-get install -y python python-pip curl
|
||||
RUN curl -sSL https://github.com/shykes/helloflask/archive/master.tar.gz | tar -xzv
|
||||
RUN cd helloflask-master && pip install -r requirements.txt
|
||||
```
|
||||
* Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.
|
||||
|
||||
Note that Docker doesn't care *how* dependencies are built - as long
|
||||
as they can be built by running a Unix command in a container.
|
||||
* Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremeley fast, memory-cheap and disk-cheap.
|
||||
|
||||
* Logging: the standard streams (stdout/stderr/stdin) of each process container is collected and logged for real-time or batch retrieval.
|
||||
|
||||
* Change management: changes to a container's filesystem can be committed into a new image and re-used to create more containers. No templating or manual configuration required.
|
||||
|
||||
* Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throaway interactive shell.
|
||||
|
||||
|
||||
Getting started
|
||||
===============
|
||||
|
||||
Docker can be installed on your local machine as well as servers - both
|
||||
bare metal and virtualized. It is available as a binary on most modern
|
||||
Linux systems, or as a VM on Windows, Mac and other systems.
|
||||
|
||||
We also offer an [interactive tutorial](http://www.docker.com/tryit/)
|
||||
for quickly learning the basics of using Docker.
|
||||
|
||||
For up-to-date install instructions, see the [Docs](http://docs.docker.com).
|
||||
|
||||
Usage examples
|
||||
==============
|
||||
|
||||
Docker can be used to run short-lived commands, long-running daemons
|
||||
(app servers, databases etc.), interactive shell sessions, etc.
|
||||
|
||||
You can find a [list of real-world
|
||||
examples](http://docs.docker.com/examples/) in the
|
||||
documentation.
|
||||
|
||||
Under the hood
|
||||
--------------
|
||||
|
||||
Under the hood, Docker is built on the following components:
|
||||
|
||||
* The
|
||||
[cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c)
|
||||
and
|
||||
[namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part)
|
||||
capabilities of the Linux kernel;
|
||||
* The [Go](http://golang.org) programming language.
|
||||
|
||||
Contributing to Docker
|
||||
======================
|
||||
* The [cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c) and [namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part) capabilities of the Linux kernel;
|
||||
|
||||
[](https://godoc.org/github.com/docker/docker)
|
||||
[](https://ci.dockerproject.com/github.com/docker/docker)
|
||||
* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union filesystem with copy-on-write capabilities;
|
||||
|
||||
Want to hack on Docker? Awesome! There are instructions to get you
|
||||
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).
|
||||
* The [Go](http://golang.org) programming language;
|
||||
|
||||
These instructions are probably not perfect, please let us know if anything
|
||||
feels wrong or incomplete.
|
||||
|
||||
### Legal
|
||||
|
||||
*Brought to you courtesy of our legal counsel. For more context,
|
||||
please see the Notice document.*
|
||||
|
||||
Use and transfer of Docker may be subject to certain restrictions by the
|
||||
United States and other governments.
|
||||
It is your responsibility to ensure that your use and/or transfer does not
|
||||
violate applicable laws.
|
||||
|
||||
For more information, please see http://www.bis.doc.gov
|
||||
* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers.
|
||||
|
||||
|
||||
Licensing
|
||||
=========
|
||||
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.
|
||||
Setup instructions
|
||||
==================
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Right now, the officially supported distributions are:
|
||||
|
||||
* Ubuntu 12.04 (precise LTS)
|
||||
* Ubuntu 12.10 (quantal)
|
||||
|
||||
Docker probably works on other distributions featuring a recent kernel, the AUFS patch, and up-to-date lxc. However this has not been tested.
|
||||
|
||||
|
||||
Installation
|
||||
---------------
|
||||
|
||||
1. Set up your host of choice on a physical / virtual machine
|
||||
2. Assume root identity on your newly installed environment (`sudo -s`)
|
||||
3. Type the following commands:
|
||||
|
||||
apt-get update
|
||||
apt-get install lxc wget bsdtar curl
|
||||
|
||||
4. Download the latest docker binaries: `wget http://docker.io.s3.amazonaws.com/builds/$(uname -s)/$(uname -m)/docker-master.tgz` ([Or get the Linux/x86_64 binaries here](http://docker.io.s3.amazonaws.com/builds/Linux/x86_64/docker-master.tgz) )
|
||||
5. Extract the contents of the tar file `tar -xf docker-master.tar.gz`
|
||||
6. Launch the docker daemon in the background `./dockerd &`
|
||||
7. Download a base image `./docker pull base`
|
||||
8. Run your first container! `./docker run -i -a -t base /bin/bash`
|
||||
9. Start exploring `./docker --help`
|
||||
|
||||
Consider adding docker and dockerd to your `PATH` for simplicity.
|
||||
|
||||
|
||||
What is a Standard Container?
|
||||
-----------------------------
|
||||
|
||||
Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in
|
||||
a format that is self-describing and portable, so that any compliant runtime can run it without extra dependency, regardless of the underlying machine and the contents of the container.
|
||||
|
||||
The spec for Standard Containers is currently work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment.
|
||||
|
||||
A great analogy for this is the shipping container. Just like Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery.
|
||||
|
||||
### 1. STANDARD OPERATIONS
|
||||
|
||||
Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, standard containers can be started, stopped, copied, snapshotted, downloaded, uploaded and tagged.
|
||||
|
||||
|
||||
### 2. CONTENT-AGNOSTIC
|
||||
|
||||
Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffe or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts.
|
||||
|
||||
|
||||
### 3. INFRASTRUCTURE-AGNOSTIC
|
||||
|
||||
Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions.
|
||||
|
||||
|
||||
### 4. DESIGNED FOR AUTOMATION
|
||||
|
||||
Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterpart, are extremely well-suited for automation. In fact, you could say automation is their secret weapon.
|
||||
|
||||
Many things that once required time-consuming and error-prone human effort can now be programmed. Before shipping containers, a bag of powder coffee was hauled, dragged, dropped, rolled and stacked by 10 different people in 10 different locations by the time it reached its destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The process was slow, inefficient and cost a fortune - and was entirely different depending on the facility and the type of goods.
|
||||
|
||||
Similarly, before Standard Containers, by the time a software component ran in production, it had been individually built, configured, bundled, documented, patched, vendored, templated, tweaked and instrumented by 10 different people on 10 different computers. Builds failed, libraries conflicted, mirrors crashed, post-it notes were lost, logs were misplaced, cluster updates were half-broken. The process was slow, inefficient and cost a fortune - and was entirely different depending on the language and infrastructure provider.
|
||||
|
||||
|
||||
### 5. INDUSTRIAL-GRADE DELIVERY
|
||||
|
||||
There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded on the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away.
|
||||
|
||||
With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality.
|
||||
|
||||
|
||||
|
||||
|
||||
Standard Container Specification
|
||||
--------------------------------
|
||||
|
||||
(TODO)
|
||||
|
||||
### Image format
|
||||
|
||||
|
||||
### Standard operations
|
||||
|
||||
* Copy
|
||||
* Run
|
||||
* Stop
|
||||
* Wait
|
||||
* Commit
|
||||
* Attach standard streams
|
||||
* List filesystem changes
|
||||
* ...
|
||||
|
||||
### Execution environment
|
||||
|
||||
#### Root filesystem
|
||||
|
||||
#### Environment variables
|
||||
|
||||
#### Process arguments
|
||||
|
||||
#### Networking
|
||||
|
||||
#### Process namespacing
|
||||
|
||||
#### Resource limits
|
||||
|
||||
#### Process monitoring
|
||||
|
||||
#### Logging
|
||||
|
||||
#### Signals
|
||||
|
||||
#### Pseudo-terminal allocation
|
||||
|
||||
#### Security
|
||||
|
||||
|
||||
|
||||
Vendored
+100
@@ -0,0 +1,100 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
Vagrant::Config.run do |config|
|
||||
# All Vagrant configuration is done here. The most common configuration
|
||||
# options are documented and commented below. For a complete reference,
|
||||
# please see the online documentation at vagrantup.com.
|
||||
|
||||
# Every Vagrant virtual environment requires a box to build off of.
|
||||
config.vm.box = "quantal64_3.5.0-25"
|
||||
|
||||
# The url from where the 'config.vm.box' box will be fetched if it
|
||||
# doesn't already exist on the user's system.
|
||||
config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box"
|
||||
|
||||
# Boot with a GUI so you can see the screen. (Default is headless)
|
||||
# config.vm.boot_mode = :gui
|
||||
|
||||
# Assign this VM to a host-only network IP, allowing you to access it
|
||||
# via the IP. Host-only networks can talk to the host machine as well as
|
||||
# any other machines on the same network, but cannot be accessed (through this
|
||||
# network interface) by any external networks.
|
||||
# config.vm.network :hostonly, "192.168.33.10"
|
||||
|
||||
# Assign this VM to a bridged network, allowing you to connect directly to a
|
||||
# network using the host's network device. This makes the VM appear as another
|
||||
# physical device on your network.
|
||||
# config.vm.network :bridged
|
||||
|
||||
# Forward a port from the guest to the host, which allows for outside
|
||||
# computers to access the VM, whereas host only networking does not.
|
||||
# config.vm.forward_port 80, 8080
|
||||
|
||||
# Share an additional folder to the guest VM. The first argument is
|
||||
# an identifier, the second is the path on the guest to mount the
|
||||
# folder, and the third is the path on the host to the actual folder.
|
||||
# config.vm.share_folder "v-data", "/vagrant_data", "../data"
|
||||
|
||||
# Enable provisioning with Puppet stand alone. Puppet manifests
|
||||
# are contained in a directory path relative to this Vagrantfile.
|
||||
# You will need to create the manifests directory and a manifest in
|
||||
# the file quantal64.pp in the manifests_path directory.
|
||||
#
|
||||
# An example Puppet manifest to provision the message of the day:
|
||||
#
|
||||
# # group { "puppet":
|
||||
# # ensure => "present",
|
||||
# # }
|
||||
# #
|
||||
# # File { owner => 0, group => 0, mode => 0644 }
|
||||
# #
|
||||
# # file { '/etc/motd':
|
||||
# # content => "Welcome to your Vagrant-built virtual machine!
|
||||
# # Managed by Puppet.\n"
|
||||
# # }
|
||||
#
|
||||
config.vm.provision :puppet do |puppet|
|
||||
puppet.manifests_path = "puppet/manifests"
|
||||
puppet.manifest_file = "quantal64.pp"
|
||||
puppet.module_path = "puppet/modules"
|
||||
end
|
||||
|
||||
# Enable provisioning with chef solo, specifying a cookbooks path, roles
|
||||
# path, and data_bags path (all relative to this Vagrantfile), and adding
|
||||
# some recipes and/or roles.
|
||||
#
|
||||
# config.vm.provision :chef_solo do |chef|
|
||||
# chef.cookbooks_path = "../my-recipes/cookbooks"
|
||||
# chef.roles_path = "../my-recipes/roles"
|
||||
# chef.data_bags_path = "../my-recipes/data_bags"
|
||||
# chef.add_recipe "mysql"
|
||||
# chef.add_role "web"
|
||||
#
|
||||
# # You may also specify custom JSON attributes:
|
||||
# chef.json = { :mysql_password => "foo" }
|
||||
# end
|
||||
|
||||
# Enable provisioning with chef server, specifying the chef server URL,
|
||||
# and the path to the validation key (relative to this Vagrantfile).
|
||||
#
|
||||
# The Opscode Platform uses HTTPS. Substitute your organization for
|
||||
# ORGNAME in the URL and validation key.
|
||||
#
|
||||
# If you have your own Chef Server, use the appropriate URL, which may be
|
||||
# HTTP instead of HTTPS depending on your configuration. Also change the
|
||||
# validation key to validation.pem.
|
||||
#
|
||||
# config.vm.provision :chef_client do |chef|
|
||||
# chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME"
|
||||
# chef.validation_key_path = "ORGNAME-validator.pem"
|
||||
# end
|
||||
#
|
||||
# If you're using the Opscode platform, your validator client is
|
||||
# ORGNAME-validator, replacing ORGNAME with your organization name.
|
||||
#
|
||||
# IF you have your own Chef Server, the default validation client name is
|
||||
# chef-validator, unless you changed the configuration.
|
||||
#
|
||||
# chef.validation_client_name = "ORGNAME-validator"
|
||||
end
|
||||
@@ -1,2 +0,0 @@
|
||||
Victor Vieux <vieux@docker.com> (@vieux)
|
||||
Jessie Frazelle <jess@docker.com> (@jfrazelle)
|
||||
@@ -1,5 +0,0 @@
|
||||
This directory contains code pertaining to the Docker API:
|
||||
|
||||
- Used by the docker client when communicating with the docker daemon
|
||||
|
||||
- Used by third party tools wishing to interface with the docker daemon
|
||||
@@ -1,19 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJsonContentType(t *testing.T) {
|
||||
if !MatchesContentType("application/json", "application/json") {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !MatchesContentType("application/json; charset=utf-8", "application/json") {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if MatchesContentType("dockerapplication/json", "application/json") {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/libtrust"
|
||||
)
|
||||
|
||||
type DockerCli struct {
|
||||
proto string
|
||||
addr string
|
||||
configFile *registry.ConfigFile
|
||||
in io.ReadCloser
|
||||
out io.Writer
|
||||
err io.Writer
|
||||
key libtrust.PrivateKey
|
||||
tlsConfig *tls.Config
|
||||
scheme string
|
||||
// inFd holds file descriptor of the client's STDIN, if it's a valid file
|
||||
inFd uintptr
|
||||
// outFd holds file descriptor of the client's STDOUT, if it's a valid file
|
||||
outFd uintptr
|
||||
// isTerminalIn describes if client's STDIN is a TTY
|
||||
isTerminalIn bool
|
||||
// isTerminalOut describes if client's STDOUT is a TTY
|
||||
isTerminalOut bool
|
||||
transport *http.Transport
|
||||
}
|
||||
|
||||
var funcMap = template.FuncMap{
|
||||
"json": func(v interface{}) string {
|
||||
a, _ := json.Marshal(v)
|
||||
return string(a)
|
||||
},
|
||||
}
|
||||
|
||||
func (cli *DockerCli) getMethod(args ...string) (func(...string) error, bool) {
|
||||
camelArgs := make([]string, len(args))
|
||||
for i, s := range args {
|
||||
if len(s) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
||||
}
|
||||
methodName := "Cmd" + strings.Join(camelArgs, "")
|
||||
method := reflect.ValueOf(cli).MethodByName(methodName)
|
||||
if !method.IsValid() {
|
||||
return nil, false
|
||||
}
|
||||
return method.Interface().(func(...string) error), true
|
||||
}
|
||||
|
||||
// Cmd executes the specified command
|
||||
func (cli *DockerCli) Cmd(args ...string) error {
|
||||
if len(args) > 1 {
|
||||
method, exists := cli.getMethod(args[:2]...)
|
||||
if exists {
|
||||
return method(args[2:]...)
|
||||
}
|
||||
}
|
||||
if len(args) > 0 {
|
||||
method, exists := cli.getMethod(args[0])
|
||||
if !exists {
|
||||
fmt.Println("Error: Command not found:", args[0])
|
||||
return cli.CmdHelp()
|
||||
}
|
||||
return method(args[1:]...)
|
||||
}
|
||||
return cli.CmdHelp()
|
||||
}
|
||||
|
||||
func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet {
|
||||
flags := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||
flags.Usage = func() {
|
||||
options := ""
|
||||
if flags.FlagCountUndeprecated() > 0 {
|
||||
options = "[OPTIONS] "
|
||||
}
|
||||
fmt.Fprintf(cli.err, "\nUsage: docker %s %s%s\n\n%s\n\n", name, options, signature, description)
|
||||
flags.PrintDefaults()
|
||||
os.Exit(2)
|
||||
}
|
||||
return flags
|
||||
}
|
||||
|
||||
func (cli *DockerCli) LoadConfigFile() (err error) {
|
||||
cli.configFile, err = registry.LoadConfig(os.Getenv("HOME"))
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "WARNING: %s\n", 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 {
|
||||
var (
|
||||
inFd uintptr
|
||||
outFd uintptr
|
||||
isTerminalIn = false
|
||||
isTerminalOut = false
|
||||
scheme = "http"
|
||||
)
|
||||
|
||||
if tlsConfig != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
if in != nil {
|
||||
if file, ok := in.(*os.File); ok {
|
||||
inFd = file.Fd()
|
||||
isTerminalIn = term.IsTerminal(inFd)
|
||||
}
|
||||
}
|
||||
|
||||
if out != nil {
|
||||
if file, ok := out.(*os.File); ok {
|
||||
outFd = file.Fd()
|
||||
isTerminalOut = term.IsTerminal(outFd)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
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{
|
||||
proto: proto,
|
||||
addr: addr,
|
||||
in: in,
|
||||
out: out,
|
||||
err: err,
|
||||
key: key,
|
||||
inFd: inFd,
|
||||
outFd: outFd,
|
||||
isTerminalIn: isTerminalIn,
|
||||
isTerminalOut: isTerminalOut,
|
||||
tlsConfig: tlsConfig,
|
||||
scheme: scheme,
|
||||
transport: tr,
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,230 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/dockerversion"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
)
|
||||
|
||||
type tlsClientCon struct {
|
||||
*tls.Conn
|
||||
rawConn net.Conn
|
||||
}
|
||||
|
||||
func (c *tlsClientCon) CloseWrite() error {
|
||||
// Go standard tls.Conn doesn't provide the CloseWrite() method so we do it
|
||||
// on its underlying connection.
|
||||
if cwc, ok := c.rawConn.(interface {
|
||||
CloseWrite() error
|
||||
}); ok {
|
||||
return cwc.CloseWrite()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
|
||||
return tlsDialWithDialer(new(net.Dialer), network, addr, config)
|
||||
}
|
||||
|
||||
// We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in
|
||||
// order to return our custom tlsClientCon struct which holds both the tls.Conn
|
||||
// object _and_ its underlying raw connection. The rationale for this is that
|
||||
// we need to be able to close the write end of the connection when attaching,
|
||||
// which tls.Conn does not provide.
|
||||
func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
|
||||
// We want the Timeout and Deadline values from dialer to cover the
|
||||
// whole process: TCP connection and TLS handshake. This means that we
|
||||
// also need to start our own timers now.
|
||||
timeout := dialer.Timeout
|
||||
|
||||
if !dialer.Deadline.IsZero() {
|
||||
deadlineTimeout := dialer.Deadline.Sub(time.Now())
|
||||
if timeout == 0 || deadlineTimeout < timeout {
|
||||
timeout = deadlineTimeout
|
||||
}
|
||||
}
|
||||
|
||||
var errChannel chan error
|
||||
|
||||
if timeout != 0 {
|
||||
errChannel = make(chan error, 2)
|
||||
time.AfterFunc(timeout, func() {
|
||||
errChannel <- errors.New("")
|
||||
})
|
||||
}
|
||||
|
||||
rawConn, err := dialer.Dial(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
colonPos := strings.LastIndex(addr, ":")
|
||||
if colonPos == -1 {
|
||||
colonPos = len(addr)
|
||||
}
|
||||
hostname := addr[:colonPos]
|
||||
|
||||
// If no ServerName is set, infer the ServerName
|
||||
// from the hostname we're connecting to.
|
||||
if config.ServerName == "" {
|
||||
// Make a copy to avoid polluting argument or default.
|
||||
c := *config
|
||||
c.ServerName = hostname
|
||||
config = &c
|
||||
}
|
||||
|
||||
conn := tls.Client(rawConn, config)
|
||||
|
||||
if timeout == 0 {
|
||||
err = conn.Handshake()
|
||||
} else {
|
||||
go func() {
|
||||
errChannel <- conn.Handshake()
|
||||
}()
|
||||
|
||||
err = <-errChannel
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
rawConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This is Docker difference with standard's crypto/tls package: returned a
|
||||
// wrapper which holds both the TLS and raw connections.
|
||||
return &tlsClientCon{conn, rawConn}, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) dial() (net.Conn, error) {
|
||||
if cli.tlsConfig != nil && cli.proto != "unix" {
|
||||
// Notice this isn't Go standard's tls.Dial function
|
||||
return tlsDial(cli.proto, cli.addr, cli.tlsConfig)
|
||||
}
|
||||
return net.Dial(cli.proto, cli.addr)
|
||||
}
|
||||
|
||||
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer, data interface{}) error {
|
||||
defer func() {
|
||||
if started != nil {
|
||||
close(started)
|
||||
}
|
||||
}()
|
||||
|
||||
params, err := cli.encodeData(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
req.Host = cli.addr
|
||||
|
||||
dial, err := cli.dial()
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
||||
}
|
||||
return err
|
||||
}
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
defer clientconn.Close()
|
||||
|
||||
// Server hijacks the connection, error 'connection closed' expected
|
||||
clientconn.Do(req)
|
||||
|
||||
rwc, br := clientconn.Hijack()
|
||||
defer rwc.Close()
|
||||
|
||||
if started != nil {
|
||||
started <- rwc
|
||||
}
|
||||
|
||||
var receiveStdout chan error
|
||||
|
||||
var oldState *term.State
|
||||
|
||||
if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" {
|
||||
oldState, err = term.SetRawTerminal(cli.inFd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer term.RestoreTerminal(cli.inFd, oldState)
|
||||
}
|
||||
|
||||
if stdout != nil || stderr != nil {
|
||||
receiveStdout = promise.Go(func() (err error) {
|
||||
defer func() {
|
||||
if in != nil {
|
||||
if setRawTerminal && cli.isTerminalIn {
|
||||
term.RestoreTerminal(cli.inFd, oldState)
|
||||
}
|
||||
// For some reason this Close call blocks on darwin..
|
||||
// As the client exists right after, simply discard the close
|
||||
// until we find a better solution.
|
||||
if runtime.GOOS != "darwin" {
|
||||
in.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// When TTY is ON, use regular copy
|
||||
if setRawTerminal && stdout != nil {
|
||||
_, err = io.Copy(stdout, br)
|
||||
} else {
|
||||
_, err = stdcopy.StdCopy(stdout, stderr, br)
|
||||
}
|
||||
log.Debugf("[hijack] End of stdout")
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
sendStdin := promise.Go(func() error {
|
||||
if in != nil {
|
||||
io.Copy(rwc, in)
|
||||
log.Debugf("[hijack] End of stdin")
|
||||
}
|
||||
|
||||
if conn, ok := rwc.(interface {
|
||||
CloseWrite() error
|
||||
}); ok {
|
||||
if err := conn.CloseWrite(); err != nil {
|
||||
log.Debugf("Couldn't send EOF: %s", err)
|
||||
}
|
||||
}
|
||||
// Discard errors due to pipe interruption
|
||||
return nil
|
||||
})
|
||||
|
||||
if stdout != nil || stderr != nil {
|
||||
if err := <-receiveStdout; err != nil {
|
||||
log.Debugf("Error receiveStdout: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !cli.isTerminalIn {
|
||||
if err := <-sendStdin; err != nil {
|
||||
log.Debugf("Error sendStdin: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
gosignal "os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/dockerversion"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
||||
)
|
||||
|
||||
func (cli *DockerCli) HTTPClient() *http.Client {
|
||||
return &http.Client{Transport: cli.transport}
|
||||
}
|
||||
|
||||
func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
|
||||
params := bytes.NewBuffer(nil)
|
||||
if data != nil {
|
||||
if env, ok := data.(engine.Env); ok {
|
||||
if err := env.Encode(params); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
buf, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := params.Write(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
|
||||
params, err := cli.encodeData(data)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
if passAuthInfo {
|
||||
cli.LoadConfigFile()
|
||||
// Resolve the Auth config relevant for this server
|
||||
authConfig := cli.configFile.ResolveAuthConfig(registry.IndexServerAddress())
|
||||
getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) {
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
registryAuthHeader := []string{
|
||||
base64.URLEncoding.EncodeToString(buf),
|
||||
}
|
||||
return map[string][]string{"X-Registry-Auth": registryAuthHeader}, nil
|
||||
}
|
||||
if headers, err := getHeaders(authConfig); err == nil && headers != nil {
|
||||
for k, v := range headers {
|
||||
req.Header[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
||||
req.URL.Host = cli.addr
|
||||
req.URL.Scheme = cli.scheme
|
||||
if data != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else if method == "POST" {
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
}
|
||||
resp, err := cli.HTTPClient().Do(req)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return nil, -1, ErrConnectionRefused
|
||||
}
|
||||
|
||||
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 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
if len(body) == 0 {
|
||||
return nil, resp.StatusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(resp.StatusCode), req.URL)
|
||||
}
|
||||
return nil, resp.StatusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
||||
}
|
||||
|
||||
return resp.Body, resp.StatusCode, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error {
|
||||
return cli.streamHelper(method, path, true, in, out, nil, headers)
|
||||
}
|
||||
|
||||
func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in io.Reader, stdout, stderr io.Writer, headers map[string][]string) error {
|
||||
if (method == "POST" || method == "PUT") && in == nil {
|
||||
in = bytes.NewReader([]byte{})
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
||||
req.URL.Host = cli.addr
|
||||
req.URL.Scheme = cli.scheme
|
||||
if method == "POST" {
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
}
|
||||
|
||||
if headers != nil {
|
||||
for k, v := range headers {
|
||||
req.Header[k] = v
|
||||
}
|
||||
}
|
||||
resp, err := cli.HTTPClient().Do(req)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(body) == 0 {
|
||||
return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode))
|
||||
}
|
||||
return fmt.Errorf("Error: %s", bytes.TrimSpace(body))
|
||||
}
|
||||
|
||||
if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") {
|
||||
return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.outFd, cli.isTerminalOut)
|
||||
}
|
||||
if stdout != nil || stderr != nil {
|
||||
// When TTY is ON, use regular copy
|
||||
if setRawTerminal {
|
||||
_, err = io.Copy(stdout, resp.Body)
|
||||
} else {
|
||||
_, err = stdcopy.StdCopy(stdout, stderr, resp.Body)
|
||||
}
|
||||
log.Debugf("[stream] End of stdout")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) resizeTty(id string, isExec bool) {
|
||||
height, width := cli.getTtySize()
|
||||
if height == 0 && width == 0 {
|
||||
return
|
||||
}
|
||||
v := url.Values{}
|
||||
v.Set("h", strconv.Itoa(height))
|
||||
v.Set("w", strconv.Itoa(width))
|
||||
|
||||
path := ""
|
||||
if !isExec {
|
||||
path = "/containers/" + id + "/resize?"
|
||||
} else {
|
||||
path = "/exec/" + id + "/resize?"
|
||||
}
|
||||
|
||||
if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, false)); err != nil {
|
||||
log.Debugf("Error resize: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func waitForExit(cli *DockerCli, containerId string) (int, error) {
|
||||
stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
var out engine.Env
|
||||
if err := out.Decode(stream); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return out.GetInt("StatusCode"), nil
|
||||
}
|
||||
|
||||
// getExitCode perform an inspect on the container. It returns
|
||||
// the running state and the exit code.
|
||||
func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
||||
stream, _, err := cli.call("GET", "/containers/"+containerId+"/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
|
||||
}
|
||||
|
||||
state := result.GetSubEnv("State")
|
||||
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 {
|
||||
cli.resizeTty(id, isExec)
|
||||
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
gosignal.Notify(sigchan, signal.SIGWINCH)
|
||||
go func() {
|
||||
for _ = range sigchan {
|
||||
cli.resizeTty(id, isExec)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) getTtySize() (int, int) {
|
||||
if !cli.isTerminalOut {
|
||||
return 0, 0
|
||||
}
|
||||
ws, err := term.GetWinsize(cli.outFd)
|
||||
if err != nil {
|
||||
log.Debugf("Error getting size: %s", err)
|
||||
if ws == nil {
|
||||
return 0, 0
|
||||
}
|
||||
}
|
||||
return int(ws.Height), int(ws.Width)
|
||||
}
|
||||
|
||||
func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, error) {
|
||||
if stream != nil {
|
||||
defer stream.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, statusCode, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(stream)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
return body, statusCode, nil
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/pkg/version"
|
||||
"github.com/docker/libtrust"
|
||||
)
|
||||
|
||||
const (
|
||||
APIVERSION version.Version = "1.16"
|
||||
DEFAULTHTTPHOST = "127.0.0.1"
|
||||
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
||||
)
|
||||
|
||||
func ValidateHost(val string) (string, error) {
|
||||
host, err := parsers.ParseHost(DEFAULTHTTPHOST, DEFAULTUNIXSOCKET, val)
|
||||
if err != nil {
|
||||
return val, err
|
||||
}
|
||||
return host, nil
|
||||
}
|
||||
|
||||
//TODO remove, used on < 1.5 in getContainersJSON
|
||||
func DisplayablePorts(ports *engine.Table) string {
|
||||
result := []string{}
|
||||
ports.SetKey("PublicPort")
|
||||
ports.Sort()
|
||||
for _, port := range ports.Data {
|
||||
if port.Get("IP") == "" {
|
||||
result = append(result, fmt.Sprintf("%d/%s", port.GetInt("PrivatePort"), port.Get("Type")))
|
||||
} else {
|
||||
result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type")))
|
||||
}
|
||||
}
|
||||
return strings.Join(result, ", ")
|
||||
}
|
||||
|
||||
func MatchesContentType(contentType, expectedType string) bool {
|
||||
mimetype, _, err := mime.ParseMediaType(contentType)
|
||||
if err != nil {
|
||||
log.Errorf("Error parsing media type: %s error: %s", contentType, err.Error())
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
Victor Vieux <vieux@docker.com> (@vieux)
|
||||
Johan Euphrosine <proppy@google.com> (@proppy)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,555 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/pkg/version"
|
||||
)
|
||||
|
||||
func TestGetBoolParam(t *testing.T) {
|
||||
if ret, err := getBoolParam("true"); err != nil || !ret {
|
||||
t.Fatalf("true -> true, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam("True"); err != nil || !ret {
|
||||
t.Fatalf("True -> true, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam("1"); err != nil || !ret {
|
||||
t.Fatalf("1 -> true, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam(""); err != nil || ret {
|
||||
t.Fatalf("\"\" -> false, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam("false"); err != nil || ret {
|
||||
t.Fatalf("false -> false, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam("0"); err != nil || ret {
|
||||
t.Fatalf("0 -> false, nil | got %t %s", ret, err)
|
||||
}
|
||||
if ret, err := getBoolParam("faux"); err == nil || ret {
|
||||
t.Fatalf("faux -> false, err | got %t %s", ret, err)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TesthttpError(t *testing.T) {
|
||||
r := httptest.NewRecorder()
|
||||
|
||||
httpError(r, fmt.Errorf("No such method"))
|
||||
if r.Code != http.StatusNotFound {
|
||||
t.Fatalf("Expected %d, got %d", http.StatusNotFound, r.Code)
|
||||
}
|
||||
|
||||
httpError(r, fmt.Errorf("This accound hasn't been activated"))
|
||||
if r.Code != http.StatusForbidden {
|
||||
t.Fatalf("Expected %d, got %d", http.StatusForbidden, r.Code)
|
||||
}
|
||||
|
||||
httpError(r, fmt.Errorf("Some error"))
|
||||
if r.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("Expected %d, got %d", http.StatusInternalServerError, r.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVersion(t *testing.T) {
|
||||
eng := engine.New()
|
||||
var called bool
|
||||
eng.Register("version", func(job *engine.Job) engine.Status {
|
||||
called = true
|
||||
v := &engine.Env{}
|
||||
v.SetJson("Version", "42.1")
|
||||
v.Set("ApiVersion", "1.1.1.1.1")
|
||||
v.Set("GoVersion", "2.42")
|
||||
v.Set("Os", "Linux")
|
||||
v.Set("Arch", "x86_64")
|
||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
return engine.StatusOK
|
||||
})
|
||||
r := serveRequest("GET", "/version", nil, eng, t)
|
||||
if !called {
|
||||
t.Fatalf("handler was not called")
|
||||
}
|
||||
v := readEnv(r.Body, t)
|
||||
if v.Get("Version") != "42.1" {
|
||||
t.Fatalf("%#v\n", v)
|
||||
}
|
||||
if r.HeaderMap.Get("Content-Type") != "application/json" {
|
||||
t.Fatalf("%#v\n", r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetInfo(t *testing.T) {
|
||||
eng := engine.New()
|
||||
var called bool
|
||||
eng.Register("info", func(job *engine.Job) engine.Status {
|
||||
called = true
|
||||
v := &engine.Env{}
|
||||
v.SetInt("Containers", 1)
|
||||
v.SetInt("Images", 42000)
|
||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
return engine.StatusOK
|
||||
})
|
||||
r := serveRequest("GET", "/info", nil, eng, t)
|
||||
if !called {
|
||||
t.Fatalf("handler was not called")
|
||||
}
|
||||
v := readEnv(r.Body, t)
|
||||
if v.GetInt("Images") != 42000 {
|
||||
t.Fatalf("%#v\n", v)
|
||||
}
|
||||
if v.GetInt("Containers") != 1 {
|
||||
t.Fatalf("%#v\n", v)
|
||||
}
|
||||
assertContentType(r, "application/json", t)
|
||||
}
|
||||
|
||||
func TestGetImagesJSON(t *testing.T) {
|
||||
eng := engine.New()
|
||||
var called bool
|
||||
eng.Register("images", func(job *engine.Job) engine.Status {
|
||||
called = true
|
||||
v := createEnvFromGetImagesJSONStruct(sampleImage)
|
||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
return engine.StatusOK
|
||||
})
|
||||
r := serveRequest("GET", "/images/json", nil, eng, t)
|
||||
if !called {
|
||||
t.Fatal("handler was not called")
|
||||
}
|
||||
assertHttpNotError(r, t)
|
||||
assertContentType(r, "application/json", t)
|
||||
var observed getImagesJSONStruct
|
||||
if err := json.Unmarshal(r.Body.Bytes(), &observed); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(observed, sampleImage) {
|
||||
t.Errorf("Expected %#v but got %#v", sampleImage, observed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetImagesJSONFilter(t *testing.T) {
|
||||
eng := engine.New()
|
||||
filter := "nothing"
|
||||
eng.Register("images", func(job *engine.Job) engine.Status {
|
||||
filter = job.Getenv("filter")
|
||||
return engine.StatusOK
|
||||
})
|
||||
serveRequest("GET", "/images/json?filter=aaaa", nil, eng, t)
|
||||
if filter != "aaaa" {
|
||||
t.Errorf("%#v", filter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetImagesJSONFilters(t *testing.T) {
|
||||
eng := engine.New()
|
||||
filter := "nothing"
|
||||
eng.Register("images", func(job *engine.Job) engine.Status {
|
||||
filter = job.Getenv("filters")
|
||||
return engine.StatusOK
|
||||
})
|
||||
serveRequest("GET", "/images/json?filters=nnnn", nil, eng, t)
|
||||
if filter != "nnnn" {
|
||||
t.Errorf("%#v", filter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetImagesJSONAll(t *testing.T) {
|
||||
eng := engine.New()
|
||||
allFilter := "-1"
|
||||
eng.Register("images", func(job *engine.Job) engine.Status {
|
||||
allFilter = job.Getenv("all")
|
||||
return engine.StatusOK
|
||||
})
|
||||
serveRequest("GET", "/images/json?all=1", nil, eng, t)
|
||||
if allFilter != "1" {
|
||||
t.Errorf("%#v", allFilter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetImagesJSONLegacyFormat(t *testing.T) {
|
||||
eng := engine.New()
|
||||
var called bool
|
||||
eng.Register("images", func(job *engine.Job) engine.Status {
|
||||
called = true
|
||||
outsLegacy := engine.NewTable("Created", 0)
|
||||
outsLegacy.Add(createEnvFromGetImagesJSONStruct(sampleImage))
|
||||
if _, err := outsLegacy.WriteListTo(job.Stdout); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
return engine.StatusOK
|
||||
})
|
||||
r := serveRequestUsingVersion("GET", "/images/json", "1.6", nil, eng, t)
|
||||
if !called {
|
||||
t.Fatal("handler was not called")
|
||||
}
|
||||
assertHttpNotError(r, t)
|
||||
assertContentType(r, "application/json", t)
|
||||
images := engine.NewTable("Created", 0)
|
||||
if _, err := images.ReadListFrom(r.Body.Bytes()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if images.Len() != 1 {
|
||||
t.Fatalf("Expected 1 image, %d found", images.Len())
|
||||
}
|
||||
image := images.Data[0]
|
||||
if image.Get("Tag") != "test-tag" {
|
||||
t.Errorf("Expected tag 'test-tag', found '%s'", image.Get("Tag"))
|
||||
}
|
||||
if image.Get("Repository") != "test-name" {
|
||||
t.Errorf("Expected repository 'test-name', found '%s'", image.Get("Repository"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetContainersByName(t *testing.T) {
|
||||
eng := engine.New()
|
||||
name := "container_name"
|
||||
var called bool
|
||||
eng.Register("container_inspect", func(job *engine.Job) engine.Status {
|
||||
called = true
|
||||
if job.Args[0] != name {
|
||||
t.Errorf("name != '%s': %#v", name, job.Args[0])
|
||||
}
|
||||
if api.APIVERSION.LessThan("1.12") && !job.GetenvBool("dirty") {
|
||||
t.Errorf("dirty env variable not set")
|
||||
} else if api.APIVERSION.GreaterThanOrEqualTo("1.12") && job.GetenvBool("dirty") {
|
||||
t.Errorf("dirty env variable set when it shouldn't")
|
||||
}
|
||||
v := &engine.Env{}
|
||||
v.SetBool("dirty", true)
|
||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
return engine.StatusOK
|
||||
})
|
||||
r := serveRequest("GET", "/containers/"+name+"/json", nil, eng, t)
|
||||
if !called {
|
||||
t.Fatal("handler was not called")
|
||||
}
|
||||
assertContentType(r, "application/json", t)
|
||||
var stdoutJson interface{}
|
||||
if err := json.Unmarshal(r.Body.Bytes(), &stdoutJson); err != nil {
|
||||
t.Fatalf("%#v", err)
|
||||
}
|
||||
if stdoutJson.(map[string]interface{})["dirty"].(float64) != 1 {
|
||||
t.Fatalf("%#v", stdoutJson)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEvents(t *testing.T) {
|
||||
eng := engine.New()
|
||||
var called bool
|
||||
eng.Register("events", func(job *engine.Job) engine.Status {
|
||||
called = true
|
||||
since := job.Getenv("since")
|
||||
if since != "1" {
|
||||
t.Fatalf("'since' should be 1, found %#v instead", since)
|
||||
}
|
||||
until := job.Getenv("until")
|
||||
if until != "0" {
|
||||
t.Fatalf("'until' should be 0, found %#v instead", until)
|
||||
}
|
||||
v := &engine.Env{}
|
||||
v.Set("since", since)
|
||||
v.Set("until", until)
|
||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
return engine.StatusOK
|
||||
})
|
||||
r := serveRequest("GET", "/events?since=1&until=0", nil, eng, t)
|
||||
if !called {
|
||||
t.Fatal("handler was not called")
|
||||
}
|
||||
assertContentType(r, "application/json", t)
|
||||
var stdout_json struct {
|
||||
Since int
|
||||
Until int
|
||||
}
|
||||
if err := json.Unmarshal(r.Body.Bytes(), &stdout_json); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stdout_json.Since != 1 {
|
||||
t.Errorf("since != 1: %#v", stdout_json.Since)
|
||||
}
|
||||
if stdout_json.Until != 0 {
|
||||
t.Errorf("until != 0: %#v", stdout_json.Until)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogs(t *testing.T) {
|
||||
eng := engine.New()
|
||||
var inspect bool
|
||||
var logs bool
|
||||
eng.Register("container_inspect", func(job *engine.Job) engine.Status {
|
||||
inspect = true
|
||||
if len(job.Args) == 0 {
|
||||
t.Fatal("Job arguments is empty")
|
||||
}
|
||||
if job.Args[0] != "test" {
|
||||
t.Fatalf("Container name %s, must be test", job.Args[0])
|
||||
}
|
||||
return engine.StatusOK
|
||||
})
|
||||
expected := "logs"
|
||||
eng.Register("logs", func(job *engine.Job) engine.Status {
|
||||
logs = true
|
||||
if len(job.Args) == 0 {
|
||||
t.Fatal("Job arguments is empty")
|
||||
}
|
||||
if job.Args[0] != "test" {
|
||||
t.Fatalf("Container name %s, must be test", job.Args[0])
|
||||
}
|
||||
follow := job.Getenv("follow")
|
||||
if follow != "1" {
|
||||
t.Fatalf("follow: %s, must be 1", follow)
|
||||
}
|
||||
stdout := job.Getenv("stdout")
|
||||
if stdout != "1" {
|
||||
t.Fatalf("stdout %s, must be 1", stdout)
|
||||
}
|
||||
stderr := job.Getenv("stderr")
|
||||
if stderr != "" {
|
||||
t.Fatalf("stderr %s, must be empty", stderr)
|
||||
}
|
||||
timestamps := job.Getenv("timestamps")
|
||||
if timestamps != "1" {
|
||||
t.Fatalf("timestamps %s, must be 1", timestamps)
|
||||
}
|
||||
job.Stdout.Write([]byte(expected))
|
||||
return engine.StatusOK
|
||||
})
|
||||
r := serveRequest("GET", "/containers/test/logs?follow=1&stdout=1×tamps=1", nil, eng, t)
|
||||
if r.Code != http.StatusOK {
|
||||
t.Fatalf("Got status %d, expected %d", r.Code, http.StatusOK)
|
||||
}
|
||||
if !inspect {
|
||||
t.Fatal("container_inspect job was not called")
|
||||
}
|
||||
if !logs {
|
||||
t.Fatal("logs job was not called")
|
||||
}
|
||||
res := r.Body.String()
|
||||
if res != expected {
|
||||
t.Fatalf("Output %s, expected %s", res, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogsNoStreams(t *testing.T) {
|
||||
eng := engine.New()
|
||||
var inspect bool
|
||||
var logs bool
|
||||
eng.Register("container_inspect", func(job *engine.Job) engine.Status {
|
||||
inspect = true
|
||||
if len(job.Args) == 0 {
|
||||
t.Fatal("Job arguments is empty")
|
||||
}
|
||||
if job.Args[0] != "test" {
|
||||
t.Fatalf("Container name %s, must be test", job.Args[0])
|
||||
}
|
||||
return engine.StatusOK
|
||||
})
|
||||
eng.Register("logs", func(job *engine.Job) engine.Status {
|
||||
logs = true
|
||||
return engine.StatusOK
|
||||
})
|
||||
r := serveRequest("GET", "/containers/test/logs", nil, eng, t)
|
||||
if r.Code != http.StatusBadRequest {
|
||||
t.Fatalf("Got status %d, expected %d", r.Code, http.StatusBadRequest)
|
||||
}
|
||||
if inspect {
|
||||
t.Fatal("container_inspect job was called, but it shouldn't")
|
||||
}
|
||||
if logs {
|
||||
t.Fatal("logs job was called, but it shouldn't")
|
||||
}
|
||||
res := strings.TrimSpace(r.Body.String())
|
||||
expected := "Bad parameters: you must choose at least one stream"
|
||||
if !strings.Contains(res, expected) {
|
||||
t.Fatalf("Output %s, expected %s in it", res, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetImagesHistory(t *testing.T) {
|
||||
eng := engine.New()
|
||||
imageName := "docker-test-image"
|
||||
var called bool
|
||||
eng.Register("history", func(job *engine.Job) engine.Status {
|
||||
called = true
|
||||
if len(job.Args) == 0 {
|
||||
t.Fatal("Job arguments is empty")
|
||||
}
|
||||
if job.Args[0] != imageName {
|
||||
t.Fatalf("name != '%s': %#v", imageName, job.Args[0])
|
||||
}
|
||||
v := &engine.Env{}
|
||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
return engine.StatusOK
|
||||
})
|
||||
r := serveRequest("GET", "/images/"+imageName+"/history", nil, eng, t)
|
||||
if !called {
|
||||
t.Fatalf("handler was not called")
|
||||
}
|
||||
if r.Code != http.StatusOK {
|
||||
t.Fatalf("Got status %d, expected %d", r.Code, http.StatusOK)
|
||||
}
|
||||
if r.HeaderMap.Get("Content-Type") != "application/json" {
|
||||
t.Fatalf("%#v\n", r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetImagesByName(t *testing.T) {
|
||||
eng := engine.New()
|
||||
name := "image_name"
|
||||
var called bool
|
||||
eng.Register("image_inspect", func(job *engine.Job) engine.Status {
|
||||
called = true
|
||||
if job.Args[0] != name {
|
||||
t.Fatalf("name != '%s': %#v", name, job.Args[0])
|
||||
}
|
||||
if api.APIVERSION.LessThan("1.12") && !job.GetenvBool("dirty") {
|
||||
t.Fatal("dirty env variable not set")
|
||||
} else if api.APIVERSION.GreaterThanOrEqualTo("1.12") && job.GetenvBool("dirty") {
|
||||
t.Fatal("dirty env variable set when it shouldn't")
|
||||
}
|
||||
v := &engine.Env{}
|
||||
v.SetBool("dirty", true)
|
||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
return engine.StatusOK
|
||||
})
|
||||
r := serveRequest("GET", "/images/"+name+"/json", nil, eng, t)
|
||||
if !called {
|
||||
t.Fatal("handler was not called")
|
||||
}
|
||||
if r.HeaderMap.Get("Content-Type") != "application/json" {
|
||||
t.Fatalf("%#v\n", r)
|
||||
}
|
||||
var stdoutJson interface{}
|
||||
if err := json.Unmarshal(r.Body.Bytes(), &stdoutJson); err != nil {
|
||||
t.Fatalf("%#v", err)
|
||||
}
|
||||
if stdoutJson.(map[string]interface{})["dirty"].(float64) != 1 {
|
||||
t.Fatalf("%#v", stdoutJson)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteContainers(t *testing.T) {
|
||||
eng := engine.New()
|
||||
name := "foo"
|
||||
var called bool
|
||||
eng.Register("rm", func(job *engine.Job) engine.Status {
|
||||
called = true
|
||||
if len(job.Args) == 0 {
|
||||
t.Fatalf("Job arguments is empty")
|
||||
}
|
||||
if job.Args[0] != name {
|
||||
t.Fatalf("name != '%s': %#v", name, job.Args[0])
|
||||
}
|
||||
return engine.StatusOK
|
||||
})
|
||||
r := serveRequest("DELETE", "/containers/"+name, nil, eng, t)
|
||||
if !called {
|
||||
t.Fatalf("handler was not called")
|
||||
}
|
||||
if r.Code != http.StatusNoContent {
|
||||
t.Fatalf("Got status %d, expected %d", r.Code, http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
func serveRequest(method, target string, body io.Reader, eng *engine.Engine, t *testing.T) *httptest.ResponseRecorder {
|
||||
return serveRequestUsingVersion(method, target, api.APIVERSION, body, eng, t)
|
||||
}
|
||||
|
||||
func serveRequestUsingVersion(method, target string, version version.Version, body io.Reader, eng *engine.Engine, t *testing.T) *httptest.ResponseRecorder {
|
||||
r := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(method, target, body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ServeRequest(eng, version, r, req); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func readEnv(src io.Reader, t *testing.T) *engine.Env {
|
||||
out := engine.NewOutput()
|
||||
v, err := out.AddEnv()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := io.Copy(out, src); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out.Close()
|
||||
return v
|
||||
}
|
||||
|
||||
func toJson(data interface{}, t *testing.T) io.Reader {
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return &buf
|
||||
}
|
||||
|
||||
func assertContentType(recorder *httptest.ResponseRecorder, content_type string, t *testing.T) {
|
||||
if recorder.HeaderMap.Get("Content-Type") != content_type {
|
||||
t.Fatalf("%#v\n", recorder)
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Duplicated from integration/utils_test.go, but maybe that's OK as that
|
||||
// should die as soon as we converted all integration tests?
|
||||
// assertHttpNotError expect the given response to not have an error.
|
||||
// Otherwise the it causes the test to fail.
|
||||
func assertHttpNotError(r *httptest.ResponseRecorder, t *testing.T) {
|
||||
// Non-error http status are [200, 400)
|
||||
if r.Code < http.StatusOK || r.Code >= http.StatusBadRequest {
|
||||
t.Fatal(fmt.Errorf("Unexpected http error: %v", r.Code))
|
||||
}
|
||||
}
|
||||
|
||||
func createEnvFromGetImagesJSONStruct(data getImagesJSONStruct) *engine.Env {
|
||||
v := &engine.Env{}
|
||||
v.SetList("RepoTags", data.RepoTags)
|
||||
v.Set("Id", data.Id)
|
||||
v.SetInt64("Created", data.Created)
|
||||
v.SetInt64("Size", data.Size)
|
||||
v.SetInt64("VirtualSize", data.VirtualSize)
|
||||
return v
|
||||
}
|
||||
|
||||
type getImagesJSONStruct struct {
|
||||
RepoTags []string
|
||||
Id string
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
}
|
||||
|
||||
var sampleImage getImagesJSONStruct = getImagesJSONStruct{
|
||||
RepoTags: []string{"test-name:test-tag"},
|
||||
Id: "ID",
|
||||
Created: 999,
|
||||
Size: 777,
|
||||
VirtualSize: 666,
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
Tibor Vass <teabee89@gmail.com> (@tiborvass)
|
||||
Erik Hollensbe <github@hollensbe.org> (@erikh)
|
||||
@@ -1,377 +0,0 @@
|
||||
package builder
|
||||
|
||||
// This file contains the dispatchers for each command. Note that
|
||||
// `nullDispatch` is not actually a command, but support for commands we parse
|
||||
// but do nothing with.
|
||||
//
|
||||
// See evaluator.go for a higher level discussion of the whole evaluator
|
||||
// package.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/nat"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/runconfig"
|
||||
)
|
||||
|
||||
// dispatch with no layer / parsing. This is effectively not a command.
|
||||
func nullDispatch(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ENV foo bar
|
||||
//
|
||||
// Sets the environment variable foo to bar, also makes interpolation
|
||||
// in the dockerfile available from the next statement on via ${foo}.
|
||||
//
|
||||
func env(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("ENV is missing arguments")
|
||||
}
|
||||
|
||||
if len(args)%2 != 0 {
|
||||
// should never get here, but just in case
|
||||
return fmt.Errorf("Bad input to ENV, too many args")
|
||||
}
|
||||
|
||||
commitStr := "ENV"
|
||||
|
||||
for j := 0; j < len(args); j++ {
|
||||
// name ==> args[j]
|
||||
// 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++
|
||||
}
|
||||
|
||||
return b.commit("", b.Config.Cmd, commitStr)
|
||||
}
|
||||
|
||||
// MAINTAINER some text <maybe@an.email.address>
|
||||
//
|
||||
// Sets the maintainer metadata.
|
||||
func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("MAINTAINER requires only one argument")
|
||||
}
|
||||
|
||||
b.maintainer = args[0]
|
||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer))
|
||||
}
|
||||
|
||||
// ADD foo /path
|
||||
//
|
||||
// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
|
||||
// exist here. If you do not wish to have this automatic handling, use COPY.
|
||||
//
|
||||
func add(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) < 2 {
|
||||
return fmt.Errorf("ADD requires at least two arguments")
|
||||
}
|
||||
|
||||
return b.runContextCommand(args, true, true, "ADD")
|
||||
}
|
||||
|
||||
// COPY foo /path
|
||||
//
|
||||
// Same as 'ADD' but without the tar and remote url handling.
|
||||
//
|
||||
func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) < 2 {
|
||||
return fmt.Errorf("COPY requires at least two arguments")
|
||||
}
|
||||
|
||||
return b.runContextCommand(args, false, false, "COPY")
|
||||
}
|
||||
|
||||
// FROM imagename
|
||||
//
|
||||
// This sets the image the dockerfile will build on top of.
|
||||
//
|
||||
func from(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("FROM requires one argument")
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
image, err := b.Daemon.Repositories().LookupImage(name)
|
||||
if b.Pull {
|
||||
image, err = b.pullImage(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if b.Daemon.Graph().IsNotExist(err) {
|
||||
image, err = b.pullImage(name)
|
||||
}
|
||||
|
||||
// note that the top level err will still be !nil here if IsNotExist is
|
||||
// not the error. This approach just simplifies hte logic a bit.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return b.processImageFrom(image)
|
||||
}
|
||||
|
||||
// ONBUILD RUN echo yo
|
||||
//
|
||||
// ONBUILD triggers run when the image is used in a FROM statement.
|
||||
//
|
||||
// ONBUILD handling has a lot of special-case functionality, the heading in
|
||||
// evaluator.go and comments around dispatch() in the same file explain the
|
||||
// special cases. search for 'OnBuild' in internals.go for additional special
|
||||
// cases.
|
||||
//
|
||||
func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
|
||||
switch triggerInstruction {
|
||||
case "ONBUILD":
|
||||
return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
|
||||
case "MAINTAINER", "FROM":
|
||||
return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
|
||||
}
|
||||
|
||||
original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "")
|
||||
|
||||
b.Config.OnBuild = append(b.Config.OnBuild, original)
|
||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("ONBUILD %s", original))
|
||||
}
|
||||
|
||||
// WORKDIR /tmp
|
||||
//
|
||||
// Set the working directory for future RUN/CMD/etc statements.
|
||||
//
|
||||
func workdir(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("WORKDIR requires exactly one argument")
|
||||
}
|
||||
|
||||
workdir := args[0]
|
||||
|
||||
if workdir[0] == '/' {
|
||||
b.Config.WorkingDir = workdir
|
||||
} else {
|
||||
if b.Config.WorkingDir == "" {
|
||||
b.Config.WorkingDir = "/"
|
||||
}
|
||||
b.Config.WorkingDir = filepath.Join(b.Config.WorkingDir, workdir)
|
||||
}
|
||||
|
||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
|
||||
}
|
||||
|
||||
// RUN some command yo
|
||||
//
|
||||
// run a command and commit the image. Args are automatically prepended with
|
||||
// 'sh -c' in the event there is only one argument. The difference in
|
||||
// processing:
|
||||
//
|
||||
// RUN echo hi # sh -c echo hi
|
||||
// RUN [ "echo", "hi" ] # echo hi
|
||||
//
|
||||
func run(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if b.image == "" {
|
||||
return fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
|
||||
args = handleJsonArgs(args, attributes)
|
||||
|
||||
if len(args) == 1 {
|
||||
args = append([]string{"/bin/sh", "-c"}, args[0])
|
||||
}
|
||||
|
||||
runCmd := flag.NewFlagSet("run", flag.ContinueOnError)
|
||||
runCmd.SetOutput(ioutil.Discard)
|
||||
runCmd.Usage = nil
|
||||
|
||||
config, _, _, err := runconfig.Parse(runCmd, append([]string{b.image}, args...))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := b.Config.Cmd
|
||||
// set Cmd manually, this is special case only for Dockerfiles
|
||||
b.Config.Cmd = config.Cmd
|
||||
runconfig.Merge(b.Config, config)
|
||||
|
||||
defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
|
||||
|
||||
log.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd)
|
||||
|
||||
hit, err := b.probeCache()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hit {
|
||||
return nil
|
||||
}
|
||||
|
||||
c, err := b.create()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure that we keep the container mounted until the commit
|
||||
// to avoid unmounting and then mounting directly again
|
||||
c.Mount()
|
||||
defer c.Unmount()
|
||||
|
||||
err = b.run(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.commit(c.ID, cmd, "run"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CMD foo
|
||||
//
|
||||
// Set the default command to run in the container (which may be empty).
|
||||
// Argument handling is the same as RUN.
|
||||
//
|
||||
func cmd(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
b.Config.Cmd = handleJsonArgs(args, attributes)
|
||||
|
||||
if !attributes["json"] {
|
||||
b.Config.Cmd = append([]string{"/bin/sh", "-c"}, b.Config.Cmd...)
|
||||
}
|
||||
|
||||
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %v", b.Config.Cmd)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) != 0 {
|
||||
b.cmdSet = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ENTRYPOINT /usr/sbin/nginx
|
||||
//
|
||||
// Set the entrypoint (which defaults to sh -c) to /usr/sbin/nginx. Will
|
||||
// accept the CMD as the arguments to /usr/sbin/nginx.
|
||||
//
|
||||
// Handles command processing similar to CMD and RUN, only b.Config.Entrypoint
|
||||
// is initialized at NewBuilder time instead of through argument parsing.
|
||||
//
|
||||
func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
parsed := handleJsonArgs(args, attributes)
|
||||
|
||||
switch {
|
||||
case attributes["json"]:
|
||||
// ENTRYPOINT ["echo", "hi"]
|
||||
b.Config.Entrypoint = parsed
|
||||
case len(parsed) == 0:
|
||||
// ENTRYPOINT []
|
||||
b.Config.Entrypoint = nil
|
||||
default:
|
||||
// ENTRYPOINT echo hi
|
||||
b.Config.Entrypoint = []string{"/bin/sh", "-c", parsed[0]}
|
||||
}
|
||||
|
||||
// when setting the entrypoint if a CMD was not explicitly set then
|
||||
// set the command to nil
|
||||
if !b.cmdSet {
|
||||
b.Config.Cmd = nil
|
||||
}
|
||||
|
||||
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("ENTRYPOINT %v", b.Config.Entrypoint)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EXPOSE 6667/tcp 7000/tcp
|
||||
//
|
||||
// Expose ports for links and port mappings. This all ends up in
|
||||
// b.Config.ExposedPorts for runconfig.
|
||||
//
|
||||
func expose(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
portsTab := args
|
||||
|
||||
if b.Config.ExposedPorts == nil {
|
||||
b.Config.ExposedPorts = make(nat.PortSet)
|
||||
}
|
||||
|
||||
ports, _, err := nat.ParsePortSpecs(append(portsTab, b.Config.PortSpecs...))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for port := range ports {
|
||||
if _, exists := b.Config.ExposedPorts[port]; !exists {
|
||||
b.Config.ExposedPorts[port] = struct{}{}
|
||||
}
|
||||
}
|
||||
b.Config.PortSpecs = nil
|
||||
|
||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
|
||||
}
|
||||
|
||||
// USER foo
|
||||
//
|
||||
// Set the user to 'foo' for future commands and when running the
|
||||
// ENTRYPOINT/CMD at container run time.
|
||||
//
|
||||
func user(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("USER requires exactly one argument")
|
||||
}
|
||||
|
||||
b.Config.User = args[0]
|
||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("USER %v", args))
|
||||
}
|
||||
|
||||
// VOLUME /foo
|
||||
//
|
||||
// Expose the volume /foo for use. Will also accept the JSON array form.
|
||||
//
|
||||
func volume(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("Volume cannot be empty")
|
||||
}
|
||||
|
||||
if b.Config.Volumes == nil {
|
||||
b.Config.Volumes = map[string]struct{}{}
|
||||
}
|
||||
for _, v := range args {
|
||||
b.Config.Volumes[v] = struct{}{}
|
||||
}
|
||||
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("VOLUME %v", args)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// INSERT is no longer accepted, but we still parse it.
|
||||
func insert(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
// builder is the evaluation step in the Dockerfile parse/evaluate pipeline.
|
||||
//
|
||||
// It incorporates a dispatch table based on the parser.Node values (see the
|
||||
// parser package for more information) that are yielded from the parser itself.
|
||||
// Calling NewBuilder with the BuildOpts struct can be used to customize the
|
||||
// experience for execution purposes only. Parsing is controlled in the parser
|
||||
// package, and this division of resposibility should be respected.
|
||||
//
|
||||
// Please see the jump table targets for the actual invocations, most of which
|
||||
// will call out to the functions in internals.go to deal with their tasks.
|
||||
//
|
||||
// ONBUILD is a special case, which is covered in the onbuild() func in
|
||||
// dispatchers.go.
|
||||
//
|
||||
// The evaluator uses the concept of "steps", which are usually each processable
|
||||
// line in the Dockerfile. Each step is numbered and certain actions are taken
|
||||
// before and after each step, such as creating an image ID and removing temporary
|
||||
// containers and images. Note that ONBUILD creates a kinda-sorta "sub run" which
|
||||
// includes its own set of steps (usually only one of them).
|
||||
package builder
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/builder/parser"
|
||||
"github.com/docker/docker/daemon"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/pkg/tarsum"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDockerfileEmpty = errors.New("Dockerfile cannot be empty")
|
||||
)
|
||||
|
||||
// Environment variable interpolation will happen on these statements only.
|
||||
var replaceEnvAllowed = map[string]struct{}{
|
||||
"env": {},
|
||||
"add": {},
|
||||
"copy": {},
|
||||
"workdir": {},
|
||||
"expose": {},
|
||||
"volume": {},
|
||||
"user": {},
|
||||
}
|
||||
|
||||
var evaluateTable map[string]func(*Builder, []string, map[string]bool, string) error
|
||||
|
||||
func init() {
|
||||
evaluateTable = map[string]func(*Builder, []string, map[string]bool, string) error{
|
||||
"env": env,
|
||||
"maintainer": maintainer,
|
||||
"add": add,
|
||||
"copy": dispatchCopy, // copy() is a go builtin
|
||||
"from": from,
|
||||
"onbuild": onbuild,
|
||||
"workdir": workdir,
|
||||
"run": run,
|
||||
"cmd": cmd,
|
||||
"entrypoint": entrypoint,
|
||||
"expose": expose,
|
||||
"volume": volume,
|
||||
"user": user,
|
||||
"insert": insert,
|
||||
}
|
||||
}
|
||||
|
||||
// internal struct, used to maintain configuration of the Dockerfile's
|
||||
// processing as it evaluates the parsing result.
|
||||
type Builder struct {
|
||||
Daemon *daemon.Daemon
|
||||
Engine *engine.Engine
|
||||
|
||||
// effectively stdio for the run. Because it is not stdio, I said
|
||||
// "Effectively". Do not use stdio anywhere in this package for any reason.
|
||||
OutStream io.Writer
|
||||
ErrStream io.Writer
|
||||
|
||||
Verbose bool
|
||||
UtilizeCache bool
|
||||
|
||||
// controls how images and containers are handled between steps.
|
||||
Remove bool
|
||||
ForceRemove bool
|
||||
Pull bool
|
||||
|
||||
AuthConfig *registry.AuthConfig
|
||||
AuthConfigFile *registry.ConfigFile
|
||||
|
||||
// Deprecated, original writer used for ImagePull. To be removed.
|
||||
OutOld io.Writer
|
||||
StreamFormatter *utils.StreamFormatter
|
||||
|
||||
Config *runconfig.Config // runconfig for cmd, run, entrypoint etc.
|
||||
|
||||
// both of these are controlled by the Remove and ForceRemove options in BuildOpts
|
||||
TmpContainers map[string]struct{} // a map of containers used for removes
|
||||
|
||||
dockerfile *parser.Node // the syntax tree of the dockerfile
|
||||
image string // image name for commit processing
|
||||
maintainer string // maintainer name. could probably be removed.
|
||||
cmdSet bool // indicates is CMD was set in current Dockerfile
|
||||
context tarsum.TarSum // the context is a tarball that is uploaded by the client
|
||||
contextPath string // the path of the temporary directory the local context is unpacked to (server side)
|
||||
|
||||
}
|
||||
|
||||
// Run the builder with the context. This is the lynchpin of this package. This
|
||||
// will (barring errors):
|
||||
//
|
||||
// * call readContext() which will set up the temporary directory and unpack
|
||||
// the context into it.
|
||||
// * read the dockerfile
|
||||
// * parse the dockerfile
|
||||
// * walk the parse tree and execute it by dispatching to handlers. If Remove
|
||||
// or ForceRemove is set, additional cleanup around containers happens after
|
||||
// processing.
|
||||
// * Print a happy message and return the image ID.
|
||||
//
|
||||
func (b *Builder) Run(context io.Reader) (string, error) {
|
||||
if err := b.readContext(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := os.RemoveAll(b.contextPath); err != nil {
|
||||
log.Debugf("[BUILDER] failed to remove temporary context: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
filename := path.Join(b.contextPath, "Dockerfile")
|
||||
|
||||
fi, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("Cannot build a directory without a Dockerfile")
|
||||
}
|
||||
if fi.Size() == 0 {
|
||||
return "", ErrDockerfileEmpty
|
||||
}
|
||||
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
ast, err := parser.Parse(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b.dockerfile = ast
|
||||
|
||||
// some initializations that would not have been supplied by the caller.
|
||||
b.Config = &runconfig.Config{}
|
||||
b.TmpContainers = map[string]struct{}{}
|
||||
|
||||
for i, n := range b.dockerfile.Children {
|
||||
if err := b.dispatch(i, n); err != nil {
|
||||
if b.ForceRemove {
|
||||
b.clearTmp()
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(b.OutStream, " ---> %s\n", utils.TruncateID(b.image))
|
||||
if b.Remove {
|
||||
b.clearTmp()
|
||||
}
|
||||
}
|
||||
|
||||
if b.image == "" {
|
||||
return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?\n")
|
||||
}
|
||||
|
||||
fmt.Fprintf(b.OutStream, "Successfully built %s\n", utils.TruncateID(b.image))
|
||||
return b.image, nil
|
||||
}
|
||||
|
||||
// This method is the entrypoint to all statement handling routines.
|
||||
//
|
||||
// Almost all nodes will have this structure:
|
||||
// Child[Node, Node, Node] where Child is from parser.Node.Children and each
|
||||
// node comes from parser.Node.Next. This forms a "line" with a statement and
|
||||
// arguments and we process them in this normalized form by hitting
|
||||
// evaluateTable with the leaf nodes of the command and the Builder object.
|
||||
//
|
||||
// ONBUILD is a special case; in this case the parser will emit:
|
||||
// Child[Node, Child[Node, Node...]] where the first node is the literal
|
||||
// "onbuild" and the child entrypoint is the command of the ONBUILD statmeent,
|
||||
// such as `RUN` in ONBUILD RUN foo. There is special case logic in here to
|
||||
// deal with that, at least until it becomes more of a general concern with new
|
||||
// features.
|
||||
func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
|
||||
cmd := ast.Value
|
||||
attrs := ast.Attributes
|
||||
original := ast.Original
|
||||
strs := []string{}
|
||||
msg := fmt.Sprintf("Step %d : %s", stepN, strings.ToUpper(cmd))
|
||||
|
||||
if cmd == "onbuild" {
|
||||
ast = ast.Next.Children[0]
|
||||
strs = append(strs, ast.Value)
|
||||
msg += " " + ast.Value
|
||||
}
|
||||
|
||||
for ast.Next != nil {
|
||||
ast = ast.Next
|
||||
var str string
|
||||
str = ast.Value
|
||||
if _, ok := replaceEnvAllowed[cmd]; ok {
|
||||
str = b.replaceEnv(ast.Value)
|
||||
}
|
||||
strs = append(strs, str)
|
||||
msg += " " + ast.Value
|
||||
}
|
||||
|
||||
fmt.Fprintln(b.OutStream, msg)
|
||||
|
||||
// XXX yes, we skip any cmds that are not valid; the parser should have
|
||||
// picked these out already.
|
||||
if f, ok := evaluateTable[cmd]; ok {
|
||||
return f(b, strs, attrs, original)
|
||||
}
|
||||
|
||||
fmt.Fprintf(b.ErrStream, "# Skipping unknown instruction %s\n", strings.ToUpper(cmd))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,705 +0,0 @@
|
||||
package builder
|
||||
|
||||
// internals for handling commands. Covers many areas and a lot of
|
||||
// non-contiguous functionality. Please read the comments.
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/builder/parser"
|
||||
"github.com/docker/docker/daemon"
|
||||
imagepkg "github.com/docker/docker/image"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/chrootarchive"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/pkg/symlink"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/pkg/tarsum"
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
func (b *Builder) readContext(context io.Reader) error {
|
||||
tmpdirPath, err := ioutil.TempDir("", "docker-build")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decompressedStream, err := archive.DecompressStream(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.context, err = tarsum.NewTarSum(decompressedStream, true, tarsum.Version0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := chrootarchive.Untar(b.context, tmpdirPath, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.contextPath = tmpdirPath
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Builder) commit(id string, autoCmd []string, comment string) error {
|
||||
if b.image == "" {
|
||||
return fmt.Errorf("Please provide a source image with `from` prior to commit")
|
||||
}
|
||||
b.Config.Image = b.image
|
||||
if id == "" {
|
||||
cmd := b.Config.Cmd
|
||||
b.Config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
|
||||
defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
|
||||
|
||||
hit, err := b.probeCache()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hit {
|
||||
return nil
|
||||
}
|
||||
|
||||
container, err := b.create()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id = container.ID
|
||||
|
||||
if err := container.Mount(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer container.Unmount()
|
||||
}
|
||||
container := b.Daemon.Get(id)
|
||||
if container == nil {
|
||||
return fmt.Errorf("An error occured while creating the container")
|
||||
}
|
||||
|
||||
// Note: Actually copy the struct
|
||||
autoConfig := *b.Config
|
||||
autoConfig.Cmd = autoCmd
|
||||
|
||||
// Commit the container
|
||||
image, err := b.Daemon.Commit(container, "", "", "", b.maintainer, true, &autoConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.image = image.ID
|
||||
return nil
|
||||
}
|
||||
|
||||
type copyInfo struct {
|
||||
origPath string
|
||||
destPath string
|
||||
hash string
|
||||
decompress bool
|
||||
tmpDir string
|
||||
}
|
||||
|
||||
func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecompression bool, cmdName string) error {
|
||||
if b.context == nil {
|
||||
return fmt.Errorf("No context given. Impossible to use %s", cmdName)
|
||||
}
|
||||
|
||||
if len(args) < 2 {
|
||||
return fmt.Errorf("Invalid %s format - at least two arguments required", cmdName)
|
||||
}
|
||||
|
||||
dest := args[len(args)-1] // last one is always the dest
|
||||
|
||||
copyInfos := []*copyInfo{}
|
||||
|
||||
b.Config.Image = b.image
|
||||
|
||||
defer func() {
|
||||
for _, ci := range copyInfos {
|
||||
if ci.tmpDir != "" {
|
||||
os.RemoveAll(ci.tmpDir)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Loop through each src file and calculate the info we need to
|
||||
// do the copy (e.g. hash value if cached). Don't actually do
|
||||
// the copy until we've looked at all src files
|
||||
for _, orig := range args[0 : len(args)-1] {
|
||||
err := calcCopyInfo(b, cmdName, ©Infos, orig, dest, allowRemote, allowDecompression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(copyInfos) == 0 {
|
||||
return fmt.Errorf("No source files were specified")
|
||||
}
|
||||
|
||||
if len(copyInfos) > 1 && !strings.HasSuffix(dest, "/") {
|
||||
return fmt.Errorf("When using %s with more than one source file, the destination must be a directory and end with a /", cmdName)
|
||||
}
|
||||
|
||||
// For backwards compat, if there's just one CI then use it as the
|
||||
// cache look-up string, otherwise hash 'em all into one
|
||||
var srcHash string
|
||||
var origPaths string
|
||||
|
||||
if len(copyInfos) == 1 {
|
||||
srcHash = copyInfos[0].hash
|
||||
origPaths = copyInfos[0].origPath
|
||||
} else {
|
||||
var hashs []string
|
||||
var origs []string
|
||||
for _, ci := range copyInfos {
|
||||
hashs = append(hashs, ci.hash)
|
||||
origs = append(origs, ci.origPath)
|
||||
}
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(strings.Join(hashs, ",")))
|
||||
srcHash = "multi:" + hex.EncodeToString(hasher.Sum(nil))
|
||||
origPaths = strings.Join(origs, " ")
|
||||
}
|
||||
|
||||
cmd := b.Config.Cmd
|
||||
b.Config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest)}
|
||||
defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
|
||||
|
||||
hit, err := b.probeCache()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// If we do not have at least one hash, never use the cache
|
||||
if hit && b.UtilizeCache {
|
||||
return nil
|
||||
}
|
||||
|
||||
container, _, err := b.Daemon.Create(b.Config, nil, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.TmpContainers[container.ID] = struct{}{}
|
||||
|
||||
if err := container.Mount(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer container.Unmount()
|
||||
|
||||
for _, ci := range copyInfos {
|
||||
if err := b.addContext(container, ci.origPath, ci.destPath, ci.decompress); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.commit(container.ID, cmd, fmt.Sprintf("%s %s in %s", cmdName, origPaths, dest)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath string, destPath string, allowRemote bool, allowDecompression bool) error {
|
||||
|
||||
if origPath != "" && origPath[0] == '/' && len(origPath) > 1 {
|
||||
origPath = origPath[1:]
|
||||
}
|
||||
origPath = strings.TrimPrefix(origPath, "./")
|
||||
|
||||
// In the remote/URL case, download it and gen its hashcode
|
||||
if urlutil.IsURL(origPath) {
|
||||
if !allowRemote {
|
||||
return fmt.Errorf("Source can't be a URL for %s", cmdName)
|
||||
}
|
||||
|
||||
ci := copyInfo{}
|
||||
ci.origPath = origPath
|
||||
ci.hash = origPath // default to this but can change
|
||||
ci.destPath = destPath
|
||||
ci.decompress = false
|
||||
*cInfos = append(*cInfos, &ci)
|
||||
|
||||
// Initiate the download
|
||||
resp, err := utils.Download(ci.origPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a tmp dir
|
||||
tmpDirName, err := ioutil.TempDir(b.contextPath, "docker-remote")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ci.tmpDir = tmpDirName
|
||||
|
||||
// Create a tmp file within our tmp dir
|
||||
tmpFileName := path.Join(tmpDirName, "tmp")
|
||||
tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Download and dump result to tmp file
|
||||
if _, err := io.Copy(tmpFile, utils.ProgressReader(resp.Body, int(resp.ContentLength), b.OutOld, b.StreamFormatter, true, "", "Downloading")); err != nil {
|
||||
tmpFile.Close()
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(b.OutStream, "\n")
|
||||
tmpFile.Close()
|
||||
|
||||
// Set the mtime to the Last-Modified header value if present
|
||||
// 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
|
||||
}
|
||||
|
||||
ci.origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
|
||||
|
||||
// If the destination is a directory, figure out the filename.
|
||||
if strings.HasSuffix(ci.destPath, "/") {
|
||||
u, err := url.Parse(origPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := u.Path
|
||||
if strings.HasSuffix(path, "/") {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
parts := strings.Split(path, "/")
|
||||
filename := parts[len(parts)-1]
|
||||
if filename == "" {
|
||||
return fmt.Errorf("cannot determine filename from url: %s", u)
|
||||
}
|
||||
ci.destPath = ci.destPath + filename
|
||||
}
|
||||
|
||||
// Calc the checksum, only if we're using the cache
|
||||
if b.UtilizeCache {
|
||||
r, err := archive.Tar(tmpFileName, archive.Uncompressed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tarSum, err := tarsum.NewTarSum(r, true, tarsum.Version0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(ioutil.Discard, tarSum); err != nil {
|
||||
return err
|
||||
}
|
||||
ci.hash = tarSum.Sum(nil)
|
||||
r.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deal with wildcards
|
||||
if ContainsWildcards(origPath) {
|
||||
for _, fileInfo := range b.context.GetSums() {
|
||||
if fileInfo.Name() == "" {
|
||||
continue
|
||||
}
|
||||
match, _ := path.Match(origPath, fileInfo.Name())
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
|
||||
calcCopyInfo(b, cmdName, cInfos, fileInfo.Name(), destPath, allowRemote, allowDecompression)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Must be a dir or a file
|
||||
|
||||
if err := b.checkPathForAddition(origPath); err != nil {
|
||||
return err
|
||||
}
|
||||
fi, _ := os.Stat(path.Join(b.contextPath, origPath))
|
||||
|
||||
ci := copyInfo{}
|
||||
ci.origPath = origPath
|
||||
ci.hash = origPath
|
||||
ci.destPath = destPath
|
||||
ci.decompress = allowDecompression
|
||||
*cInfos = append(*cInfos, &ci)
|
||||
|
||||
// If not using cache don't need to do anything else.
|
||||
// If we are using a cache then calc the hash for the src file/dir
|
||||
if !b.UtilizeCache {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deal with the single file case
|
||||
if !fi.IsDir() {
|
||||
// This will match first file in sums of the archive
|
||||
fis := b.context.GetSums().GetFile(ci.origPath)
|
||||
if fis != nil {
|
||||
ci.hash = "file:" + fis.Sum()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Must be a dir
|
||||
var subfiles []string
|
||||
absOrigPath := path.Join(b.contextPath, ci.origPath)
|
||||
|
||||
// Add a trailing / to make sure we only pick up nested files under
|
||||
// the dir and not sibling files of the dir that just happen to
|
||||
// start with the same chars
|
||||
if !strings.HasSuffix(absOrigPath, "/") {
|
||||
absOrigPath += "/"
|
||||
}
|
||||
|
||||
// Need path w/o / too to find matching dir w/o trailing /
|
||||
absOrigPathNoSlash := absOrigPath[:len(absOrigPath)-1]
|
||||
|
||||
for _, fileInfo := range b.context.GetSums() {
|
||||
absFile := path.Join(b.contextPath, fileInfo.Name())
|
||||
if strings.HasPrefix(absFile, absOrigPath) || absFile == absOrigPathNoSlash {
|
||||
subfiles = append(subfiles, fileInfo.Sum())
|
||||
}
|
||||
}
|
||||
sort.Strings(subfiles)
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(strings.Join(subfiles, ",")))
|
||||
ci.hash = "dir:" + hex.EncodeToString(hasher.Sum(nil))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ContainsWildcards(name string) bool {
|
||||
for i := 0; i < len(name); i++ {
|
||||
ch := name[i]
|
||||
if ch == '\\' {
|
||||
i++
|
||||
} else if ch == '*' || ch == '?' || ch == '[' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *Builder) pullImage(name string) (*imagepkg.Image, error) {
|
||||
remote, tag := parsers.ParseRepositoryTag(name)
|
||||
if tag == "" {
|
||||
tag = "latest"
|
||||
}
|
||||
pullRegistryAuth := b.AuthConfig
|
||||
if len(b.AuthConfigFile.Configs) > 0 {
|
||||
// The request came with a full auth config file, we prefer to use that
|
||||
endpoint, _, err := registry.ResolveRepositoryName(remote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(endpoint)
|
||||
pullRegistryAuth = &resolvedAuth
|
||||
}
|
||||
job := b.Engine.Job("pull", remote, tag)
|
||||
job.SetenvBool("json", b.StreamFormatter.Json())
|
||||
job.SetenvBool("parallel", true)
|
||||
job.SetenvJson("authConfig", pullRegistryAuth)
|
||||
job.Stdout.Add(b.OutOld)
|
||||
if err := job.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
image, err := b.Daemon.Repositories().LookupImage(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return image, nil
|
||||
}
|
||||
|
||||
func (b *Builder) processImageFrom(img *imagepkg.Image) error {
|
||||
b.image = img.ID
|
||||
|
||||
if img.Config != nil {
|
||||
b.Config = img.Config
|
||||
}
|
||||
|
||||
if len(b.Config.Env) == 0 {
|
||||
b.Config.Env = append(b.Config.Env, "PATH="+daemon.DefaultPathEnv)
|
||||
}
|
||||
|
||||
// Process ONBUILD triggers if they exist
|
||||
if nTriggers := len(b.Config.OnBuild); nTriggers != 0 {
|
||||
fmt.Fprintf(b.ErrStream, "# Executing %d build triggers\n", nTriggers)
|
||||
}
|
||||
|
||||
// Copy the ONBUILD triggers, and remove them from the config, since the config will be commited.
|
||||
onBuildTriggers := b.Config.OnBuild
|
||||
b.Config.OnBuild = []string{}
|
||||
|
||||
// parse the ONBUILD triggers by invoking the parser
|
||||
for stepN, step := range onBuildTriggers {
|
||||
ast, err := parser.Parse(strings.NewReader(step))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, n := range ast.Children {
|
||||
switch strings.ToUpper(n.Value) {
|
||||
case "ONBUILD":
|
||||
return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
|
||||
case "MAINTAINER", "FROM":
|
||||
return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", n.Value)
|
||||
}
|
||||
|
||||
fmt.Fprintf(b.OutStream, "Trigger %d, %s\n", stepN, step)
|
||||
|
||||
if err := b.dispatch(i, n); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// probeCache checks to see if image-caching is enabled (`b.UtilizeCache`)
|
||||
// and if so attempts to look up the current `b.image` and `b.Config` pair
|
||||
// in the current server `b.Daemon`. If an image is found, probeCache returns
|
||||
// `(true, nil)`. If no image is found, it returns `(false, nil)`. If there
|
||||
// is any error, it returns `(false, err)`.
|
||||
func (b *Builder) probeCache() (bool, error) {
|
||||
if b.UtilizeCache {
|
||||
if cache, err := b.Daemon.ImageGetCached(b.image, b.Config); err != nil {
|
||||
return false, err
|
||||
} else if cache != nil {
|
||||
fmt.Fprintf(b.OutStream, " ---> Using cache\n")
|
||||
log.Debugf("[BUILDER] Use cached version")
|
||||
b.image = cache.ID
|
||||
return true, nil
|
||||
} else {
|
||||
log.Debugf("[BUILDER] Cache miss")
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (b *Builder) create() (*daemon.Container, error) {
|
||||
if b.image == "" {
|
||||
return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
b.Config.Image = b.image
|
||||
|
||||
config := *b.Config
|
||||
|
||||
// Create the container
|
||||
c, warnings, err := b.Daemon.Create(b.Config, nil, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, warning := range warnings {
|
||||
fmt.Fprintf(b.OutStream, " ---> [Warning] %s\n", warning)
|
||||
}
|
||||
|
||||
b.TmpContainers[c.ID] = struct{}{}
|
||||
fmt.Fprintf(b.OutStream, " ---> Running in %s\n", utils.TruncateID(c.ID))
|
||||
|
||||
// override the entry point that may have been picked up from the base image
|
||||
c.Path = config.Cmd[0]
|
||||
c.Args = config.Cmd[1:]
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (b *Builder) run(c *daemon.Container) error {
|
||||
//start the container
|
||||
if err := c.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.Verbose {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for it to finish
|
||||
if ret, _ := c.WaitStop(-1 * time.Second); ret != 0 {
|
||||
err := &utils.JSONError{
|
||||
Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.Config.Cmd, ret),
|
||||
Code: ret,
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Builder) checkPathForAddition(orig string) error {
|
||||
origPath := path.Join(b.contextPath, orig)
|
||||
origPath, err := filepath.EvalSymlinks(origPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("%s: no such file or directory", orig)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !strings.HasPrefix(origPath, b.contextPath) {
|
||||
return fmt.Errorf("Forbidden path outside the build context: %s (%s)", orig, origPath)
|
||||
}
|
||||
if _, err := os.Stat(origPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("%s: no such file or directory", orig)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Builder) addContext(container *daemon.Container, orig, dest string, decompress bool) error {
|
||||
var (
|
||||
err error
|
||||
destExists = true
|
||||
origPath = path.Join(b.contextPath, orig)
|
||||
destPath = path.Join(container.RootfsPath(), dest)
|
||||
)
|
||||
|
||||
if destPath != container.RootfsPath() {
|
||||
destPath, err = symlink.FollowSymlinkInScope(destPath, container.RootfsPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve the trailing '/'
|
||||
if strings.HasSuffix(dest, "/") || dest == "." {
|
||||
destPath = destPath + "/"
|
||||
}
|
||||
|
||||
destStat, err := os.Stat(destPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
destExists = false
|
||||
}
|
||||
|
||||
fi, err := os.Stat(origPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("%s: no such file or directory", orig)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
return copyAsDirectory(origPath, destPath, destExists)
|
||||
}
|
||||
|
||||
// If we are adding a remote file (or we've been told not to decompress), do not try to untar it
|
||||
if decompress {
|
||||
// First try to unpack the source as an archive
|
||||
// to support the untar feature we need to clean up the path a little bit
|
||||
// because tar is very forgiving. First we need to strip off the archive's
|
||||
// filename from the path but this is only added if it does not end in / .
|
||||
tarDest := destPath
|
||||
if strings.HasSuffix(tarDest, "/") {
|
||||
tarDest = filepath.Dir(destPath)
|
||||
}
|
||||
|
||||
// try to successfully untar the orig
|
||||
if err := chrootarchive.UntarPath(origPath, tarDest); err == nil {
|
||||
return nil
|
||||
} else if err != io.EOF {
|
||||
log.Debugf("Couldn't untar %s to %s: %s", origPath, tarDest, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chrootarchive.CopyWithTar(origPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resPath := destPath
|
||||
if destExists && destStat.IsDir() {
|
||||
resPath = path.Join(destPath, path.Base(origPath))
|
||||
}
|
||||
|
||||
return fixPermissions(origPath, resPath, 0, 0, destExists)
|
||||
}
|
||||
|
||||
func copyAsDirectory(source, destination string, destExisted bool) error {
|
||||
if err := chrootarchive.CopyWithTar(source, destination); err != nil {
|
||||
return err
|
||||
}
|
||||
return fixPermissions(source, destination, 0, 0, destExisted)
|
||||
}
|
||||
|
||||
func fixPermissions(source, destination string, uid, gid int, destExisted bool) error {
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
|
||||
fullpath = path.Join(destination, cleaned)
|
||||
return os.Lchown(fullpath, uid, gid)
|
||||
})
|
||||
}
|
||||
|
||||
func (b *Builder) clearTmp() {
|
||||
for c := range b.TmpContainers {
|
||||
tmp := b.Daemon.Get(c)
|
||||
if err := b.Daemon.Destroy(tmp); err != nil {
|
||||
fmt.Fprintf(b.OutStream, "Error removing intermediate container %s: %s\n", utils.TruncateID(c), err.Error())
|
||||
return
|
||||
}
|
||||
b.Daemon.DeleteVolumes(tmp.VolumePaths())
|
||||
delete(b.TmpContainers, c)
|
||||
fmt.Fprintf(b.OutStream, "Removing intermediate container %s\n", utils.TruncateID(c))
|
||||
}
|
||||
}
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/docker/docker/daemon"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/graph"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
type BuilderJob struct {
|
||||
Engine *engine.Engine
|
||||
Daemon *daemon.Daemon
|
||||
}
|
||||
|
||||
func (b *BuilderJob) Install() {
|
||||
b.Engine.Register("build", b.CmdBuild)
|
||||
}
|
||||
|
||||
func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
|
||||
if len(job.Args) != 0 {
|
||||
return job.Errorf("Usage: %s\n", job.Name)
|
||||
}
|
||||
var (
|
||||
remoteURL = job.Getenv("remote")
|
||||
repoName = job.Getenv("t")
|
||||
suppressOutput = job.GetenvBool("q")
|
||||
noCache = job.GetenvBool("nocache")
|
||||
rm = job.GetenvBool("rm")
|
||||
forceRm = job.GetenvBool("forcerm")
|
||||
pull = job.GetenvBool("pull")
|
||||
authConfig = ®istry.AuthConfig{}
|
||||
configFile = ®istry.ConfigFile{}
|
||||
tag string
|
||||
context io.ReadCloser
|
||||
)
|
||||
job.GetenvJson("authConfig", authConfig)
|
||||
job.GetenvJson("configFile", configFile)
|
||||
|
||||
repoName, tag = parsers.ParseRepositoryTag(repoName)
|
||||
if repoName != "" {
|
||||
if _, _, err := registry.ResolveRepositoryName(repoName); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
if len(tag) > 0 {
|
||||
if err := graph.ValidateTagName(tag); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if remoteURL == "" {
|
||||
context = ioutil.NopCloser(job.Stdin)
|
||||
} else if urlutil.IsGitURL(remoteURL) {
|
||||
if !urlutil.IsGitTransport(remoteURL) {
|
||||
remoteURL = "https://" + remoteURL
|
||||
}
|
||||
root, err := ioutil.TempDir("", "docker-build-git")
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
|
||||
if output, err := exec.Command("git", "clone", "--recursive", remoteURL, root).CombinedOutput(); err != nil {
|
||||
return job.Errorf("Error trying to use git: %s (%s)", err, output)
|
||||
}
|
||||
|
||||
c, err := archive.Tar(root, archive.Uncompressed)
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
context = c
|
||||
} else if urlutil.IsURL(remoteURL) {
|
||||
f, err := utils.Download(remoteURL)
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
defer f.Body.Close()
|
||||
dockerFile, err := ioutil.ReadAll(f.Body)
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
c, err := archive.Generate("Dockerfile", string(dockerFile))
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
context = c
|
||||
}
|
||||
defer context.Close()
|
||||
|
||||
sf := utils.NewStreamFormatter(job.GetenvBool("json"))
|
||||
|
||||
builder := &Builder{
|
||||
Daemon: b.Daemon,
|
||||
Engine: b.Engine,
|
||||
OutStream: &utils.StdoutFormater{
|
||||
Writer: job.Stdout,
|
||||
StreamFormatter: sf,
|
||||
},
|
||||
ErrStream: &utils.StderrFormater{
|
||||
Writer: job.Stdout,
|
||||
StreamFormatter: sf,
|
||||
},
|
||||
Verbose: !suppressOutput,
|
||||
UtilizeCache: !noCache,
|
||||
Remove: rm,
|
||||
ForceRemove: forceRm,
|
||||
Pull: pull,
|
||||
OutOld: job.Stdout,
|
||||
StreamFormatter: sf,
|
||||
AuthConfig: authConfig,
|
||||
AuthConfigFile: configFile,
|
||||
}
|
||||
|
||||
id, err := builder.Run(context)
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
|
||||
if repoName != "" {
|
||||
b.Daemon.Repositories().Set(repoName, tag, id, true)
|
||||
}
|
||||
return engine.StatusOK
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/docker/docker/builder/parser"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var f *os.File
|
||||
var err error
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("please supply filename(s)")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for _, fn := range os.Args[1:] {
|
||||
f, err = os.Open(fn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ast, err := parser.Parse(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
fmt.Println(ast.Dump())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
package parser
|
||||
|
||||
// line parsers are dispatch calls that parse a single unit of text into a
|
||||
// Node object which contains the whole statement. Dockerfiles have varied
|
||||
// (but not usually unique, see ONBUILD for a unique example) parsing rules
|
||||
// per-command, and these unify the processing in a way that makes it
|
||||
// manageable.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
errDockerfileJSONNesting = errors.New("You may not nest arrays in Dockerfile statements.")
|
||||
)
|
||||
|
||||
// ignore the current argument. This will still leave a command parsed, but
|
||||
// will not incorporate the arguments into the ast.
|
||||
func parseIgnore(rest string) (*Node, map[string]bool, error) {
|
||||
return &Node{}, nil, nil
|
||||
}
|
||||
|
||||
// used for onbuild. Could potentially be used for anything that represents a
|
||||
// statement with sub-statements.
|
||||
//
|
||||
// ONBUILD RUN foo bar -> (onbuild (run foo bar))
|
||||
//
|
||||
func parseSubCommand(rest string) (*Node, map[string]bool, error) {
|
||||
_, child, err := parseLine(rest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &Node{Children: []*Node{child}}, nil, nil
|
||||
}
|
||||
|
||||
// parse environment like statements. Note that this does *not* handle
|
||||
// variable interpolation, which will be handled in the evaluator.
|
||||
func parseEnv(rest string) (*Node, map[string]bool, error) {
|
||||
// This is kind of tricky because we need to support the old
|
||||
// variant: ENV name value
|
||||
// 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
|
||||
|
||||
const (
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if len(words) == 0 {
|
||||
return nil, nil, fmt.Errorf("ENV must have some arguments")
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// parses a whitespace-delimited set of arguments. The result is effectively a
|
||||
// linked list of string arguments.
|
||||
func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error) {
|
||||
node := &Node{}
|
||||
rootnode := node
|
||||
prevnode := node
|
||||
for _, str := range TOKEN_WHITESPACE.Split(rest, -1) { // use regexp
|
||||
prevnode = node
|
||||
node.Value = str
|
||||
node.Next = &Node{}
|
||||
node = node.Next
|
||||
}
|
||||
|
||||
// XXX to get around regexp.Split *always* providing an empty string at the
|
||||
// end due to how our loop is constructed, nil out the last node in the
|
||||
// chain.
|
||||
prevnode.Next = nil
|
||||
|
||||
return rootnode, nil, nil
|
||||
}
|
||||
|
||||
// parsestring just wraps the string in quotes and returns a working node.
|
||||
func parseString(rest string) (*Node, map[string]bool, error) {
|
||||
n := &Node{}
|
||||
n.Value = rest
|
||||
return n, nil, nil
|
||||
}
|
||||
|
||||
// parseJSON converts JSON arrays to an AST.
|
||||
func parseJSON(rest string) (*Node, map[string]bool, error) {
|
||||
var (
|
||||
myJson []interface{}
|
||||
next = &Node{}
|
||||
orignext = next
|
||||
prevnode = next
|
||||
)
|
||||
|
||||
if err := json.Unmarshal([]byte(rest), &myJson); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, str := range myJson {
|
||||
switch str.(type) {
|
||||
case string:
|
||||
case float64:
|
||||
str = strconv.FormatFloat(str.(float64), 'G', -1, 64)
|
||||
default:
|
||||
return nil, nil, errDockerfileJSONNesting
|
||||
}
|
||||
next.Value = str.(string)
|
||||
next.Next = &Node{}
|
||||
prevnode = next
|
||||
next = next.Next
|
||||
}
|
||||
|
||||
prevnode.Next = nil
|
||||
|
||||
return orignext, map[string]bool{"json": true}, nil
|
||||
}
|
||||
|
||||
// parseMaybeJSON determines if the argument appears to be a JSON array. If
|
||||
// so, passes to parseJSON; if not, quotes the result and returns a single
|
||||
// node.
|
||||
func parseMaybeJSON(rest string) (*Node, map[string]bool, error) {
|
||||
rest = strings.TrimSpace(rest)
|
||||
|
||||
node, attrs, err := parseJSON(rest)
|
||||
|
||||
if err == nil {
|
||||
return node, attrs, nil
|
||||
}
|
||||
if err == errDockerfileJSONNesting {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
node = &Node{}
|
||||
node.Value = rest
|
||||
return node, nil, nil
|
||||
}
|
||||
|
||||
// parseMaybeJSONToList determines if the argument appears to be a JSON array. If
|
||||
// so, passes to parseJSON; if not, attmpts to parse it as a whitespace
|
||||
// delimited string.
|
||||
func parseMaybeJSONToList(rest string) (*Node, map[string]bool, error) {
|
||||
rest = strings.TrimSpace(rest)
|
||||
|
||||
node, attrs, err := parseJSON(rest)
|
||||
|
||||
if err == nil {
|
||||
return node, attrs, nil
|
||||
}
|
||||
if err == errDockerfileJSONNesting {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return parseStringsWhitespaceDelimited(rest)
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
// This package implements a parser and parse tree dumper for Dockerfiles.
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Node is a structure used to represent a parse tree.
|
||||
//
|
||||
// In the node there are three fields, Value, Next, and Children. Value is the
|
||||
// current token's string value. Next is always the next non-child token, and
|
||||
// children contains all the children. Here's an example:
|
||||
//
|
||||
// (value next (child child-next child-next-next) next-next)
|
||||
//
|
||||
// This data structure is frankly pretty lousy for handling complex languages,
|
||||
// but lucky for us the Dockerfile isn't very complicated. This structure
|
||||
// works a little more effectively than a "proper" parse tree for our needs.
|
||||
//
|
||||
type Node struct {
|
||||
Value string // actual content
|
||||
Next *Node // the next item in the current sexp
|
||||
Children []*Node // the children of this sexp
|
||||
Attributes map[string]bool // special attributes for this node
|
||||
Original string // original line used before parsing
|
||||
}
|
||||
|
||||
var (
|
||||
dispatch map[string]func(string) (*Node, map[string]bool, error)
|
||||
TOKEN_WHITESPACE = regexp.MustCompile(`[\t\v\f\r ]+`)
|
||||
TOKEN_LINE_CONTINUATION = regexp.MustCompile(`\\\s*$`)
|
||||
TOKEN_COMMENT = regexp.MustCompile(`^#.*$`)
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Dispatch Table. see line_parsers.go for the parse functions.
|
||||
// The command is parsed and mapped to the line parser. The line parser
|
||||
// recieves the arguments but not the command, and returns an AST after
|
||||
// reformulating the arguments according to the rules in the parser
|
||||
// functions. Errors are propogated up by Parse() and the resulting AST can
|
||||
// be incorporated directly into the existing AST as a next.
|
||||
dispatch = map[string]func(string) (*Node, map[string]bool, error){
|
||||
"user": parseString,
|
||||
"onbuild": parseSubCommand,
|
||||
"workdir": parseString,
|
||||
"env": parseEnv,
|
||||
"maintainer": parseString,
|
||||
"from": parseString,
|
||||
"add": parseStringsWhitespaceDelimited,
|
||||
"copy": parseStringsWhitespaceDelimited,
|
||||
"run": parseMaybeJSON,
|
||||
"cmd": parseMaybeJSON,
|
||||
"entrypoint": parseMaybeJSON,
|
||||
"expose": parseStringsWhitespaceDelimited,
|
||||
"volume": parseMaybeJSONToList,
|
||||
"insert": parseIgnore,
|
||||
}
|
||||
}
|
||||
|
||||
// parse a line and return the remainder.
|
||||
func parseLine(line string) (string, *Node, error) {
|
||||
if line = stripComments(line); line == "" {
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
if TOKEN_LINE_CONTINUATION.MatchString(line) {
|
||||
line = TOKEN_LINE_CONTINUATION.ReplaceAllString(line, "")
|
||||
return line, nil, nil
|
||||
}
|
||||
|
||||
cmd, args, err := splitCommand(line)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
node := &Node{}
|
||||
node.Value = cmd
|
||||
|
||||
sexp, attrs, err := fullDispatch(cmd, args)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if sexp.Value != "" || sexp.Next != nil || sexp.Children != nil {
|
||||
node.Next = sexp
|
||||
}
|
||||
|
||||
node.Attributes = attrs
|
||||
node.Original = line
|
||||
|
||||
return "", node, nil
|
||||
}
|
||||
|
||||
// The main parse routine. Handles an io.ReadWriteCloser and returns the root
|
||||
// of the AST.
|
||||
func Parse(rwc io.Reader) (*Node, error) {
|
||||
root := &Node{}
|
||||
scanner := bufio.NewScanner(rwc)
|
||||
|
||||
for scanner.Scan() {
|
||||
scannedLine := strings.TrimLeftFunc(scanner.Text(), unicode.IsSpace)
|
||||
line, child, err := parseLine(scannedLine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if line != "" && child == nil {
|
||||
for scanner.Scan() {
|
||||
newline := scanner.Text()
|
||||
|
||||
if stripComments(strings.TrimSpace(newline)) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
line, child, err = parseLine(line + newline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if child != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if child == nil && line != "" {
|
||||
line, child, err = parseLine(line)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if child != nil {
|
||||
root.Children = append(root.Children, child)
|
||||
}
|
||||
}
|
||||
|
||||
return root, nil
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const testDir = "testfiles"
|
||||
const negativeTestDir = "testfiles-negative"
|
||||
|
||||
func getDirs(t *testing.T, dir string) []os.FileInfo {
|
||||
f, err := os.Open(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
dirs, err := f.Readdir(0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return dirs
|
||||
}
|
||||
|
||||
func TestTestNegative(t *testing.T) {
|
||||
for _, dir := range getDirs(t, negativeTestDir) {
|
||||
dockerfile := filepath.Join(negativeTestDir, dir.Name(), "Dockerfile")
|
||||
|
||||
df, err := os.Open(dockerfile)
|
||||
if err != nil {
|
||||
t.Fatalf("Dockerfile missing for %s: %s", dir.Name(), err.Error())
|
||||
}
|
||||
|
||||
_, err = Parse(df)
|
||||
if err == nil {
|
||||
t.Fatalf("No error parsing broken dockerfile for %s", dir.Name())
|
||||
}
|
||||
|
||||
df.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestData(t *testing.T) {
|
||||
for _, dir := range getDirs(t, testDir) {
|
||||
dockerfile := filepath.Join(testDir, dir.Name(), "Dockerfile")
|
||||
resultfile := filepath.Join(testDir, dir.Name(), "result")
|
||||
|
||||
df, err := os.Open(dockerfile)
|
||||
if err != nil {
|
||||
t.Fatalf("Dockerfile missing for %s: %s", dir.Name(), err.Error())
|
||||
}
|
||||
|
||||
rf, err := os.Open(resultfile)
|
||||
if err != nil {
|
||||
t.Fatalf("Result file missing for %s: %s", dir.Name(), err.Error())
|
||||
}
|
||||
|
||||
ast, err := Parse(df)
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing %s's dockerfile: %s", dir.Name(), err.Error())
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadAll(rf)
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading %s's result file: %s", dir.Name(), err.Error())
|
||||
}
|
||||
|
||||
if ast.Dump()+"\n" != string(content) {
|
||||
fmt.Fprintln(os.Stderr, "Result:\n"+ast.Dump())
|
||||
fmt.Fprintln(os.Stderr, "Expected:\n"+string(content))
|
||||
t.Fatalf("%s: AST dump of dockerfile does not match result", dir.Name())
|
||||
}
|
||||
|
||||
df.Close()
|
||||
rf.Close()
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM busybox
|
||||
|
||||
ENV PATH
|
||||
@@ -1,2 +0,0 @@
|
||||
<html>
|
||||
</html>
|
||||
@@ -1 +0,0 @@
|
||||
CMD [ "echo", [ "nested json" ] ]
|
||||
@@ -1,25 +0,0 @@
|
||||
FROM brimstone/ubuntu:14.04
|
||||
|
||||
MAINTAINER brimstone@the.narro.ws
|
||||
|
||||
# TORUN -v /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
ENV GOPATH /go
|
||||
|
||||
# Set our command
|
||||
ENTRYPOINT ["/usr/local/bin/consuldock"]
|
||||
|
||||
# Install the packages we need, clean up after them and us
|
||||
RUN apt-get update \
|
||||
&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.clean \
|
||||
&& apt-get install -y --no-install-recommends git golang ca-certificates \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists \
|
||||
|
||||
&& go get -v github.com/brimstone/consuldock \
|
||||
&& mv $GOPATH/bin/consuldock /usr/local/bin/consuldock \
|
||||
|
||||
&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.dirty \
|
||||
&& apt-get remove --purge -y $(diff /tmp/dpkg.clean /tmp/dpkg.dirty | awk '/^>/ {print $2}') \
|
||||
&& rm /tmp/dpkg.* \
|
||||
&& rm -rf $GOPATH
|
||||
@@ -1,5 +0,0 @@
|
||||
(from "brimstone/ubuntu:14.04")
|
||||
(maintainer "brimstone@the.narro.ws")
|
||||
(env "GOPATH" "/go")
|
||||
(entrypoint "/usr/local/bin/consuldock")
|
||||
(run "apt-get update && dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.clean && apt-get install -y --no-install-recommends git golang ca-certificates && apt-get clean && rm -rf /var/lib/apt/lists && go get -v github.com/brimstone/consuldock && mv $GOPATH/bin/consuldock /usr/local/bin/consuldock && dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.dirty && apt-get remove --purge -y $(diff /tmp/dpkg.clean /tmp/dpkg.dirty | awk '/^>/ {print $2}') && rm /tmp/dpkg.* && rm -rf $GOPATH")
|
||||
@@ -1,52 +0,0 @@
|
||||
FROM brimstone/ubuntu:14.04
|
||||
|
||||
CMD []
|
||||
|
||||
ENTRYPOINT ["/usr/bin/consul", "agent", "-server", "-data-dir=/consul", "-client=0.0.0.0", "-ui-dir=/webui"]
|
||||
|
||||
EXPOSE 8500 8600 8400 8301 8302
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y unzip wget \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists
|
||||
|
||||
RUN cd /tmp \
|
||||
&& wget https://dl.bintray.com/mitchellh/consul/0.3.1_web_ui.zip \
|
||||
-O web_ui.zip \
|
||||
&& unzip web_ui.zip \
|
||||
&& mv dist /webui \
|
||||
&& rm web_ui.zip
|
||||
|
||||
RUN apt-get update \
|
||||
&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.clean \
|
||||
&& apt-get install -y --no-install-recommends unzip wget \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists \
|
||||
|
||||
&& cd /tmp \
|
||||
&& wget https://dl.bintray.com/mitchellh/consul/0.3.1_web_ui.zip \
|
||||
-O web_ui.zip \
|
||||
&& unzip web_ui.zip \
|
||||
&& mv dist /webui \
|
||||
&& rm web_ui.zip \
|
||||
|
||||
&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.dirty \
|
||||
&& apt-get remove --purge -y $(diff /tmp/dpkg.clean /tmp/dpkg.dirty | awk '/^>/ {print $2}') \
|
||||
&& rm /tmp/dpkg.*
|
||||
|
||||
ENV GOPATH /go
|
||||
|
||||
RUN apt-get update \
|
||||
&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.clean \
|
||||
&& apt-get install -y --no-install-recommends git golang ca-certificates build-essential \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists \
|
||||
|
||||
&& go get -v github.com/hashicorp/consul \
|
||||
&& mv $GOPATH/bin/consul /usr/bin/consul \
|
||||
|
||||
&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.dirty \
|
||||
&& apt-get remove --purge -y $(diff /tmp/dpkg.clean /tmp/dpkg.dirty | awk '/^>/ {print $2}') \
|
||||
&& rm /tmp/dpkg.* \
|
||||
&& rm -rf $GOPATH
|
||||
@@ -1,9 +0,0 @@
|
||||
(from "brimstone/ubuntu:14.04")
|
||||
(cmd)
|
||||
(entrypoint "/usr/bin/consul" "agent" "-server" "-data-dir=/consul" "-client=0.0.0.0" "-ui-dir=/webui")
|
||||
(expose "8500" "8600" "8400" "8301" "8302")
|
||||
(run "apt-get update && apt-get install -y unzip wget && apt-get clean && rm -rf /var/lib/apt/lists")
|
||||
(run "cd /tmp && wget https://dl.bintray.com/mitchellh/consul/0.3.1_web_ui.zip -O web_ui.zip && unzip web_ui.zip && mv dist /webui && rm web_ui.zip")
|
||||
(run "apt-get update && dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.clean && apt-get install -y --no-install-recommends unzip wget && apt-get clean && rm -rf /var/lib/apt/lists && cd /tmp && wget https://dl.bintray.com/mitchellh/consul/0.3.1_web_ui.zip -O web_ui.zip && unzip web_ui.zip && mv dist /webui && rm web_ui.zip && dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.dirty && apt-get remove --purge -y $(diff /tmp/dpkg.clean /tmp/dpkg.dirty | awk '/^>/ {print $2}') && rm /tmp/dpkg.*")
|
||||
(env "GOPATH" "/go")
|
||||
(run "apt-get update && dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.clean && apt-get install -y --no-install-recommends git golang ca-certificates build-essential && apt-get clean && rm -rf /var/lib/apt/lists && go get -v github.com/hashicorp/consul && mv $GOPATH/bin/consul /usr/bin/consul && dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.dirty && apt-get remove --purge -y $(diff /tmp/dpkg.clean /tmp/dpkg.dirty | awk '/^>/ {print $2}') && rm /tmp/dpkg.* && rm -rf $GOPATH")
|
||||
@@ -1,36 +0,0 @@
|
||||
FROM ubuntu:14.04
|
||||
|
||||
RUN echo hello\
|
||||
world\
|
||||
goodnight \
|
||||
moon\
|
||||
light\
|
||||
ning
|
||||
RUN echo hello \
|
||||
world
|
||||
RUN echo hello \
|
||||
world
|
||||
RUN echo hello \
|
||||
goodbye\
|
||||
frog
|
||||
RUN echo hello \
|
||||
world
|
||||
RUN echo hi \
|
||||
\
|
||||
world \
|
||||
\
|
||||
good\
|
||||
\
|
||||
night
|
||||
RUN echo goodbye\
|
||||
frog
|
||||
RUN echo good\
|
||||
bye\
|
||||
frog
|
||||
|
||||
RUN echo hello \
|
||||
# this is a comment
|
||||
|
||||
# this is a comment with a blank line surrounding it
|
||||
|
||||
this is some more useful stuff
|
||||
@@ -1,10 +0,0 @@
|
||||
(from "ubuntu:14.04")
|
||||
(run "echo hello world goodnight moon lightning")
|
||||
(run "echo hello world")
|
||||
(run "echo hello world")
|
||||
(run "echo hello goodbyefrog")
|
||||
(run "echo hello world")
|
||||
(run "echo hi world goodnight")
|
||||
(run "echo goodbyefrog")
|
||||
(run "echo goodbyefrog")
|
||||
(run "echo hello this is some more useful stuff")
|
||||
@@ -1,54 +0,0 @@
|
||||
FROM cpuguy83/ubuntu
|
||||
ENV NAGIOS_HOME /opt/nagios
|
||||
ENV NAGIOS_USER nagios
|
||||
ENV NAGIOS_GROUP nagios
|
||||
ENV NAGIOS_CMDUSER nagios
|
||||
ENV NAGIOS_CMDGROUP nagios
|
||||
ENV NAGIOSADMIN_USER nagiosadmin
|
||||
ENV NAGIOSADMIN_PASS nagios
|
||||
ENV APACHE_RUN_USER nagios
|
||||
ENV APACHE_RUN_GROUP nagios
|
||||
ENV NAGIOS_TIMEZONE UTC
|
||||
|
||||
RUN sed -i 's/universe/universe multiverse/' /etc/apt/sources.list
|
||||
RUN apt-get update && apt-get install -y iputils-ping netcat build-essential snmp snmpd snmp-mibs-downloader php5-cli apache2 libapache2-mod-php5 runit bc postfix bsd-mailx
|
||||
RUN ( egrep -i "^${NAGIOS_GROUP}" /etc/group || groupadd $NAGIOS_GROUP ) && ( egrep -i "^${NAGIOS_CMDGROUP}" /etc/group || groupadd $NAGIOS_CMDGROUP )
|
||||
RUN ( id -u $NAGIOS_USER || useradd --system $NAGIOS_USER -g $NAGIOS_GROUP -d $NAGIOS_HOME ) && ( id -u $NAGIOS_CMDUSER || useradd --system -d $NAGIOS_HOME -g $NAGIOS_CMDGROUP $NAGIOS_CMDUSER )
|
||||
|
||||
ADD http://downloads.sourceforge.net/project/nagios/nagios-3.x/nagios-3.5.1/nagios-3.5.1.tar.gz?r=http%3A%2F%2Fwww.nagios.org%2Fdownload%2Fcore%2Fthanks%2F%3Ft%3D1398863696&ts=1398863718&use_mirror=superb-dca3 /tmp/nagios.tar.gz
|
||||
RUN cd /tmp && tar -zxvf nagios.tar.gz && cd nagios && ./configure --prefix=${NAGIOS_HOME} --exec-prefix=${NAGIOS_HOME} --enable-event-broker --with-nagios-command-user=${NAGIOS_CMDUSER} --with-command-group=${NAGIOS_CMDGROUP} --with-nagios-user=${NAGIOS_USER} --with-nagios-group=${NAGIOS_GROUP} && make all && make install && make install-config && make install-commandmode && cp sample-config/httpd.conf /etc/apache2/conf.d/nagios.conf
|
||||
ADD http://www.nagios-plugins.org/download/nagios-plugins-1.5.tar.gz /tmp/
|
||||
RUN cd /tmp && tar -zxvf nagios-plugins-1.5.tar.gz && cd nagios-plugins-1.5 && ./configure --prefix=${NAGIOS_HOME} && make && make install
|
||||
|
||||
RUN sed -i.bak 's/.*\=www\-data//g' /etc/apache2/envvars
|
||||
RUN export DOC_ROOT="DocumentRoot $(echo $NAGIOS_HOME/share)"; sed -i "s,DocumentRoot.*,$DOC_ROOT," /etc/apache2/sites-enabled/000-default
|
||||
|
||||
RUN ln -s ${NAGIOS_HOME}/bin/nagios /usr/local/bin/nagios && mkdir -p /usr/share/snmp/mibs && chmod 0755 /usr/share/snmp/mibs && touch /usr/share/snmp/mibs/.foo
|
||||
|
||||
RUN echo "use_timezone=$NAGIOS_TIMEZONE" >> ${NAGIOS_HOME}/etc/nagios.cfg && echo "SetEnv TZ \"${NAGIOS_TIMEZONE}\"" >> /etc/apache2/conf.d/nagios.conf
|
||||
|
||||
RUN mkdir -p ${NAGIOS_HOME}/etc/conf.d && mkdir -p ${NAGIOS_HOME}/etc/monitor && ln -s /usr/share/snmp/mibs ${NAGIOS_HOME}/libexec/mibs
|
||||
RUN echo "cfg_dir=${NAGIOS_HOME}/etc/conf.d" >> ${NAGIOS_HOME}/etc/nagios.cfg
|
||||
RUN echo "cfg_dir=${NAGIOS_HOME}/etc/monitor" >> ${NAGIOS_HOME}/etc/nagios.cfg
|
||||
RUN download-mibs && echo "mibs +ALL" > /etc/snmp/snmp.conf
|
||||
|
||||
RUN sed -i 's,/bin/mail,/usr/bin/mail,' /opt/nagios/etc/objects/commands.cfg && \
|
||||
sed -i 's,/usr/usr,/usr,' /opt/nagios/etc/objects/commands.cfg
|
||||
RUN cp /etc/services /var/spool/postfix/etc/
|
||||
|
||||
RUN mkdir -p /etc/sv/nagios && mkdir -p /etc/sv/apache && rm -rf /etc/sv/getty-5 && mkdir -p /etc/sv/postfix
|
||||
ADD nagios.init /etc/sv/nagios/run
|
||||
ADD apache.init /etc/sv/apache/run
|
||||
ADD postfix.init /etc/sv/postfix/run
|
||||
ADD postfix.stop /etc/sv/postfix/finish
|
||||
|
||||
ADD start.sh /usr/local/bin/start_nagios
|
||||
|
||||
ENV APACHE_LOCK_DIR /var/run
|
||||
ENV APACHE_LOG_DIR /var/log/apache2
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
VOLUME ["/opt/nagios/var", "/opt/nagios/etc", "/opt/nagios/libexec", "/var/log/apache2", "/usr/share/snmp/mibs"]
|
||||
|
||||
CMD ["/usr/local/bin/start_nagios"]
|
||||
@@ -1,40 +0,0 @@
|
||||
(from "cpuguy83/ubuntu")
|
||||
(env "NAGIOS_HOME" "/opt/nagios")
|
||||
(env "NAGIOS_USER" "nagios")
|
||||
(env "NAGIOS_GROUP" "nagios")
|
||||
(env "NAGIOS_CMDUSER" "nagios")
|
||||
(env "NAGIOS_CMDGROUP" "nagios")
|
||||
(env "NAGIOSADMIN_USER" "nagiosadmin")
|
||||
(env "NAGIOSADMIN_PASS" "nagios")
|
||||
(env "APACHE_RUN_USER" "nagios")
|
||||
(env "APACHE_RUN_GROUP" "nagios")
|
||||
(env "NAGIOS_TIMEZONE" "UTC")
|
||||
(run "sed -i 's/universe/universe multiverse/' /etc/apt/sources.list")
|
||||
(run "apt-get update && apt-get install -y iputils-ping netcat build-essential snmp snmpd snmp-mibs-downloader php5-cli apache2 libapache2-mod-php5 runit bc postfix bsd-mailx")
|
||||
(run "( egrep -i \"^${NAGIOS_GROUP}\" /etc/group || groupadd $NAGIOS_GROUP ) && ( egrep -i \"^${NAGIOS_CMDGROUP}\" /etc/group || groupadd $NAGIOS_CMDGROUP )")
|
||||
(run "( id -u $NAGIOS_USER || useradd --system $NAGIOS_USER -g $NAGIOS_GROUP -d $NAGIOS_HOME ) && ( id -u $NAGIOS_CMDUSER || useradd --system -d $NAGIOS_HOME -g $NAGIOS_CMDGROUP $NAGIOS_CMDUSER )")
|
||||
(add "http://downloads.sourceforge.net/project/nagios/nagios-3.x/nagios-3.5.1/nagios-3.5.1.tar.gz?r=http%3A%2F%2Fwww.nagios.org%2Fdownload%2Fcore%2Fthanks%2F%3Ft%3D1398863696&ts=1398863718&use_mirror=superb-dca3" "/tmp/nagios.tar.gz")
|
||||
(run "cd /tmp && tar -zxvf nagios.tar.gz && cd nagios && ./configure --prefix=${NAGIOS_HOME} --exec-prefix=${NAGIOS_HOME} --enable-event-broker --with-nagios-command-user=${NAGIOS_CMDUSER} --with-command-group=${NAGIOS_CMDGROUP} --with-nagios-user=${NAGIOS_USER} --with-nagios-group=${NAGIOS_GROUP} && make all && make install && make install-config && make install-commandmode && cp sample-config/httpd.conf /etc/apache2/conf.d/nagios.conf")
|
||||
(add "http://www.nagios-plugins.org/download/nagios-plugins-1.5.tar.gz" "/tmp/")
|
||||
(run "cd /tmp && tar -zxvf nagios-plugins-1.5.tar.gz && cd nagios-plugins-1.5 && ./configure --prefix=${NAGIOS_HOME} && make && make install")
|
||||
(run "sed -i.bak 's/.*\\=www\\-data//g' /etc/apache2/envvars")
|
||||
(run "export DOC_ROOT=\"DocumentRoot $(echo $NAGIOS_HOME/share)\"; sed -i \"s,DocumentRoot.*,$DOC_ROOT,\" /etc/apache2/sites-enabled/000-default")
|
||||
(run "ln -s ${NAGIOS_HOME}/bin/nagios /usr/local/bin/nagios && mkdir -p /usr/share/snmp/mibs && chmod 0755 /usr/share/snmp/mibs && touch /usr/share/snmp/mibs/.foo")
|
||||
(run "echo \"use_timezone=$NAGIOS_TIMEZONE\" >> ${NAGIOS_HOME}/etc/nagios.cfg && echo \"SetEnv TZ \\\"${NAGIOS_TIMEZONE}\\\"\" >> /etc/apache2/conf.d/nagios.conf")
|
||||
(run "mkdir -p ${NAGIOS_HOME}/etc/conf.d && mkdir -p ${NAGIOS_HOME}/etc/monitor && ln -s /usr/share/snmp/mibs ${NAGIOS_HOME}/libexec/mibs")
|
||||
(run "echo \"cfg_dir=${NAGIOS_HOME}/etc/conf.d\" >> ${NAGIOS_HOME}/etc/nagios.cfg")
|
||||
(run "echo \"cfg_dir=${NAGIOS_HOME}/etc/monitor\" >> ${NAGIOS_HOME}/etc/nagios.cfg")
|
||||
(run "download-mibs && echo \"mibs +ALL\" > /etc/snmp/snmp.conf")
|
||||
(run "sed -i 's,/bin/mail,/usr/bin/mail,' /opt/nagios/etc/objects/commands.cfg && sed -i 's,/usr/usr,/usr,' /opt/nagios/etc/objects/commands.cfg")
|
||||
(run "cp /etc/services /var/spool/postfix/etc/")
|
||||
(run "mkdir -p /etc/sv/nagios && mkdir -p /etc/sv/apache && rm -rf /etc/sv/getty-5 && mkdir -p /etc/sv/postfix")
|
||||
(add "nagios.init" "/etc/sv/nagios/run")
|
||||
(add "apache.init" "/etc/sv/apache/run")
|
||||
(add "postfix.init" "/etc/sv/postfix/run")
|
||||
(add "postfix.stop" "/etc/sv/postfix/finish")
|
||||
(add "start.sh" "/usr/local/bin/start_nagios")
|
||||
(env "APACHE_LOCK_DIR" "/var/run")
|
||||
(env "APACHE_LOG_DIR" "/var/log/apache2")
|
||||
(expose "80")
|
||||
(volume "/opt/nagios/var" "/opt/nagios/etc" "/opt/nagios/libexec" "/var/log/apache2" "/usr/share/snmp/mibs")
|
||||
(cmd "/usr/local/bin/start_nagios")
|
||||
@@ -1,104 +0,0 @@
|
||||
# This file describes the standard way to build Docker, using docker
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# # Assemble the full dev environment. This is slow the first time.
|
||||
# docker build -t docker .
|
||||
#
|
||||
# # Mount your source in an interactive container for quick testing:
|
||||
# docker run -v `pwd`:/go/src/github.com/docker/docker --privileged -i -t docker bash
|
||||
#
|
||||
# # Run the test suite:
|
||||
# docker run --privileged docker hack/make.sh test
|
||||
#
|
||||
# # Publish a release:
|
||||
# docker run --privileged \
|
||||
# -e AWS_S3_BUCKET=baz \
|
||||
# -e AWS_ACCESS_KEY=foo \
|
||||
# -e AWS_SECRET_KEY=bar \
|
||||
# -e GPG_PASSPHRASE=gloubiboulga \
|
||||
# docker hack/release.sh
|
||||
#
|
||||
# Note: Apparmor used to mess with privileged mode, but this is no longer
|
||||
# the case. Therefore, you don't have to disable it anymore.
|
||||
#
|
||||
|
||||
FROM ubuntu:14.04
|
||||
MAINTAINER Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
|
||||
# Packaged dependencies
|
||||
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
|
||||
|
||||
# Get lvm2 source for compiling statically
|
||||
RUN git clone --no-checkout https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout -q v2_02_103
|
||||
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
|
||||
# note: we don't use "git clone -b" above because it then spews big nasty warnings about 'detached HEAD' state that we can't silence as easily as we can silence them using "git checkout" directly
|
||||
|
||||
# Compile and install lvm2
|
||||
RUN cd /usr/local/lvm2 && ./configure --enable-static_link && make device-mapper && make install_device-mapper
|
||||
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
|
||||
|
||||
# Install Go
|
||||
RUN curl -sSL https://golang.org/dl/go1.3.src.tar.gz | tar -v -C /usr/local -xz
|
||||
ENV PATH /usr/local/go/bin:$PATH
|
||||
ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
|
||||
RUN cd /usr/local/go/src && ./make.bash --no-clean 2>&1
|
||||
|
||||
# Compile Go for cross compilation
|
||||
ENV DOCKER_CROSSPLATFORMS \
|
||||
linux/386 linux/arm \
|
||||
darwin/amd64 darwin/386 \
|
||||
freebsd/amd64 freebsd/386 freebsd/arm
|
||||
# (set an explicit GOARM of 5 for maximum compatibility)
|
||||
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'
|
||||
|
||||
# Grab Go's cover tool for dead-simple code coverage testing
|
||||
RUN go get golang.org/x/tools/cmd/cover
|
||||
|
||||
# TODO replace FPM with some very minimal debhelper stuff
|
||||
RUN gem install --no-rdoc --no-ri fpm --version 1.0.2
|
||||
|
||||
# Get the "busybox" image source so we can build locally instead of pulling
|
||||
RUN git clone -b buildroot-2014.02 https://github.com/jpetazzo/docker-busybox.git /docker-busybox
|
||||
|
||||
# Setup s3cmd config
|
||||
RUN /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY' > /.s3cfg
|
||||
|
||||
# Set user.email so crosbymichael's in-container merge commits go smoothly
|
||||
RUN git config --global user.email 'docker-dummy@example.com'
|
||||
|
||||
# Add an unprivileged user to be used for tests which need it
|
||||
RUN groupadd -r docker
|
||||
RUN useradd --create-home --gid docker unprivilegeduser
|
||||
|
||||
VOLUME /var/lib/docker
|
||||
WORKDIR /go/src/github.com/docker/docker
|
||||
ENV DOCKER_BUILDTAGS apparmor selinux
|
||||
|
||||
# Wrap all commands in the "docker-in-docker" script to allow nested containers
|
||||
ENTRYPOINT ["hack/dind"]
|
||||
|
||||
# Upload docker source
|
||||
COPY . /go/src/github.com/docker/docker
|
||||
@@ -1,24 +0,0 @@
|
||||
(from "ubuntu:14.04")
|
||||
(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 "git clone --no-checkout https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout -q v2_02_103")
|
||||
(run "cd /usr/local/lvm2 && ./configure --enable-static_link && make device-mapper && make install_device-mapper")
|
||||
(run "curl -sSL https://golang.org/dl/go1.3.src.tar.gz | tar -v -C /usr/local -xz")
|
||||
(env "PATH" "/usr/local/go/bin:$PATH")
|
||||
(env "GOPATH" "/go:/go/src/github.com/docker/docker/vendor")
|
||||
(run "cd /usr/local/go/src && ./make.bash --no-clean 2>&1")
|
||||
(env "DOCKER_CROSSPLATFORMS" "linux/386 linux/arm darwin/amd64 darwin/386 freebsd/amd64 freebsd/386 freebsd/arm")
|
||||
(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 "go get golang.org/x/tools/cmd/cover")
|
||||
(run "gem install --no-rdoc --no-ri fpm --version 1.0.2")
|
||||
(run "git clone -b buildroot-2014.02 https://github.com/jpetazzo/docker-busybox.git /docker-busybox")
|
||||
(run "/bin/echo -e '[default]\\naccess_key=$AWS_ACCESS_KEY\\nsecret_key=$AWS_SECRET_KEY' > /.s3cfg")
|
||||
(run "git config --global user.email 'docker-dummy@example.com'")
|
||||
(run "groupadd -r docker")
|
||||
(run "useradd --create-home --gid docker unprivilegeduser")
|
||||
(volume "/var/lib/docker")
|
||||
(workdir "/go/src/github.com/docker/docker")
|
||||
(env "DOCKER_BUILDTAGS" "apparmor selinux")
|
||||
(entrypoint "hack/dind")
|
||||
(copy "." "/go/src/github.com/docker/docker")
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
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" \
|
||||
Vendored
-10
@@ -1,10 +0,0 @@
|
||||
(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")
|
||||
@@ -1,14 +0,0 @@
|
||||
FROM ubuntu:14.04
|
||||
MAINTAINER Erik \\Hollensbe <erik@hollensbe.org>\"
|
||||
|
||||
RUN apt-get \update && \
|
||||
apt-get \"install znc -y
|
||||
ADD \conf\\" /.znc
|
||||
|
||||
RUN foo \
|
||||
|
||||
bar \
|
||||
|
||||
baz
|
||||
|
||||
CMD [ "\/usr\\\"/bin/znc", "-f", "-r" ]
|
||||
@@ -1,6 +0,0 @@
|
||||
(from "ubuntu:14.04")
|
||||
(maintainer "Erik \\\\Hollensbe <erik@hollensbe.org>\\\"")
|
||||
(run "apt-get \\update && apt-get \\\"install znc -y")
|
||||
(add "\\conf\\\\\"" "/.znc")
|
||||
(run "foo bar baz")
|
||||
(cmd "/usr\\\"/bin/znc" "-f" "-r")
|
||||
@@ -1,15 +0,0 @@
|
||||
FROM ubuntu:14.04
|
||||
|
||||
RUN apt-get update && apt-get install wget -y
|
||||
RUN wget http://s3.amazonaws.com/influxdb/influxdb_latest_amd64.deb
|
||||
RUN dpkg -i influxdb_latest_amd64.deb
|
||||
RUN rm -r /opt/influxdb/shared
|
||||
|
||||
VOLUME /opt/influxdb/shared
|
||||
|
||||
CMD /usr/bin/influxdb --pidfile /var/run/influxdb.pid -config /opt/influxdb/shared/config.toml
|
||||
|
||||
EXPOSE 8083
|
||||
EXPOSE 8086
|
||||
EXPOSE 8090
|
||||
EXPOSE 8099
|
||||
@@ -1,11 +0,0 @@
|
||||
(from "ubuntu:14.04")
|
||||
(run "apt-get update && apt-get install wget -y")
|
||||
(run "wget http://s3.amazonaws.com/influxdb/influxdb_latest_amd64.deb")
|
||||
(run "dpkg -i influxdb_latest_amd64.deb")
|
||||
(run "rm -r /opt/influxdb/shared")
|
||||
(volume "/opt/influxdb/shared")
|
||||
(cmd "/usr/bin/influxdb --pidfile /var/run/influxdb.pid -config /opt/influxdb/shared/config.toml")
|
||||
(expose "8083")
|
||||
(expose "8086")
|
||||
(expose "8090")
|
||||
(expose "8099")
|
||||
@@ -1 +0,0 @@
|
||||
CMD "[\"echo\", \"Phew, I just managed to escaped those double quotes\"]"
|
||||
@@ -1 +0,0 @@
|
||||
(cmd "\"[\\\"echo\\\", \\\"Phew, I just managed to escaped those double quotes\\\"]\"")
|
||||
@@ -1 +0,0 @@
|
||||
CMD '["echo", "Well, JSON in a string is JSON too?"]'
|
||||
@@ -1 +0,0 @@
|
||||
(cmd "'[\"echo\", \"Well, JSON in a string is JSON too?\"]'")
|
||||
@@ -1 +0,0 @@
|
||||
CMD ['echo','single quotes are invalid JSON']
|
||||
@@ -1 +0,0 @@
|
||||
(cmd "['echo','single quotes are invalid JSON']")
|
||||
@@ -1 +0,0 @@
|
||||
CMD ["echo", "Please, close the brackets when you're done"
|
||||
@@ -1 +0,0 @@
|
||||
(cmd "[\"echo\", \"Please, close the brackets when you're done\"")
|
||||
@@ -1 +0,0 @@
|
||||
CMD ["echo", "look ma, no quote!]
|
||||
@@ -1 +0,0 @@
|
||||
(cmd "[\"echo\", \"look ma, no quote!]")
|
||||
@@ -1,7 +0,0 @@
|
||||
FROM ubuntu:14.04
|
||||
MAINTAINER James Turnbull "james@example.com"
|
||||
ENV REFRESHED_AT 2014-06-01
|
||||
RUN apt-get update
|
||||
RUN apt-get -y install redis-server redis-tools
|
||||
EXPOSE 6379
|
||||
ENTRYPOINT [ "/usr/bin/redis-server" ]
|
||||
@@ -1,7 +0,0 @@
|
||||
(from "ubuntu:14.04")
|
||||
(maintainer "James Turnbull \"james@example.com\"")
|
||||
(env "REFRESHED_AT" "2014-06-01")
|
||||
(run "apt-get update")
|
||||
(run "apt-get -y install redis-server redis-tools")
|
||||
(expose "6379")
|
||||
(entrypoint "/usr/bin/redis-server")
|
||||
@@ -1,48 +0,0 @@
|
||||
FROM busybox:buildroot-2014.02
|
||||
|
||||
MAINTAINER docker <docker@docker.io>
|
||||
|
||||
ONBUILD RUN ["echo", "test"]
|
||||
ONBUILD RUN echo test
|
||||
ONBUILD COPY . /
|
||||
|
||||
|
||||
# RUN Commands \
|
||||
# linebreak in comment \
|
||||
RUN ["ls", "-la"]
|
||||
RUN ["echo", "'1234'"]
|
||||
RUN echo "1234"
|
||||
RUN echo 1234
|
||||
RUN echo '1234' && \
|
||||
echo "456" && \
|
||||
echo 789
|
||||
RUN sh -c 'echo root:testpass \
|
||||
> /tmp/passwd'
|
||||
RUN mkdir -p /test /test2 /test3/test
|
||||
|
||||
# ENV \
|
||||
ENV SCUBA 1 DUBA 3
|
||||
ENV SCUBA "1 DUBA 3"
|
||||
|
||||
# CMD \
|
||||
CMD ["echo", "test"]
|
||||
CMD echo test
|
||||
CMD echo "test"
|
||||
CMD echo 'test'
|
||||
CMD echo 'test' | wc -
|
||||
|
||||
#EXPOSE\
|
||||
EXPOSE 3000
|
||||
EXPOSE 9000 5000 6000
|
||||
|
||||
USER docker
|
||||
USER docker:root
|
||||
|
||||
VOLUME ["/test"]
|
||||
VOLUME ["/test", "/test2"]
|
||||
VOLUME /test3
|
||||
|
||||
WORKDIR /test
|
||||
|
||||
ADD . /
|
||||
COPY . copy
|
||||
@@ -1,29 +0,0 @@
|
||||
(from "busybox:buildroot-2014.02")
|
||||
(maintainer "docker <docker@docker.io>")
|
||||
(onbuild (run "echo" "test"))
|
||||
(onbuild (run "echo test"))
|
||||
(onbuild (copy "." "/"))
|
||||
(run "ls" "-la")
|
||||
(run "echo" "'1234'")
|
||||
(run "echo \"1234\"")
|
||||
(run "echo 1234")
|
||||
(run "echo '1234' && echo \"456\" && echo 789")
|
||||
(run "sh -c 'echo root:testpass > /tmp/passwd'")
|
||||
(run "mkdir -p /test /test2 /test3/test")
|
||||
(env "SCUBA" "1 DUBA 3")
|
||||
(env "SCUBA" "\"1 DUBA 3\"")
|
||||
(cmd "echo" "test")
|
||||
(cmd "echo test")
|
||||
(cmd "echo \"test\"")
|
||||
(cmd "echo 'test'")
|
||||
(cmd "echo 'test' | wc -")
|
||||
(expose "3000")
|
||||
(expose "9000" "5000" "6000")
|
||||
(user "docker")
|
||||
(user "docker:root")
|
||||
(volume "/test")
|
||||
(volume "/test" "/test2")
|
||||
(volume "/test3")
|
||||
(workdir "/test")
|
||||
(add "." "/")
|
||||
(copy "." "copy")
|
||||
@@ -1,16 +0,0 @@
|
||||
FROM ubuntu:14.04
|
||||
|
||||
RUN apt-get update -qy && apt-get install mutt offlineimap vim-nox abook elinks curl tmux cron zsh -y
|
||||
ADD .muttrc /
|
||||
ADD .offlineimaprc /
|
||||
ADD .tmux.conf /
|
||||
ADD mutt /.mutt
|
||||
ADD vim /.vim
|
||||
ADD vimrc /.vimrc
|
||||
ADD crontab /etc/crontab
|
||||
RUN chmod 644 /etc/crontab
|
||||
RUN mkdir /Mail
|
||||
RUN mkdir /.offlineimap
|
||||
RUN echo "export TERM=screen-256color" >/.zshenv
|
||||
|
||||
CMD setsid cron; tmux -2
|
||||
@@ -1,14 +0,0 @@
|
||||
(from "ubuntu:14.04")
|
||||
(run "apt-get update -qy && apt-get install mutt offlineimap vim-nox abook elinks curl tmux cron zsh -y")
|
||||
(add ".muttrc" "/")
|
||||
(add ".offlineimaprc" "/")
|
||||
(add ".tmux.conf" "/")
|
||||
(add "mutt" "/.mutt")
|
||||
(add "vim" "/.vim")
|
||||
(add "vimrc" "/.vimrc")
|
||||
(add "crontab" "/etc/crontab")
|
||||
(run "chmod 644 /etc/crontab")
|
||||
(run "mkdir /Mail")
|
||||
(run "mkdir /.offlineimap")
|
||||
(run "echo \"export TERM=screen-256color\" >/.zshenv")
|
||||
(cmd "setsid cron; tmux -2")
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM foo
|
||||
|
||||
VOLUME /opt/nagios/var /opt/nagios/etc /opt/nagios/libexec /var/log/apache2 /usr/share/snmp/mibs
|
||||
@@ -1,2 +0,0 @@
|
||||
(from "foo")
|
||||
(volume "/opt/nagios/var" "/opt/nagios/etc" "/opt/nagios/libexec" "/var/log/apache2" "/usr/share/snmp/mibs")
|
||||
@@ -1,7 +0,0 @@
|
||||
FROM ubuntu:14.04
|
||||
|
||||
RUN apt-get update && apt-get install libcap2-bin mumble-server -y
|
||||
|
||||
ADD ./mumble-server.ini /etc/mumble-server.ini
|
||||
|
||||
CMD /usr/sbin/murmurd
|
||||
@@ -1,4 +0,0 @@
|
||||
(from "ubuntu:14.04")
|
||||
(run "apt-get update && apt-get install libcap2-bin mumble-server -y")
|
||||
(add "./mumble-server.ini" "/etc/mumble-server.ini")
|
||||
(cmd "/usr/sbin/murmurd")
|
||||
@@ -1,14 +0,0 @@
|
||||
FROM ubuntu:14.04
|
||||
MAINTAINER Erik Hollensbe <erik@hollensbe.org>
|
||||
|
||||
RUN apt-get update && apt-get install nginx-full -y
|
||||
RUN rm -rf /etc/nginx
|
||||
ADD etc /etc/nginx
|
||||
RUN chown -R root:root /etc/nginx
|
||||
RUN /usr/sbin/nginx -qt
|
||||
RUN mkdir /www
|
||||
|
||||
CMD ["/usr/sbin/nginx"]
|
||||
|
||||
VOLUME /www
|
||||
EXPOSE 80
|
||||
@@ -1,11 +0,0 @@
|
||||
(from "ubuntu:14.04")
|
||||
(maintainer "Erik Hollensbe <erik@hollensbe.org>")
|
||||
(run "apt-get update && apt-get install nginx-full -y")
|
||||
(run "rm -rf /etc/nginx")
|
||||
(add "etc" "/etc/nginx")
|
||||
(run "chown -R root:root /etc/nginx")
|
||||
(run "/usr/sbin/nginx -qt")
|
||||
(run "mkdir /www")
|
||||
(cmd "/usr/sbin/nginx")
|
||||
(volume "/www")
|
||||
(expose "80")
|
||||
@@ -1,23 +0,0 @@
|
||||
FROM ubuntu:12.04
|
||||
|
||||
EXPOSE 27015
|
||||
EXPOSE 27005
|
||||
EXPOSE 26901
|
||||
EXPOSE 27020
|
||||
|
||||
RUN apt-get update && apt-get install libc6-dev-i386 curl unzip -y
|
||||
RUN mkdir -p /steam
|
||||
RUN curl http://media.steampowered.com/client/steamcmd_linux.tar.gz | tar vxz -C /steam
|
||||
ADD ./script /steam/script
|
||||
RUN /steam/steamcmd.sh +runscript /steam/script
|
||||
RUN curl http://mirror.pointysoftware.net/alliedmodders/mmsource-1.10.0-linux.tar.gz | tar vxz -C /steam/tf2/tf
|
||||
RUN curl http://mirror.pointysoftware.net/alliedmodders/sourcemod-1.5.3-linux.tar.gz | tar vxz -C /steam/tf2/tf
|
||||
ADD ./server.cfg /steam/tf2/tf/cfg/server.cfg
|
||||
ADD ./ctf_2fort.cfg /steam/tf2/tf/cfg/ctf_2fort.cfg
|
||||
ADD ./sourcemod.cfg /steam/tf2/tf/cfg/sourcemod/sourcemod.cfg
|
||||
RUN rm -r /steam/tf2/tf/addons/sourcemod/configs
|
||||
ADD ./configs /steam/tf2/tf/addons/sourcemod/configs
|
||||
RUN mkdir -p /steam/tf2/tf/addons/sourcemod/translations/en
|
||||
RUN cp /steam/tf2/tf/addons/sourcemod/translations/*.txt /steam/tf2/tf/addons/sourcemod/translations/en
|
||||
|
||||
CMD cd /steam/tf2 && ./srcds_run -port 27015 +ip 0.0.0.0 +map ctf_2fort -autoupdate -steam_dir /steam -steamcmd_script /steam/script +tf_bot_quota 12 +tf_bot_quota_mode fill
|
||||
@@ -1,20 +0,0 @@
|
||||
(from "ubuntu:12.04")
|
||||
(expose "27015")
|
||||
(expose "27005")
|
||||
(expose "26901")
|
||||
(expose "27020")
|
||||
(run "apt-get update && apt-get install libc6-dev-i386 curl unzip -y")
|
||||
(run "mkdir -p /steam")
|
||||
(run "curl http://media.steampowered.com/client/steamcmd_linux.tar.gz | tar vxz -C /steam")
|
||||
(add "./script" "/steam/script")
|
||||
(run "/steam/steamcmd.sh +runscript /steam/script")
|
||||
(run "curl http://mirror.pointysoftware.net/alliedmodders/mmsource-1.10.0-linux.tar.gz | tar vxz -C /steam/tf2/tf")
|
||||
(run "curl http://mirror.pointysoftware.net/alliedmodders/sourcemod-1.5.3-linux.tar.gz | tar vxz -C /steam/tf2/tf")
|
||||
(add "./server.cfg" "/steam/tf2/tf/cfg/server.cfg")
|
||||
(add "./ctf_2fort.cfg" "/steam/tf2/tf/cfg/ctf_2fort.cfg")
|
||||
(add "./sourcemod.cfg" "/steam/tf2/tf/cfg/sourcemod/sourcemod.cfg")
|
||||
(run "rm -r /steam/tf2/tf/addons/sourcemod/configs")
|
||||
(add "./configs" "/steam/tf2/tf/addons/sourcemod/configs")
|
||||
(run "mkdir -p /steam/tf2/tf/addons/sourcemod/translations/en")
|
||||
(run "cp /steam/tf2/tf/addons/sourcemod/translations/*.txt /steam/tf2/tf/addons/sourcemod/translations/en")
|
||||
(cmd "cd /steam/tf2 && ./srcds_run -port 27015 +ip 0.0.0.0 +map ctf_2fort -autoupdate -steam_dir /steam -steamcmd_script /steam/script +tf_bot_quota 12 +tf_bot_quota_mode fill")
|
||||
@@ -1,9 +0,0 @@
|
||||
FROM ubuntu:14.04
|
||||
|
||||
RUN apt-get update -qy && apt-get install tmux zsh weechat-curses -y
|
||||
|
||||
ADD .weechat /.weechat
|
||||
ADD .tmux.conf /
|
||||
RUN echo "export TERM=screen-256color" >/.zshenv
|
||||
|
||||
CMD zsh -c weechat
|
||||
@@ -1,6 +0,0 @@
|
||||
(from "ubuntu:14.04")
|
||||
(run "apt-get update -qy && apt-get install tmux zsh weechat-curses -y")
|
||||
(add ".weechat" "/.weechat")
|
||||
(add ".tmux.conf" "/")
|
||||
(run "echo \"export TERM=screen-256color\" >/.zshenv")
|
||||
(cmd "zsh -c weechat")
|
||||
@@ -1,7 +0,0 @@
|
||||
FROM ubuntu:14.04
|
||||
MAINTAINER Erik Hollensbe <erik@hollensbe.org>
|
||||
|
||||
RUN apt-get update && apt-get install znc -y
|
||||
ADD conf /.znc
|
||||
|
||||
CMD [ "/usr/bin/znc", "-f", "-r" ]
|
||||
@@ -1,5 +0,0 @@
|
||||
(from "ubuntu:14.04")
|
||||
(maintainer "Erik Hollensbe <erik@hollensbe.org>")
|
||||
(run "apt-get update && apt-get install znc -y")
|
||||
(add "conf" "/.znc")
|
||||
(cmd "/usr/bin/znc" "-f" "-r")
|
||||
@@ -1,94 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// QuoteString walks characters (after trimming), escapes any quotes and
|
||||
// escapes, then wraps the whole thing in quotes. Very useful for generating
|
||||
// argument output in nodes.
|
||||
func QuoteString(str string) string {
|
||||
result := ""
|
||||
chars := strings.Split(strings.TrimSpace(str), "")
|
||||
|
||||
for _, char := range chars {
|
||||
switch char {
|
||||
case `"`:
|
||||
result += `\"`
|
||||
case `\`:
|
||||
result += `\\`
|
||||
default:
|
||||
result += char
|
||||
}
|
||||
}
|
||||
|
||||
return `"` + result + `"`
|
||||
}
|
||||
|
||||
// dumps the AST defined by `node` as a list of sexps. Returns a string
|
||||
// suitable for printing.
|
||||
func (node *Node) Dump() string {
|
||||
str := ""
|
||||
str += node.Value
|
||||
|
||||
for _, n := range node.Children {
|
||||
str += "(" + n.Dump() + ")\n"
|
||||
}
|
||||
|
||||
if node.Next != nil {
|
||||
for n := node.Next; n != nil; n = n.Next {
|
||||
if len(n.Children) > 0 {
|
||||
str += " " + n.Dump()
|
||||
} else {
|
||||
str += " " + QuoteString(n.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(str)
|
||||
}
|
||||
|
||||
// performs the dispatch based on the two primal strings, cmd and args. Please
|
||||
// look at the dispatch table in parser.go to see how these dispatchers work.
|
||||
func fullDispatch(cmd, args string) (*Node, map[string]bool, error) {
|
||||
fn := dispatch[cmd]
|
||||
|
||||
// Ignore invalid Dockerfile instructions
|
||||
if fn == nil {
|
||||
fn = parseIgnore
|
||||
}
|
||||
|
||||
sexp, attrs, err := fn(args)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return sexp, attrs, nil
|
||||
}
|
||||
|
||||
// splitCommand takes a single line of text and parses out the cmd and args,
|
||||
// which are used for dispatching to more exact parsing functions.
|
||||
func splitCommand(line string) (string, string, error) {
|
||||
cmdline := TOKEN_WHITESPACE.Split(line, 2)
|
||||
|
||||
if len(cmdline) != 2 {
|
||||
return "", "", fmt.Errorf("We do not understand this file. Please ensure it is a valid Dockerfile. Parser error at %q", line)
|
||||
}
|
||||
|
||||
cmd := strings.ToLower(cmdline[0])
|
||||
// the cmd should never have whitespace, but it's possible for the args to
|
||||
// have trailing whitespace.
|
||||
return cmd, strings.TrimSpace(cmdline[1]), nil
|
||||
}
|
||||
|
||||
// covers comments and empty lines. Lines should be trimmed before passing to
|
||||
// this function.
|
||||
func stripComments(line string) string {
|
||||
// string is already trimmed at this point
|
||||
if TOKEN_COMMENT.MatchString(line) {
|
||||
return TOKEN_COMMENT.ReplaceAllString(line, "")
|
||||
}
|
||||
|
||||
return line
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// `\\\\+|[^\\]|\b|\A` - match any number of "\\" (ie, properly-escaped backslashes), or a single non-backslash character, or a word boundary, or beginning-of-line
|
||||
// `\$` - match literal $
|
||||
// `[[:alnum:]_]+` - match things like `$SOME_VAR`
|
||||
// `{[[:alnum:]_]+}` - match things like `${SOME_VAR}`
|
||||
tokenEnvInterpolation = regexp.MustCompile(`(\\|\\\\+|[^\\]|\b|\A)\$([[:alnum:]_]+|{[[:alnum:]_]+})`)
|
||||
// this intentionally punts on more exotic interpolations like ${SOME_VAR%suffix} and lets the shell handle those directly
|
||||
)
|
||||
|
||||
// handle environment replacement. Used in dispatcher.
|
||||
func (b *Builder) replaceEnv(str string) string {
|
||||
for _, match := range tokenEnvInterpolation.FindAllString(str, -1) {
|
||||
idx := strings.Index(match, "\\$")
|
||||
if idx != -1 {
|
||||
if idx+2 >= len(match) {
|
||||
str = strings.Replace(str, match, "\\$", -1)
|
||||
continue
|
||||
}
|
||||
|
||||
prefix := match[:idx]
|
||||
stripped := match[idx+2:]
|
||||
str = strings.Replace(str, match, prefix+"$"+stripped, -1)
|
||||
continue
|
||||
}
|
||||
|
||||
match = match[strings.Index(match, "$"):]
|
||||
matchKey := strings.Trim(match, "${}")
|
||||
|
||||
for _, keyval := range b.Config.Env {
|
||||
tmp := strings.SplitN(keyval, "=", 2)
|
||||
if tmp[0] == matchKey {
|
||||
str = strings.Replace(str, match, tmp[1], -1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
func handleJsonArgs(args []string, attributes map[string]bool) []string {
|
||||
if len(args) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
if attributes != nil && attributes["json"] {
|
||||
return args
|
||||
}
|
||||
|
||||
// literal string command, not an exec array
|
||||
return []string{strings.Join(args, " ")}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/docker/api"
|
||||
apiserver "github.com/docker/docker/api/server"
|
||||
"github.com/docker/docker/daemon/networkdriver/bridge"
|
||||
"github.com/docker/docker/dockerversion"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/events"
|
||||
"github.com/docker/docker/pkg/parsers/kernel"
|
||||
)
|
||||
|
||||
func Register(eng *engine.Engine) error {
|
||||
if err := daemon(eng); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := remote(eng); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := events.New().Install(eng); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := eng.Register("version", dockerVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// remote: a RESTful api for cross-docker communication
|
||||
func remote(eng *engine.Engine) error {
|
||||
if err := eng.Register("serveapi", apiserver.ServeApi); err != nil {
|
||||
return err
|
||||
}
|
||||
return eng.Register("acceptconnections", apiserver.AcceptConnections)
|
||||
}
|
||||
|
||||
// daemon: a default execution and storage backend for Docker on Linux,
|
||||
// with the following underlying components:
|
||||
//
|
||||
// * Pluggable storage drivers including aufs, vfs, lvm and btrfs.
|
||||
// * Pluggable execution drivers including lxc and chroot.
|
||||
//
|
||||
// In practice `daemon` still includes most core Docker components, including:
|
||||
//
|
||||
// * The reference registry client implementation
|
||||
// * Image management
|
||||
// * The build facility
|
||||
// * Logging
|
||||
//
|
||||
// These components should be broken off into plugins of their own.
|
||||
//
|
||||
func daemon(eng *engine.Engine) error {
|
||||
return eng.Register("init_networkdriver", bridge.InitDriver)
|
||||
}
|
||||
|
||||
// builtins jobs independent of any subsystem
|
||||
func dockerVersion(job *engine.Job) engine.Status {
|
||||
v := &engine.Env{}
|
||||
v.SetJson("Version", dockerversion.VERSION)
|
||||
v.SetJson("ApiVersion", api.APIVERSION)
|
||||
v.SetJson("GitCommit", dockerversion.GITCOMMIT)
|
||||
v.Set("GoVersion", runtime.Version())
|
||||
v.Set("Os", runtime.GOOS)
|
||||
v.Set("Arch", runtime.GOARCH)
|
||||
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
|
||||
v.Set("KernelVersion", kernelVersion.String())
|
||||
}
|
||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
return engine.StatusOK
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"../future"
|
||||
"../rcli"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Run docker in "simple mode": run a single command and return.
|
||||
func SimpleMode(args []string) error {
|
||||
var oldState *State
|
||||
var err error
|
||||
if IsTerminal(0) && os.Getenv("NORAW") == "" {
|
||||
oldState, err = MakeRaw(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer Restore(0, oldState)
|
||||
}
|
||||
// FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose
|
||||
// CloseWrite(), which we need to cleanly signal that stdin is closed without
|
||||
// closing the connection.
|
||||
// See http://code.google.com/p/go/issues/detail?id=3345
|
||||
conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
receive_stdout := future.Go(func() error {
|
||||
_, err := io.Copy(os.Stdout, conn)
|
||||
return err
|
||||
})
|
||||
send_stdin := future.Go(func() error {
|
||||
_, err := io.Copy(conn, os.Stdin)
|
||||
if err := conn.CloseWrite(); err != nil {
|
||||
log.Printf("Couldn't send EOF: " + err.Error())
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err := <-receive_stdout; err != nil {
|
||||
return err
|
||||
}
|
||||
if oldState != nil {
|
||||
Restore(0, oldState)
|
||||
}
|
||||
if !IsTerminal(0) {
|
||||
if err := <-send_stdin; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run docker in "interactive mode": run a bash-compatible shell capable of running docker commands.
|
||||
func InteractiveMode(scripts ...string) error {
|
||||
// Determine path of current docker binary
|
||||
dockerPath, err := exec.LookPath(os.Args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dockerPath, err = filepath.Abs(dockerPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a temp directory
|
||||
tmp, err := ioutil.TempDir("", "docker-shell")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
// For each command, create an alias in temp directory
|
||||
// FIXME: generate this list dynamically with introspection of some sort
|
||||
// It might make sense to merge docker and dockerd to keep that introspection
|
||||
// within a single binary.
|
||||
for _, cmd := range []string{
|
||||
"help",
|
||||
"run",
|
||||
"ps",
|
||||
"pull",
|
||||
"put",
|
||||
"rm",
|
||||
"kill",
|
||||
"wait",
|
||||
"stop",
|
||||
"start",
|
||||
"restart",
|
||||
"logs",
|
||||
"diff",
|
||||
"commit",
|
||||
"attach",
|
||||
"info",
|
||||
"tar",
|
||||
"web",
|
||||
"images",
|
||||
"docker",
|
||||
} {
|
||||
if err := os.Symlink(dockerPath, path.Join(tmp, cmd)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Run $SHELL with PATH set to temp directory
|
||||
rcfile, err := ioutil.TempFile("", "docker-shell-rc")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(rcfile.Name())
|
||||
io.WriteString(rcfile, "enable -n help\n")
|
||||
os.Setenv("PATH", tmp+":"+os.Getenv("PATH"))
|
||||
os.Setenv("PS1", "\\h docker> ")
|
||||
shell := exec.Command("/bin/bash", append([]string{"--rcfile", rcfile.Name()}, scripts...)...)
|
||||
shell.Stdin = os.Stdin
|
||||
shell.Stdout = os.Stdout
|
||||
shell.Stderr = os.Stderr
|
||||
if err := shell.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type Termios struct {
|
||||
Iflag uintptr
|
||||
Oflag uintptr
|
||||
Cflag uintptr
|
||||
Lflag uintptr
|
||||
Cc [20]byte
|
||||
Ispeed uintptr
|
||||
Ospeed uintptr
|
||||
}
|
||||
|
||||
const (
|
||||
// Input flags
|
||||
inpck = 0x010
|
||||
istrip = 0x020
|
||||
icrnl = 0x100
|
||||
ixon = 0x200
|
||||
|
||||
// Output flags
|
||||
opost = 0x1
|
||||
|
||||
// Control flags
|
||||
cs8 = 0x300
|
||||
|
||||
// Local flags
|
||||
icanon = 0x100
|
||||
iexten = 0x400
|
||||
)
|
||||
|
||||
const (
|
||||
HUPCL = 0x4000
|
||||
ICANON = 0x100
|
||||
ICRNL = 0x100
|
||||
IEXTEN = 0x400
|
||||
BRKINT = 0x2
|
||||
CFLUSH = 0xf
|
||||
CLOCAL = 0x8000
|
||||
CREAD = 0x800
|
||||
CS5 = 0x0
|
||||
CS6 = 0x100
|
||||
CS7 = 0x200
|
||||
CS8 = 0x300
|
||||
CSIZE = 0x300
|
||||
CSTART = 0x11
|
||||
CSTATUS = 0x14
|
||||
CSTOP = 0x13
|
||||
CSTOPB = 0x400
|
||||
CSUSP = 0x1a
|
||||
IGNBRK = 0x1
|
||||
IGNCR = 0x80
|
||||
IGNPAR = 0x4
|
||||
IMAXBEL = 0x2000
|
||||
INLCR = 0x40
|
||||
INPCK = 0x10
|
||||
ISIG = 0x80
|
||||
ISTRIP = 0x20
|
||||
IUTF8 = 0x4000
|
||||
IXANY = 0x800
|
||||
IXOFF = 0x400
|
||||
IXON = 0x200
|
||||
NOFLSH = 0x80000000
|
||||
OCRNL = 0x10
|
||||
OFDEL = 0x20000
|
||||
OFILL = 0x80
|
||||
ONLCR = 0x2
|
||||
ONLRET = 0x40
|
||||
ONOCR = 0x20
|
||||
ONOEOT = 0x8
|
||||
OPOST = 0x1
|
||||
RENB = 0x1000
|
||||
PARMRK = 0x8
|
||||
PARODD = 0x2000
|
||||
|
||||
TOSTOP = 0x400000
|
||||
VDISCARD = 0xf
|
||||
VDSUSP = 0xb
|
||||
VEOF = 0x0
|
||||
VEOL = 0x1
|
||||
VEOL2 = 0x2
|
||||
VERASE = 0x3
|
||||
VINTR = 0x8
|
||||
VKILL = 0x5
|
||||
VLNEXT = 0xe
|
||||
VMIN = 0x10
|
||||
VQUIT = 0x9
|
||||
VREPRINT = 0x6
|
||||
VSTART = 0xc
|
||||
VSTATUS = 0x12
|
||||
VSTOP = 0xd
|
||||
VSUSP = 0xa
|
||||
VT0 = 0x0
|
||||
VT1 = 0x10000
|
||||
VTDLY = 0x10000
|
||||
VTIME = 0x11
|
||||
ECHO = 0x00000008
|
||||
|
||||
PENDIN = 0x20000000
|
||||
)
|
||||
|
||||
type State struct {
|
||||
termios Termios
|
||||
}
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd int) bool {
|
||||
var termios Termios
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||
return err == 0
|
||||
}
|
||||
|
||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func MakeRaw(fd int) (*State, error) {
|
||||
var oldState State
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newState := oldState.termios
|
||||
newState.Iflag &^= ISTRIP | INLCR | IGNCR | IXON | IXOFF
|
||||
newState.Iflag |= ICRNL
|
||||
newState.Oflag |= ONLCR
|
||||
newState.Lflag &^= ECHO | ICANON | ISIG
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &oldState, nil
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func Restore(fd int, state *State) error {
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package client
|
||||
|
||||
import "syscall"
|
||||
|
||||
const (
|
||||
getTermios = syscall.TIOCGETA
|
||||
setTermios = syscall.TIOCSETA
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
package client
|
||||
|
||||
import "syscall"
|
||||
|
||||
const (
|
||||
getTermios = syscall.TCGETS
|
||||
setTermios = syscall.TCSETS
|
||||
)
|
||||
+540
@@ -0,0 +1,540 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"./fs"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/kr/pty"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var sysInitPath string
|
||||
|
||||
func init() {
|
||||
sysInitPath = SelfPath()
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
Id string
|
||||
Root string
|
||||
|
||||
Created time.Time
|
||||
|
||||
Path string
|
||||
Args []string
|
||||
|
||||
Config *Config
|
||||
Mountpoint *fs.Mountpoint
|
||||
State *State
|
||||
Image string
|
||||
|
||||
network *NetworkInterface
|
||||
networkManager *NetworkManager
|
||||
NetworkSettings *NetworkSettings
|
||||
|
||||
SysInitPath string
|
||||
lxcConfigPath string
|
||||
cmd *exec.Cmd
|
||||
stdout *writeBroadcaster
|
||||
stderr *writeBroadcaster
|
||||
stdin io.ReadCloser
|
||||
stdinPipe io.WriteCloser
|
||||
|
||||
stdoutLog *os.File
|
||||
stderrLog *os.File
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Hostname string
|
||||
User string
|
||||
Memory int64 // Memory limit (in bytes)
|
||||
MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap
|
||||
Ports []int
|
||||
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
||||
OpenStdin bool // Open stdin
|
||||
}
|
||||
|
||||
type NetworkSettings struct {
|
||||
IpAddress string
|
||||
IpPrefixLen int
|
||||
Gateway string
|
||||
PortMapping map[string]string
|
||||
}
|
||||
|
||||
func createContainer(id string, root string, command string, args []string, image *fs.Image, config *Config, netManager *NetworkManager) (*Container, error) {
|
||||
mountpoint, err := image.Mountpoint(path.Join(root, "rootfs"), path.Join(root, "rw"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
container := &Container{
|
||||
Id: id,
|
||||
Root: root,
|
||||
Created: time.Now(),
|
||||
Path: command,
|
||||
Args: args,
|
||||
Config: config,
|
||||
Image: image.Id,
|
||||
Mountpoint: mountpoint,
|
||||
State: newState(),
|
||||
networkManager: netManager,
|
||||
NetworkSettings: &NetworkSettings{},
|
||||
SysInitPath: sysInitPath,
|
||||
lxcConfigPath: path.Join(root, "config.lxc"),
|
||||
stdout: newWriteBroadcaster(),
|
||||
stderr: newWriteBroadcaster(),
|
||||
}
|
||||
if err := os.Mkdir(root, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Setup logging of stdout and stderr to disk
|
||||
if stdoutLog, err := os.OpenFile(path.Join(container.Root, id+"-stdout.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
container.stdoutLog = stdoutLog
|
||||
}
|
||||
if stderrLog, err := os.OpenFile(path.Join(container.Root, id+"-stderr.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
container.stderrLog = stderrLog
|
||||
}
|
||||
if container.Config.OpenStdin {
|
||||
container.stdin, container.stdinPipe = io.Pipe()
|
||||
} else {
|
||||
container.stdinPipe = NopWriteCloser(ioutil.Discard) // Silently drop stdin
|
||||
}
|
||||
container.stdout.AddWriter(NopWriteCloser(container.stdoutLog))
|
||||
container.stderr.AddWriter(NopWriteCloser(container.stderrLog))
|
||||
|
||||
if err := container.save(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func loadContainer(store *fs.Store, containerPath string, netManager *NetworkManager) (*Container, error) {
|
||||
data, err := ioutil.ReadFile(path.Join(containerPath, "config.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mountpoint, err := store.FetchMountpoint(
|
||||
path.Join(containerPath, "rootfs"),
|
||||
path.Join(containerPath, "rw"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if mountpoint == nil {
|
||||
return nil, errors.New("Couldn't load container: unregistered mountpoint.")
|
||||
}
|
||||
container := &Container{
|
||||
stdout: newWriteBroadcaster(),
|
||||
stderr: newWriteBroadcaster(),
|
||||
lxcConfigPath: path.Join(containerPath, "config.lxc"),
|
||||
networkManager: netManager,
|
||||
NetworkSettings: &NetworkSettings{},
|
||||
Mountpoint: mountpoint,
|
||||
}
|
||||
// Load container settings
|
||||
if err := json.Unmarshal(data, container); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Setup logging of stdout and stderr to disk
|
||||
if stdoutLog, err := os.OpenFile(path.Join(container.Root, container.Id+"-stdout.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
container.stdoutLog = stdoutLog
|
||||
}
|
||||
if stderrLog, err := os.OpenFile(path.Join(container.Root, container.Id+"-stderr.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
container.stderrLog = stderrLog
|
||||
}
|
||||
container.stdout.AddWriter(NopWriteCloser(container.stdoutLog))
|
||||
container.stderr.AddWriter(NopWriteCloser(container.stderrLog))
|
||||
|
||||
if container.Config.OpenStdin {
|
||||
container.stdin, container.stdinPipe = io.Pipe()
|
||||
} else {
|
||||
container.stdinPipe = NopWriteCloser(ioutil.Discard) // Silently drop stdin
|
||||
}
|
||||
container.State = newState()
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (container *Container) Cmd() *exec.Cmd {
|
||||
return container.cmd
|
||||
}
|
||||
|
||||
func (container *Container) When() time.Time {
|
||||
return container.Created
|
||||
}
|
||||
|
||||
func (container *Container) loadUserData() (map[string]string, error) {
|
||||
jsonData, err := ioutil.ReadFile(path.Join(container.Root, "userdata.json"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return make(map[string]string), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
data := make(map[string]string)
|
||||
if err := json.Unmarshal(jsonData, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (container *Container) saveUserData(data map[string]string) error {
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(path.Join(container.Root, "userdata.json"), jsonData, 0700)
|
||||
}
|
||||
|
||||
func (container *Container) SetUserData(key, value string) error {
|
||||
data, err := container.loadUserData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data[key] = value
|
||||
return container.saveUserData(data)
|
||||
}
|
||||
|
||||
func (container *Container) GetUserData(key string) string {
|
||||
data, err := container.loadUserData()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if value, exists := data[key]; exists {
|
||||
return value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (container *Container) save() (err error) {
|
||||
data, err := json.Marshal(container)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return ioutil.WriteFile(path.Join(container.Root, "config.json"), data, 0666)
|
||||
}
|
||||
|
||||
func (container *Container) generateLXCConfig() error {
|
||||
fo, err := os.Create(container.lxcConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fo.Close()
|
||||
|
||||
if err := LxcTemplateCompiled.Execute(fo, container); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) startPty() error {
|
||||
stdout_master, stdout_slave, err := pty.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.cmd.Stdout = stdout_slave
|
||||
|
||||
stderr_master, stderr_slave, err := pty.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.cmd.Stderr = stderr_slave
|
||||
|
||||
// Copy the PTYs to our broadcasters
|
||||
go func() {
|
||||
defer container.stdout.Close()
|
||||
io.Copy(container.stdout, stdout_master)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer container.stderr.Close()
|
||||
io.Copy(container.stderr, stderr_master)
|
||||
}()
|
||||
|
||||
// stdin
|
||||
var stdin_slave io.ReadCloser
|
||||
if container.Config.OpenStdin {
|
||||
stdin_master, stdin_slave, err := pty.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.cmd.Stdin = stdin_slave
|
||||
// FIXME: The following appears to be broken.
|
||||
// "cannot set terminal process group (-1): Inappropriate ioctl for device"
|
||||
// container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
|
||||
go func() {
|
||||
defer container.stdin.Close()
|
||||
io.Copy(stdin_master, container.stdin)
|
||||
}()
|
||||
}
|
||||
if err := container.cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
stdout_slave.Close()
|
||||
stderr_slave.Close()
|
||||
if stdin_slave != nil {
|
||||
stdin_slave.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) start() error {
|
||||
container.cmd.Stdout = container.stdout
|
||||
container.cmd.Stderr = container.stderr
|
||||
if container.Config.OpenStdin {
|
||||
stdin, err := container.cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
defer stdin.Close()
|
||||
io.Copy(stdin, container.stdin)
|
||||
}()
|
||||
}
|
||||
return container.cmd.Start()
|
||||
}
|
||||
|
||||
func (container *Container) Start() error {
|
||||
if err := container.Mountpoint.EnsureMounted(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.allocateNetwork(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.generateLXCConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
params := []string{
|
||||
"-n", container.Id,
|
||||
"-f", container.lxcConfigPath,
|
||||
"--",
|
||||
"/sbin/init",
|
||||
}
|
||||
|
||||
// Networking
|
||||
params = append(params, "-g", container.network.Gateway.String())
|
||||
|
||||
// User
|
||||
if container.Config.User != "" {
|
||||
params = append(params, "-u", container.Config.User)
|
||||
}
|
||||
|
||||
// Program
|
||||
params = append(params, "--", container.Path)
|
||||
params = append(params, container.Args...)
|
||||
|
||||
container.cmd = exec.Command("/usr/bin/lxc-start", params...)
|
||||
|
||||
var err error
|
||||
if container.Config.Tty {
|
||||
err = container.startPty()
|
||||
} else {
|
||||
err = container.start()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.State.setRunning(container.cmd.Process.Pid)
|
||||
container.save()
|
||||
go container.monitor()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) Run() error {
|
||||
if err := container.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
container.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) Output() (output []byte, err error) {
|
||||
pipe, err := container.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer pipe.Close()
|
||||
if err := container.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output, err = ioutil.ReadAll(pipe)
|
||||
container.Wait()
|
||||
return output, err
|
||||
}
|
||||
|
||||
// StdinPipe() returns a pipe connected to the standard input of the container's
|
||||
// active process.
|
||||
//
|
||||
func (container *Container) StdinPipe() (io.WriteCloser, error) {
|
||||
return container.stdinPipe, nil
|
||||
}
|
||||
|
||||
func (container *Container) StdoutPipe() (io.ReadCloser, error) {
|
||||
reader, writer := io.Pipe()
|
||||
container.stdout.AddWriter(writer)
|
||||
return newBufReader(reader), nil
|
||||
}
|
||||
|
||||
func (container *Container) StdoutLog() io.Reader {
|
||||
r, err := os.Open(container.stdoutLog.Name())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (container *Container) StderrPipe() (io.ReadCloser, error) {
|
||||
reader, writer := io.Pipe()
|
||||
container.stderr.AddWriter(writer)
|
||||
return newBufReader(reader), nil
|
||||
}
|
||||
|
||||
func (container *Container) StderrLog() io.Reader {
|
||||
r, err := os.Open(container.stderrLog.Name())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (container *Container) allocateNetwork() error {
|
||||
iface, err := container.networkManager.Allocate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.NetworkSettings.PortMapping = make(map[string]string)
|
||||
for _, port := range container.Config.Ports {
|
||||
if extPort, err := iface.AllocatePort(port); err != nil {
|
||||
iface.Release()
|
||||
return err
|
||||
} else {
|
||||
container.NetworkSettings.PortMapping[strconv.Itoa(port)] = strconv.Itoa(extPort)
|
||||
}
|
||||
}
|
||||
container.network = iface
|
||||
container.NetworkSettings.IpAddress = iface.IPNet.IP.String()
|
||||
container.NetworkSettings.IpPrefixLen, _ = iface.IPNet.Mask.Size()
|
||||
container.NetworkSettings.Gateway = iface.Gateway.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) releaseNetwork() error {
|
||||
err := container.network.Release()
|
||||
container.network = nil
|
||||
container.NetworkSettings = &NetworkSettings{}
|
||||
return err
|
||||
}
|
||||
|
||||
func (container *Container) monitor() {
|
||||
// Wait for the program to exit
|
||||
container.cmd.Wait()
|
||||
exitCode := container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
|
||||
// Cleanup
|
||||
if err := container.releaseNetwork(); err != nil {
|
||||
log.Printf("%v: Failed to release network: %v", container.Id, err)
|
||||
}
|
||||
container.stdout.Close()
|
||||
container.stderr.Close()
|
||||
if err := container.Mountpoint.Umount(); err != nil {
|
||||
log.Printf("%v: Failed to umount filesystem: %v", container.Id, err)
|
||||
}
|
||||
|
||||
// Re-create a brand new stdin pipe once the container exited
|
||||
if container.Config.OpenStdin {
|
||||
container.stdin, container.stdinPipe = io.Pipe()
|
||||
}
|
||||
|
||||
// Report status back
|
||||
container.State.setStopped(exitCode)
|
||||
container.save()
|
||||
}
|
||||
|
||||
func (container *Container) kill() error {
|
||||
if err := container.cmd.Process.Kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Wait for the container to be actually stopped
|
||||
container.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) Kill() error {
|
||||
if !container.State.Running {
|
||||
return nil
|
||||
}
|
||||
return container.kill()
|
||||
}
|
||||
|
||||
func (container *Container) Stop() error {
|
||||
if !container.State.Running {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 1. Send a SIGTERM
|
||||
if output, err := exec.Command("/usr/bin/lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
|
||||
log.Printf(string(output))
|
||||
log.Printf("Failed to send SIGTERM to the process, force killing")
|
||||
if err := container.Kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Wait for the process to exit on its own
|
||||
if err := container.WaitTimeout(10 * time.Second); err != nil {
|
||||
log.Printf("Container %v failed to exit within 10 seconds of SIGTERM - using the force", container.Id)
|
||||
if err := container.Kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) Restart() error {
|
||||
if err := container.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait blocks until the container stops running, then returns its exit code.
|
||||
func (container *Container) Wait() int {
|
||||
|
||||
for container.State.Running {
|
||||
container.State.wait()
|
||||
}
|
||||
return container.State.ExitCode
|
||||
}
|
||||
|
||||
func (container *Container) WaitTimeout(timeout time.Duration) error {
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
container.Wait()
|
||||
done <- true
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
return errors.New("Timed Out")
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,759 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"./fs"
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCommitRun(t *testing.T) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
container1, err := docker.Create(
|
||||
"precommit_test",
|
||||
"/bin/sh",
|
||||
[]string{"-c", "echo hello > /world"},
|
||||
GetTestImage(docker),
|
||||
&Config{
|
||||
Memory: 33554432,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container1.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container1.State.Running {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
|
||||
// FIXME: freeze the container before copying it to avoid data corruption?
|
||||
rwTar, err := fs.Tar(container1.Mountpoint.Rw, fs.Uncompressed)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// Create a new image from the container's base layers + a new layer from container changes
|
||||
parentImg, err := docker.Store.Get(container1.Image)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
img, err := docker.Store.Create(rwTar, parentImg, "test_commitrun", "unit test commited image")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world
|
||||
|
||||
container2, err := docker.Create(
|
||||
"postcommit_test",
|
||||
"cat",
|
||||
[]string{"/world"},
|
||||
img,
|
||||
&Config{
|
||||
Memory: 33554432,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container2)
|
||||
|
||||
stdout, err := container2.StdoutPipe()
|
||||
stderr, err := container2.StderrPipe()
|
||||
if err := container2.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container2.Wait()
|
||||
output, err := ioutil.ReadAll(stdout)
|
||||
output2, err := ioutil.ReadAll(stderr)
|
||||
stdout.Close()
|
||||
stderr.Close()
|
||||
if string(output) != "hello\n" {
|
||||
t.Fatalf("\nout: %s\nerr: %s\n", string(output), string(output2))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
container, err := docker.Create(
|
||||
"run_test",
|
||||
"ls",
|
||||
[]string{"-al"},
|
||||
GetTestImage(docker),
|
||||
&Config{
|
||||
Memory: 33554432,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container)
|
||||
|
||||
if container.State.Running {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container.State.Running {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutput(t *testing.T) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
container, err := docker.Create(
|
||||
"output_test",
|
||||
"echo",
|
||||
[]string{"-n", "foobar"},
|
||||
GetTestImage(docker),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container)
|
||||
output, err := container.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(output) != "foobar" {
|
||||
t.Error(string(output))
|
||||
}
|
||||
}
|
||||
|
||||
func TestKill(t *testing.T) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
container, err := docker.Create(
|
||||
"stop_test",
|
||||
"cat",
|
||||
[]string{"/dev/zero"},
|
||||
GetTestImage(docker),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container)
|
||||
|
||||
if container.State.Running {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !container.State.Running {
|
||||
t.Errorf("Container should be running")
|
||||
}
|
||||
if err := container.Kill(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container.State.Running {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
container.Wait()
|
||||
if container.State.Running {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
// Try stopping twice
|
||||
if err := container.Kill(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExitCode(t *testing.T) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
|
||||
trueContainer, err := docker.Create(
|
||||
"exit_test_1",
|
||||
"/bin/true",
|
||||
[]string{""},
|
||||
GetTestImage(docker),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(trueContainer)
|
||||
if err := trueContainer.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
falseContainer, err := docker.Create(
|
||||
"exit_test_2",
|
||||
"/bin/false",
|
||||
[]string{""},
|
||||
GetTestImage(docker),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(falseContainer)
|
||||
if err := falseContainer.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if trueContainer.State.ExitCode != 0 {
|
||||
t.Errorf("Unexpected exit code %v", trueContainer.State.ExitCode)
|
||||
}
|
||||
|
||||
if falseContainer.State.ExitCode != 1 {
|
||||
t.Errorf("Unexpected exit code %v", falseContainer.State.ExitCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestart(t *testing.T) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
container, err := docker.Create(
|
||||
"restart_test",
|
||||
"echo",
|
||||
[]string{"-n", "foobar"},
|
||||
GetTestImage(docker),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container)
|
||||
output, err := container.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(output) != "foobar" {
|
||||
t.Error(string(output))
|
||||
}
|
||||
|
||||
// Run the container again and check the output
|
||||
output, err = container.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(output) != "foobar" {
|
||||
t.Error(string(output))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestartStdin(t *testing.T) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
container, err := docker.Create(
|
||||
"restart_stdin_test",
|
||||
"cat",
|
||||
[]string{},
|
||||
GetTestImage(docker),
|
||||
&Config{
|
||||
OpenStdin: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container)
|
||||
|
||||
stdin, err := container.StdinPipe()
|
||||
stdout, err := container.StdoutPipe()
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
io.WriteString(stdin, "hello world")
|
||||
stdin.Close()
|
||||
container.Wait()
|
||||
output, err := ioutil.ReadAll(stdout)
|
||||
stdout.Close()
|
||||
if string(output) != "hello world" {
|
||||
t.Fatal(string(output))
|
||||
}
|
||||
|
||||
// Restart and try again
|
||||
stdin, err = container.StdinPipe()
|
||||
stdout, err = container.StdoutPipe()
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
io.WriteString(stdin, "hello world #2")
|
||||
stdin.Close()
|
||||
container.Wait()
|
||||
output, err = ioutil.ReadAll(stdout)
|
||||
stdout.Close()
|
||||
if string(output) != "hello world #2" {
|
||||
t.Fatal(string(output))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUser(t *testing.T) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
|
||||
// Default user must be root
|
||||
container, err := docker.Create(
|
||||
"user_default",
|
||||
"id",
|
||||
[]string{},
|
||||
GetTestImage(docker),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container)
|
||||
output, err := container.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(output), "uid=0(root) gid=0(root)") {
|
||||
t.Error(string(output))
|
||||
}
|
||||
|
||||
// Set a username
|
||||
container, err = docker.Create(
|
||||
"user_root",
|
||||
"id",
|
||||
[]string{},
|
||||
GetTestImage(docker),
|
||||
&Config{
|
||||
User: "root",
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container)
|
||||
output, err = container.Output()
|
||||
if err != nil || container.State.ExitCode != 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(output), "uid=0(root) gid=0(root)") {
|
||||
t.Error(string(output))
|
||||
}
|
||||
|
||||
// Set a UID
|
||||
container, err = docker.Create(
|
||||
"user_uid0",
|
||||
"id",
|
||||
[]string{},
|
||||
GetTestImage(docker),
|
||||
&Config{
|
||||
User: "0",
|
||||
},
|
||||
)
|
||||
if err != nil || container.State.ExitCode != 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container)
|
||||
output, err = container.Output()
|
||||
if err != nil || container.State.ExitCode != 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(output), "uid=0(root) gid=0(root)") {
|
||||
t.Error(string(output))
|
||||
}
|
||||
|
||||
// Set a different user by uid
|
||||
container, err = docker.Create(
|
||||
"user_uid1",
|
||||
"id",
|
||||
[]string{},
|
||||
GetTestImage(docker),
|
||||
&Config{
|
||||
User: "1",
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container)
|
||||
output, err = container.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if container.State.ExitCode != 0 {
|
||||
t.Fatalf("Container exit code is invalid: %d\nOutput:\n%s\n", container.State.ExitCode, output)
|
||||
}
|
||||
if !strings.Contains(string(output), "uid=1(daemon) gid=1(daemon)") {
|
||||
t.Error(string(output))
|
||||
}
|
||||
|
||||
// Set a different user by username
|
||||
container, err = docker.Create(
|
||||
"user_daemon",
|
||||
"id",
|
||||
[]string{},
|
||||
GetTestImage(docker),
|
||||
&Config{
|
||||
User: "daemon",
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container)
|
||||
output, err = container.Output()
|
||||
if err != nil || container.State.ExitCode != 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(output), "uid=1(daemon) gid=1(daemon)") {
|
||||
t.Error(string(output))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleContainers(t *testing.T) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
|
||||
container1, err := docker.Create(
|
||||
"container1",
|
||||
"cat",
|
||||
[]string{"/dev/zero"},
|
||||
GetTestImage(docker),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container1)
|
||||
|
||||
container2, err := docker.Create(
|
||||
"container2",
|
||||
"cat",
|
||||
[]string{"/dev/zero"},
|
||||
GetTestImage(docker),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container2)
|
||||
|
||||
// Start both containers
|
||||
if err := container1.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := container2.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// If we are here, both containers should be running
|
||||
if !container1.State.Running {
|
||||
t.Fatal("Container not running")
|
||||
}
|
||||
if !container2.State.Running {
|
||||
t.Fatal("Container not running")
|
||||
}
|
||||
|
||||
// Kill them
|
||||
if err := container1.Kill(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := container2.Kill(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStdin(t *testing.T) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
container, err := docker.Create(
|
||||
"stdin_test",
|
||||
"cat",
|
||||
[]string{},
|
||||
GetTestImage(docker),
|
||||
&Config{
|
||||
OpenStdin: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container)
|
||||
|
||||
stdin, err := container.StdinPipe()
|
||||
stdout, err := container.StdoutPipe()
|
||||
defer stdin.Close()
|
||||
defer stdout.Close()
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
io.WriteString(stdin, "hello world")
|
||||
stdin.Close()
|
||||
container.Wait()
|
||||
output, err := ioutil.ReadAll(stdout)
|
||||
if string(output) != "hello world" {
|
||||
t.Fatal(string(output))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTty(t *testing.T) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
container, err := docker.Create(
|
||||
"tty_test",
|
||||
"cat",
|
||||
[]string{},
|
||||
GetTestImage(docker),
|
||||
&Config{
|
||||
OpenStdin: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container)
|
||||
|
||||
stdin, err := container.StdinPipe()
|
||||
stdout, err := container.StdoutPipe()
|
||||
defer stdin.Close()
|
||||
defer stdout.Close()
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
io.WriteString(stdin, "hello world")
|
||||
stdin.Close()
|
||||
container.Wait()
|
||||
output, err := ioutil.ReadAll(stdout)
|
||||
if string(output) != "hello world" {
|
||||
t.Fatal(string(output))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnv(t *testing.T) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
container, err := docker.Create(
|
||||
"env_test",
|
||||
"/usr/bin/env",
|
||||
[]string{},
|
||||
GetTestImage(docker),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container)
|
||||
stdout, err := container.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer stdout.Close()
|
||||
if err := container.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container.Wait()
|
||||
output, err := ioutil.ReadAll(stdout)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actualEnv := strings.Split(string(output), "\n")
|
||||
if actualEnv[len(actualEnv)-1] == "" {
|
||||
actualEnv = actualEnv[:len(actualEnv)-1]
|
||||
}
|
||||
sort.Strings(actualEnv)
|
||||
goodEnv := []string{
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"HOME=/",
|
||||
}
|
||||
sort.Strings(goodEnv)
|
||||
if len(goodEnv) != len(actualEnv) {
|
||||
t.Fatalf("Wrong environment: should be %d variables, not: '%s'\n", len(goodEnv), strings.Join(actualEnv, ", "))
|
||||
}
|
||||
for i := range goodEnv {
|
||||
if actualEnv[i] != goodEnv[i] {
|
||||
t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func grepFile(t *testing.T, path string, pattern string) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
r := bufio.NewReader(f)
|
||||
var (
|
||||
line string
|
||||
)
|
||||
err = nil
|
||||
for err == nil {
|
||||
line, err = r.ReadString('\n')
|
||||
if strings.Contains(line, pattern) == true {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Fatalf("grepFile: pattern \"%s\" not found in \"%s\"", pattern, path)
|
||||
}
|
||||
|
||||
func TestLXCConfig(t *testing.T) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
// Memory is allocated randomly for testing
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
memMin := 33554432
|
||||
memMax := 536870912
|
||||
mem := memMin + rand.Intn(memMax-memMin)
|
||||
container, err := docker.Create(
|
||||
"config_test",
|
||||
"/bin/true",
|
||||
[]string{},
|
||||
GetTestImage(docker),
|
||||
&Config{
|
||||
Hostname: "foobar",
|
||||
Memory: int64(mem),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container)
|
||||
container.generateLXCConfig()
|
||||
grepFile(t, container.lxcConfigPath, "lxc.utsname = foobar")
|
||||
grepFile(t, container.lxcConfigPath,
|
||||
fmt.Sprintf("lxc.cgroup.memory.limit_in_bytes = %d", mem))
|
||||
grepFile(t, container.lxcConfigPath,
|
||||
fmt.Sprintf("lxc.cgroup.memory.memsw.limit_in_bytes = %d", mem*2))
|
||||
}
|
||||
|
||||
func BenchmarkRunSequencial(b *testing.B) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
for i := 0; i < b.N; i++ {
|
||||
container, err := docker.Create(
|
||||
fmt.Sprintf("bench_%v", i),
|
||||
"echo",
|
||||
[]string{"-n", "foo"},
|
||||
GetTestImage(docker),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer docker.Destroy(container)
|
||||
output, err := container.Output()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if string(output) != "foo" {
|
||||
b.Fatalf("Unexecpted output: %v", string(output))
|
||||
}
|
||||
if err := docker.Destroy(container); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRunParallel(b *testing.B) {
|
||||
docker, err := newTestDocker()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer nuke(docker)
|
||||
|
||||
var tasks []chan error
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
complete := make(chan error)
|
||||
tasks = append(tasks, complete)
|
||||
go func(i int, complete chan error) {
|
||||
container, err := docker.Create(
|
||||
fmt.Sprintf("bench_%v", i),
|
||||
"echo",
|
||||
[]string{"-n", "foo"},
|
||||
GetTestImage(docker),
|
||||
&Config{},
|
||||
)
|
||||
if err != nil {
|
||||
complete <- err
|
||||
return
|
||||
}
|
||||
defer docker.Destroy(container)
|
||||
if err := container.Start(); err != nil {
|
||||
complete <- err
|
||||
return
|
||||
}
|
||||
if err := container.WaitTimeout(15 * time.Second); err != nil {
|
||||
complete <- err
|
||||
return
|
||||
}
|
||||
// if string(output) != "foo" {
|
||||
// complete <- fmt.Errorf("Unexecpted output: %v", string(output))
|
||||
// }
|
||||
if err := docker.Destroy(container); err != nil {
|
||||
complete <- err
|
||||
return
|
||||
}
|
||||
complete <- nil
|
||||
}(i, complete)
|
||||
}
|
||||
var errors []error
|
||||
for _, task := range tasks {
|
||||
err := <-task
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
if len(errors) > 0 {
|
||||
b.Fatal(errors)
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
@@ -1,4 +0,0 @@
|
||||
The `contrib` directory contains scripts, images, and other helpful things
|
||||
which are not part of the core docker distribution. Please note that they
|
||||
could be out of date, since they do not receive the same attention as the
|
||||
rest of the repository.
|
||||
@@ -1,175 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# bits of this were adapted from lxc-checkconfig
|
||||
# see also https://github.com/lxc/lxc/blob/lxc-1.0.2/src/lxc/lxc-checkconfig.in
|
||||
|
||||
possibleConfigs=(
|
||||
'/proc/config.gz'
|
||||
"/boot/config-$(uname -r)"
|
||||
"/usr/src/linux-$(uname -r)/.config"
|
||||
'/usr/src/linux/.config'
|
||||
)
|
||||
: ${CONFIG:="${possibleConfigs[0]}"}
|
||||
|
||||
if ! command -v zgrep &> /dev/null; then
|
||||
zgrep() {
|
||||
zcat "$2" | grep "$1"
|
||||
}
|
||||
fi
|
||||
|
||||
is_set() {
|
||||
zgrep "CONFIG_$1=[y|m]" "$CONFIG" > /dev/null
|
||||
}
|
||||
|
||||
# see http://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
||||
declare -A colors=(
|
||||
[black]=30
|
||||
[red]=31
|
||||
[green]=32
|
||||
[yellow]=33
|
||||
[blue]=34
|
||||
[magenta]=35
|
||||
[cyan]=36
|
||||
[white]=37
|
||||
)
|
||||
color() {
|
||||
color=()
|
||||
if [ "$1" = 'bold' ]; then
|
||||
color+=( '1' )
|
||||
shift
|
||||
fi
|
||||
if [ $# -gt 0 ] && [ "${colors[$1]}" ]; then
|
||||
color+=( "${colors[$1]}" )
|
||||
fi
|
||||
local IFS=';'
|
||||
echo -en '\033['"${color[*]}"m
|
||||
}
|
||||
wrap_color() {
|
||||
text="$1"
|
||||
shift
|
||||
color "$@"
|
||||
echo -n "$text"
|
||||
color reset
|
||||
echo
|
||||
}
|
||||
|
||||
wrap_good() {
|
||||
echo "$(wrap_color "$1" white): $(wrap_color "$2" green)"
|
||||
}
|
||||
wrap_bad() {
|
||||
echo "$(wrap_color "$1" bold): $(wrap_color "$2" bold red)"
|
||||
}
|
||||
wrap_warning() {
|
||||
wrap_color >&2 "$*" red
|
||||
}
|
||||
|
||||
check_flag() {
|
||||
if is_set "$1"; then
|
||||
wrap_good "CONFIG_$1" 'enabled'
|
||||
else
|
||||
wrap_bad "CONFIG_$1" 'missing'
|
||||
fi
|
||||
}
|
||||
|
||||
check_flags() {
|
||||
for flag in "$@"; do
|
||||
echo "- $(check_flag "$flag")"
|
||||
done
|
||||
}
|
||||
|
||||
if [ ! -e "$CONFIG" ]; then
|
||||
wrap_warning "warning: $CONFIG does not exist, searching other paths for kernel config..."
|
||||
for tryConfig in "${possibleConfigs[@]}"; do
|
||||
if [ -e "$tryConfig" ]; then
|
||||
CONFIG="$tryConfig"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ ! -e "$CONFIG" ]; then
|
||||
wrap_warning "error: cannot find kernel config"
|
||||
wrap_warning " try running this script again, specifying the kernel config:"
|
||||
wrap_warning " CONFIG=/path/to/kernel/.config $0"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
wrap_color "info: reading kernel config from $CONFIG ..." white
|
||||
echo
|
||||
|
||||
echo 'Generally Necessary:'
|
||||
|
||||
echo -n '- '
|
||||
cgroupSubsystemDir="$(awk '/[, ](cpu|cpuacct|cpuset|devices|freezer|memory)[, ]/ && $3 == "cgroup" { print $2 }' /proc/mounts | head -n1)"
|
||||
cgroupDir="$(dirname "$cgroupSubsystemDir")"
|
||||
if [ -d "$cgroupDir/cpu" -o -d "$cgroupDir/cpuacct" -o -d "$cgroupDir/cpuset" -o -d "$cgroupDir/devices" -o -d "$cgroupDir/freezer" -o -d "$cgroupDir/memory" ]; then
|
||||
echo "$(wrap_good 'cgroup hierarchy' 'properly mounted') [$cgroupDir]"
|
||||
else
|
||||
if [ "$cgroupSubsystemDir" ]; then
|
||||
echo "$(wrap_bad 'cgroup hierarchy' 'single mountpoint!') [$cgroupSubsystemDir]"
|
||||
else
|
||||
echo "$(wrap_bad 'cgroup hierarchy' 'nonexistent??')"
|
||||
fi
|
||||
echo " $(wrap_color '(see https://github.com/tianon/cgroupfs-mount)' yellow)"
|
||||
fi
|
||||
|
||||
if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = 'Y' ]; then
|
||||
echo -n '- '
|
||||
if command -v apparmor_parser &> /dev/null; then
|
||||
echo "$(wrap_good 'apparmor' 'enabled and tools installed')"
|
||||
else
|
||||
echo "$(wrap_bad 'apparmor' 'enabled, but apparmor_parser missing')"
|
||||
echo -n ' '
|
||||
if command -v apt-get &> /dev/null; then
|
||||
echo "$(wrap_color '(use "apt-get install apparmor" to fix this)')"
|
||||
elif command -v yum &> /dev/null; then
|
||||
echo "$(wrap_color '(your best bet is "yum install apparmor-parser")')"
|
||||
else
|
||||
echo "$(wrap_color '(look for an "apparmor" package for your distribution)')"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
flags=(
|
||||
NAMESPACES {NET,PID,IPC,UTS}_NS
|
||||
DEVPTS_MULTIPLE_INSTANCES
|
||||
CGROUPS CGROUP_CPUACCT CGROUP_DEVICE CGROUP_FREEZER CGROUP_SCHED
|
||||
MACVLAN VETH BRIDGE
|
||||
NF_NAT_IPV4 IP_NF_FILTER IP_NF_TARGET_MASQUERADE
|
||||
NETFILTER_XT_MATCH_{ADDRTYPE,CONNTRACK}
|
||||
NF_NAT NF_NAT_NEEDED
|
||||
)
|
||||
check_flags "${flags[@]}"
|
||||
echo
|
||||
|
||||
echo 'Optional Features:'
|
||||
flags=(
|
||||
MEMCG_SWAP
|
||||
RESOURCE_COUNTERS
|
||||
CGROUP_PERF
|
||||
)
|
||||
check_flags "${flags[@]}"
|
||||
|
||||
echo '- Storage Drivers:'
|
||||
{
|
||||
echo '- "'$(wrap_color 'aufs' blue)'":'
|
||||
check_flags AUFS_FS | sed 's/^/ /'
|
||||
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)"
|
||||
fi
|
||||
check_flags EXT4_FS_POSIX_ACL EXT4_FS_SECURITY | sed 's/^/ /'
|
||||
|
||||
echo '- "'$(wrap_color 'btrfs' blue)'":'
|
||||
check_flags BTRFS_FS | sed 's/^/ /'
|
||||
|
||||
echo '- "'$(wrap_color 'devicemapper' blue)'":'
|
||||
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/^/ /'
|
||||
echo
|
||||
|
||||
#echo 'Potential Future Features:'
|
||||
#check_flags USER_NS
|
||||
#echo
|
||||
@@ -1,2 +0,0 @@
|
||||
Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
Jessie Frazelle <jess@docker.com> (@jfrazelle)
|
||||
@@ -1,903 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# bash completion file for core docker commands
|
||||
#
|
||||
# This script provides completion of:
|
||||
# - commands and their options
|
||||
# - container ids and names
|
||||
# - image repos and tags
|
||||
# - filepaths
|
||||
#
|
||||
# To enable the completions either:
|
||||
# - place this file in /etc/bash_completion.d
|
||||
# or
|
||||
# - copy this file to e.g. ~/.docker-completion.sh and add the line
|
||||
# below to your .bashrc after bash completion features are loaded
|
||||
# . ~/.docker-completion.sh
|
||||
#
|
||||
# Note:
|
||||
# Currently, the completions will not work if the docker daemon is not
|
||||
# bound to the default communication port/socket
|
||||
# If the docker daemon is using a unix socket for communication your user
|
||||
# must have access to the socket for the completions to function correctly
|
||||
|
||||
__docker_q() {
|
||||
docker 2>/dev/null "$@"
|
||||
}
|
||||
|
||||
__docker_containers_all() {
|
||||
local IFS=$'\n'
|
||||
local containers=( $(__docker_q ps -aq --no-trunc) )
|
||||
if [ "$1" ]; then
|
||||
containers=( $(__docker_q inspect --format "{{if $1}}{{.Id}}{{end}}" "${containers[@]}") )
|
||||
fi
|
||||
local names=( $(__docker_q inspect --format '{{.Name}}' "${containers[@]}") )
|
||||
names=( "${names[@]#/}" ) # trim off the leading "/" from the container names
|
||||
unset IFS
|
||||
COMPREPLY=( $(compgen -W "${names[*]} ${containers[*]}" -- "$cur") )
|
||||
}
|
||||
|
||||
__docker_containers_running() {
|
||||
__docker_containers_all '.State.Running'
|
||||
}
|
||||
|
||||
__docker_containers_stopped() {
|
||||
__docker_containers_all 'not .State.Running'
|
||||
}
|
||||
|
||||
__docker_containers_pauseable() {
|
||||
__docker_containers_all 'and .State.Running (not .State.Paused)'
|
||||
}
|
||||
|
||||
__docker_containers_unpauseable() {
|
||||
__docker_containers_all '.State.Paused'
|
||||
}
|
||||
|
||||
__docker_image_repos() {
|
||||
local repos="$(__docker_q images | awk 'NR>1 && $1 != "<none>" { print $1 }')"
|
||||
COMPREPLY=( $(compgen -W "$repos" -- "$cur") )
|
||||
}
|
||||
|
||||
__docker_image_repos_and_tags() {
|
||||
local reposAndTags="$(__docker_q images | awk 'NR>1 && $1 != "<none>" { print $1; print $1":"$2 }')"
|
||||
COMPREPLY=( $(compgen -W "$reposAndTags" -- "$cur") )
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
__docker_image_repos_and_tags_and_ids() {
|
||||
local images="$(__docker_q images -a --no-trunc | awk 'NR>1 { print $3; if ($1 != "<none>") { print $1; print $1":"$2 } }')"
|
||||
COMPREPLY=( $(compgen -W "$images" -- "$cur") )
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
__docker_containers_and_images() {
|
||||
__docker_containers_all
|
||||
local containers=( "${COMPREPLY[@]}" )
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
COMPREPLY+=( "${containers[@]}" )
|
||||
}
|
||||
|
||||
__docker_pos_first_nonflag() {
|
||||
local argument_flags=$1
|
||||
|
||||
local counter=$cpos
|
||||
while [ $counter -le $cword ]; do
|
||||
if [ -n "$argument_flags" ] && eval "case '${words[$counter]}' in $argument_flags) true ;; *) false ;; esac"; then
|
||||
(( counter++ ))
|
||||
else
|
||||
case "${words[$counter]}" in
|
||||
-*)
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
(( counter++ ))
|
||||
done
|
||||
|
||||
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() {
|
||||
case "$prev" in
|
||||
-H)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-H" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=( $( compgen -W "${commands[*]} help" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_attach() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--no-stdin --sig-proxy" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter="$(__docker_pos_first_nonflag)"
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_running
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_build() {
|
||||
case "$prev" in
|
||||
-t|--tag)
|
||||
__docker_image_repos_and_tags
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t --tag -q --quiet --no-cache --rm --force-rm" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter="$(__docker_pos_first_nonflag '-t|--tag')"
|
||||
if [ $cword -eq $counter ]; then
|
||||
_filedir -d
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_commit() {
|
||||
case "$prev" in
|
||||
-m|--message|-a|--author|--run)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-m --message -a --author --run" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag '-m|--message|-a|--author|--run')
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
return
|
||||
fi
|
||||
(( counter++ ))
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
return
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_cp() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
case "$cur" in
|
||||
*:)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
__docker_containers_all
|
||||
COMPREPLY=( $( compgen -W "${COMPREPLY[*]}" -S ':' ) )
|
||||
compopt -o nospace
|
||||
return
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
(( counter++ ))
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
_filedir
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_create() {
|
||||
case "$prev" in
|
||||
-a|--attach)
|
||||
COMPREPLY=( $( compgen -W 'stdin stdout stderr' -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
--cidfile|--env-file)
|
||||
_filedir
|
||||
return
|
||||
;;
|
||||
--volumes-from)
|
||||
__docker_containers_all
|
||||
return
|
||||
;;
|
||||
-v|--volume|--device)
|
||||
case "$cur" in
|
||||
*:*)
|
||||
# TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine)
|
||||
;;
|
||||
'')
|
||||
COMPREPLY=( $( compgen -W '/' -- "$cur" ) )
|
||||
compopt -o nospace
|
||||
;;
|
||||
/*)
|
||||
_filedir
|
||||
compopt -o nospace
|
||||
;;
|
||||
esac
|
||||
return
|
||||
;;
|
||||
-e|--env)
|
||||
COMPREPLY=( $( compgen -e -- "$cur" ) )
|
||||
compopt -o nospace
|
||||
return
|
||||
;;
|
||||
--link)
|
||||
case "$cur" in
|
||||
*:*)
|
||||
;;
|
||||
*)
|
||||
__docker_containers_running
|
||||
COMPREPLY=( $( compgen -W "${COMPREPLY[*]}" -S ':' ) )
|
||||
compopt -o nospace
|
||||
;;
|
||||
esac
|
||||
return
|
||||
;;
|
||||
--add-host)
|
||||
case "$cur" in
|
||||
*:)
|
||||
__docker_resolve_hostname
|
||||
return
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--cap-add|--cap-drop)
|
||||
__docker_capabilities
|
||||
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
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
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|--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
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_diff() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_events() {
|
||||
case "$prev" in
|
||||
--since)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--since" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_exec() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-d --detach -i --interactive -t --tty" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_running
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_export() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_help() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
COMPREPLY=( $( compgen -W "${commands[*]}" -- "$cur" ) )
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_history() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-q --quiet --no-trunc" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_images() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-q --quiet -a --all --no-trunc -v --viz -t --tree" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_import() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
return
|
||||
fi
|
||||
(( counter++ ))
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_info() {
|
||||
return
|
||||
}
|
||||
|
||||
_docker_inspect() {
|
||||
case "$prev" in
|
||||
-f|--format)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-f --format" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_and_images
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_kill() {
|
||||
__docker_containers_running
|
||||
}
|
||||
|
||||
_docker_load() {
|
||||
return
|
||||
}
|
||||
|
||||
_docker_login() {
|
||||
case "$prev" in
|
||||
-u|--username|-p|--password|-e|--email)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-u --username -p --password -e --email" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_logs() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-f --follow" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_pause() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_pauseable
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_port() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_ps() {
|
||||
case "$prev" in
|
||||
--since|--before)
|
||||
__docker_containers_all
|
||||
;;
|
||||
-n)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-q --quiet -s --size -a --all --no-trunc -l --latest --since --before -n" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_pull() {
|
||||
case "$prev" in
|
||||
-t|--tag)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t --tag" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag '-t|--tag')
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_push() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_restart() {
|
||||
case "$prev" in
|
||||
-t|--time)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t --time" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_all
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_rm() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-f --force -l --link -v --volumes" -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
*)
|
||||
for arg in "${COMP_WORDS[@]}"; do
|
||||
case "$arg" in
|
||||
-f|--force)
|
||||
__docker_containers_all
|
||||
return
|
||||
;;
|
||||
esac
|
||||
done
|
||||
__docker_containers_stopped
|
||||
return
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_rmi() {
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
}
|
||||
|
||||
_docker_run() {
|
||||
case "$prev" in
|
||||
-a|--attach)
|
||||
COMPREPLY=( $( compgen -W 'stdin stdout stderr' -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
--cidfile|--env-file)
|
||||
_filedir
|
||||
return
|
||||
;;
|
||||
--volumes-from)
|
||||
__docker_containers_all
|
||||
return
|
||||
;;
|
||||
-v|--volume|--device)
|
||||
case "$cur" in
|
||||
*:*)
|
||||
# TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine)
|
||||
;;
|
||||
'')
|
||||
COMPREPLY=( $( compgen -W '/' -- "$cur" ) )
|
||||
compopt -o nospace
|
||||
;;
|
||||
/*)
|
||||
_filedir
|
||||
compopt -o nospace
|
||||
;;
|
||||
esac
|
||||
return
|
||||
;;
|
||||
-e|--env)
|
||||
COMPREPLY=( $( compgen -e -- "$cur" ) )
|
||||
compopt -o nospace
|
||||
return
|
||||
;;
|
||||
--link)
|
||||
case "$cur" in
|
||||
*:*)
|
||||
;;
|
||||
*)
|
||||
__docker_containers_running
|
||||
COMPREPLY=( $( compgen -W "${COMPREPLY[*]}" -S ':' ) )
|
||||
compopt -o nospace
|
||||
;;
|
||||
esac
|
||||
return
|
||||
;;
|
||||
--add-host)
|
||||
case "$cur" in
|
||||
*:)
|
||||
__docker_resolve_hostname
|
||||
return
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--cap-add|--cap-drop)
|
||||
__docker_capabilities
|
||||
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
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
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')
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_save() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_search() {
|
||||
case "$prev" in
|
||||
-s|--stars)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--no-trunc --automated -s --stars" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_start() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-a --attach -i --interactive" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_stopped
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_stop() {
|
||||
case "$prev" in
|
||||
-t|--time)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t --time" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_running
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_tag() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-f --force" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
return
|
||||
fi
|
||||
(( counter++ ))
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
return
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_unpause() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_unpauseable
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_top() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_running
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_version() {
|
||||
return
|
||||
}
|
||||
|
||||
_docker_wait() {
|
||||
__docker_containers_all
|
||||
}
|
||||
|
||||
_docker() {
|
||||
local commands=(
|
||||
attach
|
||||
build
|
||||
commit
|
||||
cp
|
||||
create
|
||||
diff
|
||||
events
|
||||
exec
|
||||
export
|
||||
history
|
||||
images
|
||||
import
|
||||
info
|
||||
insert
|
||||
inspect
|
||||
kill
|
||||
load
|
||||
login
|
||||
logs
|
||||
pause
|
||||
port
|
||||
ps
|
||||
pull
|
||||
push
|
||||
restart
|
||||
rm
|
||||
rmi
|
||||
run
|
||||
save
|
||||
search
|
||||
start
|
||||
stop
|
||||
tag
|
||||
top
|
||||
unpause
|
||||
version
|
||||
wait
|
||||
)
|
||||
|
||||
COMPREPLY=()
|
||||
local cur prev words cword
|
||||
_get_comp_words_by_ref -n : cur prev words cword
|
||||
|
||||
local command='docker' cpos=0
|
||||
local counter=1
|
||||
while [ $counter -lt $cword ]; do
|
||||
case "${words[$counter]}" in
|
||||
-H)
|
||||
(( counter++ ))
|
||||
;;
|
||||
-*)
|
||||
;;
|
||||
*)
|
||||
command="${words[$counter]}"
|
||||
cpos=$counter
|
||||
(( cpos++ ))
|
||||
break
|
||||
;;
|
||||
esac
|
||||
(( counter++ ))
|
||||
done
|
||||
|
||||
local completions_func=_docker_${command}
|
||||
declare -F $completions_func >/dev/null && $completions_func
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _docker docker
|
||||
@@ -1,285 +0,0 @@
|
||||
# docker.fish - docker completions for fish shell
|
||||
#
|
||||
# This file is generated by gen_docker_fish_completions.py from:
|
||||
# https://github.com/barnybug/docker-fish-completion
|
||||
#
|
||||
# To install the completions:
|
||||
# mkdir -p ~/.config/fish/completions
|
||||
# cp docker.fish ~/.config/fish/completions
|
||||
#
|
||||
# Completion supported:
|
||||
# - parameters
|
||||
# - commands
|
||||
# - containers
|
||||
# - images
|
||||
# - repositories
|
||||
|
||||
function __fish_docker_no_subcommand --description 'Test if docker has yet to be given the subcommand'
|
||||
for i in (commandline -opc)
|
||||
if contains -- $i attach build commit cp create diff events export history images import info insert inspect kill load login logs port ps pull push restart rm rmi run save search start stop tag top version wait
|
||||
return 1
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function __fish_print_docker_containers --description 'Print a list of docker containers' -a select
|
||||
switch $select
|
||||
case running
|
||||
docker ps -a --no-trunc | command awk 'NR>1' | command awk 'BEGIN {FS=" +"}; $5 ~ "^Up" {print $1 "\n" $(NF-1)}' | tr ',' '\n'
|
||||
case stopped
|
||||
docker ps -a --no-trunc | command awk 'NR>1' | command awk 'BEGIN {FS=" +"}; $5 ~ "^Exit" {print $1 "\n" $(NF-1)}' | tr ',' '\n'
|
||||
case all
|
||||
docker ps -a --no-trunc | command awk 'NR>1' | command awk 'BEGIN {FS=" +"}; {print $1 "\n" $(NF-1)}' | tr ',' '\n'
|
||||
end
|
||||
end
|
||||
|
||||
function __fish_print_docker_images --description 'Print a list of docker images'
|
||||
docker images | command awk 'NR>1' | command grep -v '<none>' | command awk '{print $1":"$2}'
|
||||
end
|
||||
|
||||
function __fish_print_docker_repositories --description 'Print a list of docker repositories'
|
||||
docker images | command awk 'NR>1' | command grep -v '<none>' | command awk '{print $1}' | command sort | command uniq
|
||||
end
|
||||
|
||||
# common options
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s D -l debug -d 'Enable debug mode'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s G -l group -d "Group to assign the unix socket specified by -H when running in daemon mode; use '' (the empty string) to disable setting of a group"
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s H -l host -d 'tcp://host:port, unix://path/to/socket, fd://* or fd://socketfd to use in daemon mode. Multiple sockets can be specified'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -l api-enable-cors -d 'Enable CORS headers in the remote API'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s b -l bridge -d "Attach containers to a pre-existing network bridge; use 'none' to disable container networking"
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -l bip -d "Use this CIDR notation address for the network bridge's IP, not compatible with -b"
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s d -l daemon -d 'Enable daemon mode'
|
||||
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 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 '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-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 mtu -d 'Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s p -l pidfile -d 'Path to use for daemon PID file'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s r -l restart -d 'Restart previously running containers'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s s -l storage-driver -d 'Force the docker runtime to use a specific storage driver'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -s v -l version -d 'Print version information and quit'
|
||||
|
||||
# subcommands
|
||||
# attach
|
||||
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 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"
|
||||
|
||||
# build
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a build -d 'Build an image from a Dockerfile'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l force-rm -d 'Always remove intermediate containers, even after unsuccessful builds'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l no-cache -d 'Do not use cache when building the image'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -s q -l quiet -d 'Suppress the verbose output generated by the containers'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l rm -d 'Remove intermediate containers after a successful build'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -s t -l tag -d 'Repository name (and optionally a tag) to be applied to the resulting image in case of success'
|
||||
|
||||
# commit
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a commit -d "Create a new image from a container's changes"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -s a -l author -d 'Author (e.g., "John Hannibal Smith <hannibal@a-team.com>"'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -s m -l message -d 'Commit message'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -l run -d 'Config automatically applied when the image is run. (ex: -run=\'{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}\')'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -a '(__fish_print_docker_containers all)' -d "Container"
|
||||
|
||||
# cp
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a cp -d "Copy files/folders from a container's filesystem to the host path"
|
||||
|
||||
# create
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a run -d 'Run a command in a new container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s P -l publish-all -d 'Publish all exposed ports to the host interfaces'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s a -l attach -d 'Attach to stdin, stdout or stderr.'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s c -l cpu-shares -d 'CPU shares (relative weight)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l cidfile -d 'Write the container ID to the file'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l dns -d 'Set custom dns servers'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s e -l env -d 'Set environment variables'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l entrypoint -d 'Overwrite the default entrypoint of the image'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l expose -d 'Expose a port from the container without publishing it to your host'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s h -l hostname -d 'Container host name'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s i -l interactive -d 'Keep stdin open even if not attached'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l link -d 'Add link to another container (name:alias)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l lxc-conf -d 'Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s m -l memory -d 'Memory limit (format: <number><optional unit>, where unit = b, k, m or g)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s n -l networking -d 'Enable networking for this container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l name -d 'Assign a name to the container'
|
||||
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' -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 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' -l volumes-from -d 'Mount volumes from the specified container(s)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s w -l workdir -d 'Working directory inside the container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -a '(__fish_print_docker_images)' -d "Image"
|
||||
|
||||
|
||||
# diff
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a diff -d "Inspect changes on a container's filesystem"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from diff' -a '(__fish_print_docker_containers all)' -d "Container"
|
||||
|
||||
# events
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a events -d 'Get real time events from the server'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from events' -l since -d 'Show previously created events and then stream.'
|
||||
|
||||
# export
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a export -d 'Stream the contents of a container as a tar archive'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from export' -a '(__fish_print_docker_containers all)' -d "Container"
|
||||
|
||||
# history
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a history -d 'Show the history of an image'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from history' -l no-trunc -d "Don't truncate output"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from history' -s q -l quiet -d 'Only show numeric IDs'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from history' -a '(__fish_print_docker_images)' -d "Image"
|
||||
|
||||
# images
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a images -d 'List images'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s a -l all -d 'Show all images (by default filter out the intermediate image layers)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -l no-trunc -d "Don't truncate output"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s q -l quiet -d 'Only show numeric IDs'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s t -l tree -d 'Output graph in tree format'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s v -l viz -d 'Output graph in graphviz format'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -a '(__fish_print_docker_repositories)' -d "Repository"
|
||||
|
||||
# import
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a import -d 'Create a new filesystem image from the contents of a tarball'
|
||||
|
||||
# info
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a info -d 'Display system-wide information'
|
||||
|
||||
# inspect
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a inspect -d 'Return low-level information on a container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from inspect' -s f -l format -d 'Format the output using the given go template.'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from inspect' -a '(__fish_print_docker_images)' -d "Image"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from inspect' -a '(__fish_print_docker_containers all)' -d "Container"
|
||||
|
||||
# kill
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a kill -d 'Kill a running container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from kill' -s s -l signal -d 'Signal to send to the container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from kill' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
# load
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a load -d 'Load an image from a tar archive'
|
||||
|
||||
# login
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a login -d 'Register or Login to the docker registry server'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s e -l email -d 'Email'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s p -l password -d 'Password'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s u -l username -d 'Username'
|
||||
|
||||
# logs
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a logs -d 'Fetch the logs of a container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -s f -l follow -d 'Follow log output'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
# port
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a port -d 'Lookup the public-facing port which is NAT-ed to PRIVATE_PORT'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from port' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
# ps
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a ps -d 'List containers'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s a -l all -d 'Show all containers. Only running containers are shown by default.'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -l before -d 'Show only container created before Id or Name, include non-running ones.'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -s l -l latest -d 'Show only the latest created container, 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' -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 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.'
|
||||
|
||||
# pull
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a pull -d 'Pull an image or a repository from the docker registry server'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from pull' -s t -l tag -d 'Download tagged image in repository'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from pull' -a '(__fish_print_docker_images)' -d "Image"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from pull' -a '(__fish_print_docker_repositories)' -d "Repository"
|
||||
|
||||
# push
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a push -d 'Push an image or a repository to the docker registry server'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from push' -a '(__fish_print_docker_images)' -d "Image"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from push' -a '(__fish_print_docker_repositories)' -d "Repository"
|
||||
|
||||
# restart
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a restart -d 'Restart a running container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from restart' -s t -l time -d 'Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default=10'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from restart' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
# rm
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a rm -d 'Remove one or more containers'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -s f -l force -d 'Force removal of running container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -s l -l link -d 'Remove the specified link and not the underlying container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -s v -l volumes -d 'Remove the volumes associated to the container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from rm' -a '(__fish_print_docker_containers stopped)' -d "Container"
|
||||
|
||||
# rmi
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a rmi -d 'Remove one or more images'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from rmi' -s f -l force -d 'Force'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from rmi' -a '(__fish_print_docker_images)' -d "Image"
|
||||
|
||||
# run
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a run -d 'Run a command in a new container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s P -l publish-all -d 'Publish all exposed ports to the host interfaces'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s a -l attach -d 'Attach to stdin, stdout or stderr.'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s c -l cpu-shares -d 'CPU shares (relative weight)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l cidfile -d 'Write the container ID to the file'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s d -l detach -d 'Detached mode: Run container in the background, print new container id'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l dns -d 'Set custom dns servers'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s e -l env -d 'Set environment variables'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l entrypoint -d 'Overwrite the default entrypoint of the image'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l expose -d 'Expose a port from the container without publishing it to your host'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s h -l hostname -d 'Container host name'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s i -l interactive -d 'Keep stdin open even if not attached'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l link -d 'Add link to another container (name:alias)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l lxc-conf -d 'Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s m -l memory -d 'Memory limit (format: <number><optional unit>, where unit = b, k, m or g)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s n -l networking -d 'Enable networking for this container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l name -d 'Assign a name to the container'
|
||||
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 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 (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 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' -l volumes-from -d 'Mount volumes from the specified container(s)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s w -l workdir -d 'Working directory inside the container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -a '(__fish_print_docker_images)' -d "Image"
|
||||
|
||||
# save
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a save -d 'Save an image to a tar archive'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from save' -a '(__fish_print_docker_images)' -d "Image"
|
||||
|
||||
# search
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a search -d 'Search for an image in the docker index'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from search' -l no-trunc -d "Don't truncate output"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from search' -s s -l stars -d 'Only displays with at least xxx stars'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from search' -l automated -d 'Only show automated builds'
|
||||
|
||||
# start
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a start -d 'Start a stopped container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from start' -s a -l attach -d "Attach container's stdout/stderr and forward all signals to the process"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from start' -s i -l interactive -d "Attach container's stdin"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from start' -a '(__fish_print_docker_containers stopped)' -d "Container"
|
||||
|
||||
# stop
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a stop -d 'Stop a running container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from stop' -s t -l time -d 'Number of seconds to wait for the container to stop before killing it.'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from stop' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
# tag
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a tag -d 'Tag an image into a repository'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from tag' -s f -l force -d 'Force'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from tag' -a '(__fish_print_docker_images)' -d "Image"
|
||||
|
||||
# top
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a top -d 'Lookup the running processes of a container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from top' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
# version
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a version -d 'Show the docker version information'
|
||||
|
||||
# wait
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a wait -d 'Block until a container stops, then print its exit code'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from wait' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
|
||||
@@ -1,483 +0,0 @@
|
||||
#compdef docker
|
||||
#
|
||||
# zsh completion for docker (http://docker.com)
|
||||
#
|
||||
# version: 0.3.0
|
||||
# github: https://github.com/felixr/docker-zsh-completion
|
||||
#
|
||||
# contributors:
|
||||
# - Felix Riedel
|
||||
# - Vincent Bernat
|
||||
#
|
||||
# license:
|
||||
#
|
||||
# Copyright (c) 2013, Felix Riedel
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the <organization> nor the
|
||||
# names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
|
||||
__docker_get_containers() {
|
||||
local kind expl
|
||||
declare -a running stopped lines args
|
||||
|
||||
kind=$1
|
||||
shift
|
||||
[[ $kind = (stopped|all) ]] && args=($args -a)
|
||||
|
||||
lines=(${(f)"$(_call_program commands docker ps ${args})"})
|
||||
|
||||
# Parse header line to find columns
|
||||
local i=1 j=1 k header=${lines[1]}
|
||||
declare -A begin end
|
||||
while (( $j < ${#header} - 1 )) {
|
||||
i=$(( $j + ${${header[$j,-1]}[(i)[^ ]]} - 1))
|
||||
j=$(( $i + ${${header[$i,-1]}[(i) ]} - 1))
|
||||
k=$(( $j + ${${header[$j,-1]}[(i)[^ ]]} - 2))
|
||||
begin[${header[$i,$(($j-1))]}]=$i
|
||||
end[${header[$i,$(($j-1))]}]=$k
|
||||
}
|
||||
lines=(${lines[2,-1]})
|
||||
|
||||
# Container ID
|
||||
local line
|
||||
local s
|
||||
for line in $lines; do
|
||||
s="${line[${begin[CONTAINER ID]},${end[CONTAINER ID]}]%% ##}"
|
||||
s="$s:${(l:15:: :::)${${line[${begin[CREATED]},${end[CREATED]}]/ ago/}%% ##}}"
|
||||
s="$s, ${${${line[$begin[IMAGE],$end[IMAGE]]}/:/\\:}%% ##}"
|
||||
if [[ ${line[${begin[STATUS]},${end[STATUS]}]} = Exit* ]]; then
|
||||
stopped=($stopped $s)
|
||||
else
|
||||
running=($running $s)
|
||||
fi
|
||||
done
|
||||
|
||||
# Names
|
||||
local name
|
||||
local -a names
|
||||
for line in $lines; do
|
||||
names=(${(ps:,:)${${line[${begin[NAMES]},-1]}%% *}})
|
||||
for name in $names; do
|
||||
s="${name}:${(l:15:: :::)${${line[${begin[CREATED]},${end[CREATED]}]/ ago/}%% ##}}"
|
||||
s="$s, ${${${line[$begin[IMAGE],$end[IMAGE]]}/:/\\:}%% ##}"
|
||||
if [[ ${line[${begin[STATUS]},${end[STATUS]}]} = Exit* ]]; then
|
||||
stopped=($stopped $s)
|
||||
else
|
||||
running=($running $s)
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
[[ $kind = (running|all) ]] && _describe -t containers-running "running containers" running
|
||||
[[ $kind = (stopped|all) ]] && _describe -t containers-stopped "stopped containers" stopped
|
||||
}
|
||||
|
||||
__docker_stoppedcontainers() {
|
||||
__docker_get_containers stopped "$@"
|
||||
}
|
||||
|
||||
__docker_runningcontainers() {
|
||||
__docker_get_containers running "$@"
|
||||
}
|
||||
|
||||
__docker_containers () {
|
||||
__docker_get_containers all "$@"
|
||||
}
|
||||
|
||||
__docker_images () {
|
||||
local expl
|
||||
declare -a images
|
||||
images=(${${${${(f)"$(_call_program commands docker images)"}[2,-1]}/ ##/\\:}%% *})
|
||||
images=(${${images%\\:<none>}#<none>} ${${${(f)"$(_call_program commands docker images)"}[2,-1]}/(#b)([^ ]##) ##([^ ]##) ##([^ ]##)*/${match[3]}:${(r:15:: :::)match[2]} in ${match[1]}})
|
||||
_describe -t docker-images "images" images
|
||||
}
|
||||
|
||||
__docker_tags() {
|
||||
local expl
|
||||
declare -a tags
|
||||
tags=(${${${${${(f)"$(_call_program commands docker images)"}#* }## #}%% *}[2,-1]})
|
||||
_describe -t docker-tags "tags" tags
|
||||
}
|
||||
|
||||
__docker_repositories_with_tags() {
|
||||
if compset -P '*:'; then
|
||||
__docker_tags
|
||||
else
|
||||
__docker_repositories -qS ":"
|
||||
fi
|
||||
}
|
||||
|
||||
__docker_search() {
|
||||
# declare -a dockersearch
|
||||
local cache_policy
|
||||
zstyle -s ":completion:${curcontext}:" cache-policy cache_policy
|
||||
if [[ -z "$cache_policy" ]]; then
|
||||
zstyle ":completion:${curcontext}:" cache-policy __docker_caching_policy
|
||||
fi
|
||||
|
||||
local searchterm cachename
|
||||
searchterm="${words[$CURRENT]%/}"
|
||||
cachename=_docker-search-$searchterm
|
||||
|
||||
local expl
|
||||
local -a result
|
||||
if ( [[ ${(P)+cachename} -eq 0 ]] || _cache_invalid ${cachename#_} ) \
|
||||
&& ! _retrieve_cache ${cachename#_}; then
|
||||
_message "Searching for ${searchterm}..."
|
||||
result=(${${${(f)"$(_call_program commands docker search ${searchterm})"}%% *}[2,-1]})
|
||||
_store_cache ${cachename#_} result
|
||||
fi
|
||||
_wanted dockersearch expl 'available images' compadd -a result
|
||||
}
|
||||
|
||||
__docker_caching_policy()
|
||||
{
|
||||
oldp=( "$1"(Nmh+1) ) # 1 hour
|
||||
(( $#oldp ))
|
||||
}
|
||||
|
||||
|
||||
__docker_repositories () {
|
||||
local expl
|
||||
declare -a repos
|
||||
repos=(${${${(f)"$(_call_program commands docker images)"}%% *}[2,-1]})
|
||||
_describe -t docker-repos "repositories" repos "$@"
|
||||
}
|
||||
|
||||
__docker_commands () {
|
||||
# local -a _docker_subcommands
|
||||
local cache_policy
|
||||
|
||||
zstyle -s ":completion:${curcontext}:" cache-policy cache_policy
|
||||
if [[ -z "$cache_policy" ]]; then
|
||||
zstyle ":completion:${curcontext}:" cache-policy __docker_caching_policy
|
||||
fi
|
||||
|
||||
if ( [[ ${+_docker_subcommands} -eq 0 ]] || _cache_invalid docker_subcommands) \
|
||||
&& ! _retrieve_cache docker_subcommands;
|
||||
then
|
||||
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')
|
||||
_store_cache docker_subcommands _docker_subcommands
|
||||
fi
|
||||
_describe -t docker-commands "docker command" _docker_subcommands
|
||||
}
|
||||
|
||||
__docker_subcommand () {
|
||||
local -a _command_args
|
||||
case "$words[1]" in
|
||||
(attach)
|
||||
_arguments \
|
||||
'--no-stdin[Do not attach stdin]' \
|
||||
'--sig-proxy[Proxy all received signals to the process (non-TTY mode only)]' \
|
||||
':containers:__docker_runningcontainers'
|
||||
;;
|
||||
(build)
|
||||
_arguments \
|
||||
'--force-rm[Always remove intermediate containers]' \
|
||||
'--no-cache[Do not use cache when building the image]' \
|
||||
{-q,--quiet}'[Suppress verbose build output]' \
|
||||
'--rm[Remove intermediate containers after a successful build]' \
|
||||
{-t,--tag=-}'[Repository, name and tag to be applied]:repository:__docker_repositories_with_tags' \
|
||||
':path or URL:_directories'
|
||||
;;
|
||||
(commit)
|
||||
_arguments \
|
||||
{-a,--author=-}'[Author]:author: ' \
|
||||
{-m,--message=-}'[Commit message]:message: ' \
|
||||
{-p,--pause}'[Pause container during commit]' \
|
||||
'--run=-[Configuration automatically applied when the image is run]:configuration: ' \
|
||||
':container:__docker_containers' \
|
||||
':repository:__docker_repositories_with_tags'
|
||||
;;
|
||||
(cp)
|
||||
_arguments \
|
||||
':container:->container' \
|
||||
':hostpath:_files'
|
||||
case $state in
|
||||
(container)
|
||||
if compset -P '*:'; then
|
||||
_files
|
||||
else
|
||||
__docker_containers -qS ":"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
(diff|export)
|
||||
_arguments '*:containers:__docker_containers'
|
||||
;;
|
||||
(events)
|
||||
_arguments \
|
||||
'--since=-[Events created since this timestamp]:timestamp: ' \
|
||||
'--until=-[Events created until this timestamp]:timestamp: '
|
||||
;;
|
||||
(exec)
|
||||
_arguments \
|
||||
{-d,--detach}'[Detached mode: leave the container running in the background]' \
|
||||
{-i,--interactive}'[Keep stdin open even if not attached]' \
|
||||
{-t,--tty}'[Allocate a pseudo-tty]' \
|
||||
':containers:__docker_runningcontainers'
|
||||
;;
|
||||
(history)
|
||||
_arguments \
|
||||
'--no-trunc[Do not truncate output]' \
|
||||
{-q,--quiet}'[Only show numeric IDs]' \
|
||||
'*:images:__docker_images'
|
||||
;;
|
||||
(images)
|
||||
_arguments \
|
||||
{-a,--all}'[Show all images]' \
|
||||
'*'{-f,--filter=-}'[Filter values]:filter: ' \
|
||||
'--no-trunc[Do not truncate output]' \
|
||||
{-q,--quiet}'[Only show numeric IDs]' \
|
||||
'--tree[Output graph in tree format]' \
|
||||
'--viz[Output graph in graphviz format]' \
|
||||
':repository:__docker_repositories'
|
||||
;;
|
||||
(inspect)
|
||||
_arguments \
|
||||
{-f,--format=-}'[Format the output using the given go template]:template: ' \
|
||||
'*:containers:__docker_containers'
|
||||
;;
|
||||
(import)
|
||||
_arguments \
|
||||
':URL:(- http:// file://)' \
|
||||
':repository:__docker_repositories_with_tags'
|
||||
;;
|
||||
(info)
|
||||
;;
|
||||
(import)
|
||||
_arguments \
|
||||
':URL:(- http:// file://)' \
|
||||
':repository:__docker_repositories_with_tags'
|
||||
;;
|
||||
(insert)
|
||||
_arguments '1:containers:__docker_containers' \
|
||||
'2:URL:(http:// file://)' \
|
||||
'3:file:_files'
|
||||
;;
|
||||
(kill)
|
||||
_arguments \
|
||||
{-s,--signal=-}'[Signal to send]:signal:_signals' \
|
||||
'*:containers:__docker_runningcontainers'
|
||||
;;
|
||||
(load)
|
||||
_arguments \
|
||||
{-i,--input=-}'[Read from tar archive file]:tar:_files'
|
||||
;;
|
||||
(login)
|
||||
_arguments \
|
||||
{-e,--email=-}'[Email]:email: ' \
|
||||
{-p,--password=-}'[Password]:password: ' \
|
||||
{-u,--user=-}'[Username]:username: ' \
|
||||
':server: '
|
||||
;;
|
||||
(logout)
|
||||
_arguments \
|
||||
':server: '
|
||||
;;
|
||||
(logs)
|
||||
_arguments \
|
||||
{-f,--follow}'[Follow log output]' \
|
||||
{-t,--timestamps}'[Show timestamps]' \
|
||||
'*:containers:__docker_containers'
|
||||
;;
|
||||
(port)
|
||||
_arguments \
|
||||
'1:containers:__docker_runningcontainers' \
|
||||
'2:port:_ports'
|
||||
;;
|
||||
(pause|unpause)
|
||||
_arguments \
|
||||
'1:containers:__docker_runningcontainers'
|
||||
;;
|
||||
(start)
|
||||
_arguments \
|
||||
{-a,--attach}'[Attach container'"'"'s stdout/stderr and forward all signals]' \
|
||||
{-i,--interactive}'[Attach container'"'"'s stding]' \
|
||||
'*:containers:__docker_stoppedcontainers'
|
||||
;;
|
||||
(rm)
|
||||
_arguments \
|
||||
{-f,--force}'[Force removal]' \
|
||||
{-l,--link}'[Remove the specified link and not the underlying container]' \
|
||||
{-v,--volumes}'[Remove the volumes associated to the container]' \
|
||||
'*:containers:__docker_stoppedcontainers'
|
||||
;;
|
||||
(rmi)
|
||||
_arguments \
|
||||
{-f,--force}'[Force removal]' \
|
||||
'--no-prune[Do not delete untagged parents]' \
|
||||
'*:images:__docker_images'
|
||||
;;
|
||||
(restart|stop)
|
||||
_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'
|
||||
;;
|
||||
(top)
|
||||
_arguments \
|
||||
'1:containers:__docker_runningcontainers' \
|
||||
'(-)*:: :->ps-arguments'
|
||||
case $state in
|
||||
(ps-arguments)
|
||||
_ps
|
||||
;;
|
||||
esac
|
||||
|
||||
;;
|
||||
(ps)
|
||||
_arguments \
|
||||
{-a,--all}'[Show all containers]' \
|
||||
'--before=-[Show only container created before...]:containers:__docker_containers' \
|
||||
'*'{-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)' \
|
||||
'--no-trunc[Do not truncate output]' \
|
||||
{-q,--quiet}'[Only show numeric IDs]' \
|
||||
{-s,--size}'[Display total file sizes]' \
|
||||
'--since=-[Show only containers created since...]:containers:__docker_containers'
|
||||
;;
|
||||
(tag)
|
||||
_arguments \
|
||||
{-f,--force}'[force]'\
|
||||
':image:__docker_images'\
|
||||
':repository:__docker_repositories_with_tags'
|
||||
;;
|
||||
(create|run)
|
||||
_arguments \
|
||||
{-a,--attach}'[Attach to stdin, stdout or stderr]' \
|
||||
'*--add-host=-[Add a custom host-to-IP mapping]:host\:ip mapping: ' \
|
||||
{-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' \
|
||||
'--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-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: ' \
|
||||
'*--env-file=-[Read environment variables from a file]:environment file:_files' \
|
||||
'*--expose=-[Expose a port from the container without publishing it]: ' \
|
||||
{-h,--hostname=-}'[Container host name]:hostname:_hosts' \
|
||||
{-i,--interactive}'[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: ' \
|
||||
'--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]' \
|
||||
'--restart=-[Restart policy]:restart policy:(no on-failure always)' \
|
||||
'--rm[Remove intermediate containers when it exits]' \
|
||||
'*--security-opt=-[Security options]:security option: ' \
|
||||
'--sig-proxy[Proxy all received signals to the process (non-TTY mode only)]' \
|
||||
{-t,--tty}'[Allocate a pseudo-tty]' \
|
||||
{-u,--user=-}'[Username or UID]:user:_users' \
|
||||
'*-v[Bind mount a volume]:volume: '\
|
||||
'*--volumes-from=-[Mount volumes from the specified container]:volume: ' \
|
||||
{-w,--workdir=-}'[Working directory inside the container]:directory:_directories' \
|
||||
'(-):images:__docker_images' \
|
||||
'(-):command: _command_names -e' \
|
||||
'*::arguments: _normal'
|
||||
|
||||
case $state in
|
||||
(link)
|
||||
if compset -P '*:'; then
|
||||
_wanted alias expl 'Alias' compadd -E ""
|
||||
else
|
||||
__docker_runningcontainers -qS ":"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
;;
|
||||
(pull|search)
|
||||
_arguments ':name:__docker_search'
|
||||
;;
|
||||
(push)
|
||||
_arguments ':images:__docker_images'
|
||||
;;
|
||||
(save)
|
||||
_arguments \
|
||||
{-o,--output=-}'[Write to file]:file:_files' \
|
||||
':images:__docker_images'
|
||||
;;
|
||||
(wait)
|
||||
_arguments ':containers:__docker_runningcontainers'
|
||||
;;
|
||||
(help)
|
||||
_arguments ':subcommand:__docker_commands'
|
||||
;;
|
||||
(*)
|
||||
_message 'Unknown sub command'
|
||||
esac
|
||||
|
||||
}
|
||||
|
||||
_docker () {
|
||||
# Support for subservices, which allows for `compdef _docker docker-shell=_docker_containers`.
|
||||
# Based on /usr/share/zsh/functions/Completion/Unix/_git without support for `ret`.
|
||||
if [[ $service != docker ]]; then
|
||||
_call_function - _$service
|
||||
return
|
||||
fi
|
||||
|
||||
local curcontext="$curcontext" state line
|
||||
typeset -A opt_args
|
||||
|
||||
_arguments -C \
|
||||
'-H[tcp://host:port to bind/connect to]:socket: ' \
|
||||
'(-): :->command' \
|
||||
'(-)*:: :->option-or-argument'
|
||||
|
||||
if (( CURRENT == 1 )); then
|
||||
|
||||
fi
|
||||
case $state in
|
||||
(command)
|
||||
__docker_commands
|
||||
;;
|
||||
(option-or-argument)
|
||||
curcontext=${curcontext%:*:*}:docker-$words[1]:
|
||||
__docker_subcommand
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker "$@"
|
||||
|
||||
# Local Variables:
|
||||
# mode: Shell-Script
|
||||
# sh-indentation: 4
|
||||
# indent-tabs-mode: nil
|
||||
# sh-basic-offset: 4
|
||||
# End:
|
||||
# vim: ft=zsh sw=4 ts=4 et
|
||||
@@ -1,11 +0,0 @@
|
||||
Desktop Integration
|
||||
===================
|
||||
|
||||
The ./contrib/desktop-integration contains examples of typical dockerized
|
||||
desktop applications.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
* Chromium: ./chromium/Dockerfile shows a way to dockerize a common application
|
||||
* Gparted: ./gparted/Dockerfile shows a way to dockerize a common application w devices
|
||||
@@ -1,36 +0,0 @@
|
||||
# VERSION: 0.1
|
||||
# DESCRIPTION: Create chromium container with its dependencies
|
||||
# AUTHOR: Jessica Frazelle <jess@docker.com>
|
||||
# COMMENTS:
|
||||
# This file describes how to build a Chromium container with all
|
||||
# dependencies installed. It uses native X11 unix socket.
|
||||
# Tested on Debian Jessie
|
||||
# USAGE:
|
||||
# # Download Chromium Dockerfile
|
||||
# wget http://raw.githubusercontent.com/docker/docker/master/contrib/desktop-integration/chromium/Dockerfile
|
||||
#
|
||||
# # Build chromium image
|
||||
# docker build -t chromium .
|
||||
#
|
||||
# # Run stateful data-on-host chromium. For ephemeral, remove -v /data/chromium:/data
|
||||
# docker run -v /data/chromium:/data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# -e DISPLAY=unix$DISPLAY chromium
|
||||
|
||||
# # To run stateful dockerized data containers
|
||||
# docker run --volumes-from chromium-data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# -e DISPLAY=unix$DISPLAY chromium
|
||||
|
||||
# Base docker image
|
||||
FROM debian:jessie
|
||||
MAINTAINER Jessica Frazelle <jess@docker.com>
|
||||
|
||||
# Install Chromium
|
||||
RUN apt-get update && apt-get install -y \
|
||||
chromium \
|
||||
chromium-l10n \
|
||||
libcanberra-gtk-module \
|
||||
libexif-dev \
|
||||
--no-install-recommends
|
||||
|
||||
# Autorun chromium
|
||||
CMD ["/usr/bin/chromium", "--no-sandbox", "--user-data-dir=/data"]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user