mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-21 13:18:00 +00:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 867b2a90c2 | |||
| 3600720a36 | |||
| 8e681c34af | |||
| b60e316b67 | |||
| a2fc45e627 | |||
| 3f47f23fec | |||
| e5974ae0ff | |||
| ce002bf8da | |||
| 9cad4c2a1b | |||
| 6056cce265 | |||
| cec512ccbb | |||
| d1e027291b | |||
| fda2934b6d | |||
| 6fe06c6cc8 | |||
| 63596f8c5d | |||
| 3c9e23c297 | |||
| 6908e0ba1e | |||
| 816d55342d | |||
| 2463be4dea | |||
| 9e98ba72e1 | |||
| d1f7ebda16 | |||
| 899f06bf79 | |||
| f020f1d31e | |||
| b9a0e886fc | |||
| bcab23bbdc | |||
| 7c35bfd78e | |||
| 78d66068a5 | |||
| ab5b715964 | |||
| d8448f27c6 | |||
| 463cf28788 | |||
| 6ce9daea0e | |||
| 39dbaa08dc | |||
| d70e20cddd | |||
| 2741b82cf3 | |||
| 70ec7c6355 | |||
| 0225a37951 | |||
| 4bb1950089 |
@@ -1,2 +0,0 @@
|
||||
bundles
|
||||
.gopath
|
||||
@@ -23,7 +23,3 @@ bundles/
|
||||
vendor/pkg/
|
||||
pyenv
|
||||
Vagrantfile
|
||||
docs/AWS_S3_BUCKET
|
||||
docs/GIT_BRANCH
|
||||
docs/VERSION
|
||||
docs/GITCOMMIT
|
||||
|
||||
@@ -6,16 +6,14 @@ 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>
|
||||
<sridharr@activestate.com> <github@srid.name>
|
||||
Thatcher Peskens <thatcher@dotcloud.com> dhrp <thatcher@dotcloud.com>
|
||||
Thatcher Peskens <thatcher@dotcloud.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>
|
||||
Joffrey F <joffrey@dotcloud.com>
|
||||
<joffrey@dotcloud.com> <f.joffrey@gmail.com>
|
||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||
Andy Smith <github@anarkystic.com>
|
||||
<kalessin@kalessin.fr> <louis@dotcloud.com>
|
||||
@@ -25,6 +23,7 @@ Andy Smith <github@anarkystic.com>
|
||||
<victor.vieux@docker.com> <victor@docker.com>
|
||||
<victor.vieux@docker.com> <vieux@docker.com>
|
||||
<dominik@honnef.co> <dominikh@fork-bomb.org>
|
||||
Thatcher Peskens <thatcher@dotcloud.com>
|
||||
<ehanchrow@ine.com> <eric.hanchrow@gmail.com>
|
||||
Walter Stanish <walter@pratyeka.org>
|
||||
<daniel@gasienica.ch> <dgasienica@zynga.com>
|
||||
@@ -55,26 +54,7 @@ Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||
<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>
|
||||
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¨>
|
||||
<solomon@dotcloud.com> <solomon.hykes@dotcloud.com>
|
||||
<SvenDowideit@home.org.au> <SvenDowideit@fosiki.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> ¨Sven <¨SvenDowideit@home.org.au¨>
|
||||
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>
|
||||
|
||||
+14
-2
@@ -10,9 +10,21 @@ install: true
|
||||
|
||||
before_script:
|
||||
- env | sort
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq python-yaml
|
||||
- git remote add upstream git://github.com/dotcloud/docker.git
|
||||
- upstream=master;
|
||||
if [ "$TRAVIS_PULL_REQUEST" != false ]; then
|
||||
upstream=$TRAVIS_BRANCH;
|
||||
fi;
|
||||
git fetch --append --no-tags upstream refs/heads/$upstream:refs/remotes/upstream/$upstream
|
||||
# sometimes we have upstream master already as origin/master (PRs), but other times we don't, so let's just make sure we have a completely unambiguous way to specify "upstream master" from here out
|
||||
# but if it's a PR against non-master, we need that upstream branch instead :)
|
||||
- sudo pip install -r docs/requirements.txt
|
||||
|
||||
script:
|
||||
- hack/make.sh validate-dco
|
||||
- hack/make.sh validate-gofmt
|
||||
- hack/travis/dco.py
|
||||
- hack/travis/gofmt.py
|
||||
- make -sC docs SPHINXOPTS=-q docs man
|
||||
|
||||
# vim:set sw=2 ts=2:
|
||||
|
||||
@@ -1,78 +1,54 @@
|
||||
# This file lists all individuals having contributed content to the repository.
|
||||
# For how it is generated, see `.mailmap`.
|
||||
|
||||
# If you're submitting a patch, please add your name here in alphabetical order as part of the patch.
|
||||
#
|
||||
# For a list of active project maintainers, see the MAINTAINERS file.
|
||||
#
|
||||
Aanand Prasad <aanand.prasad@gmail.com>
|
||||
Aaron Feng <aaron.feng@gmail.com>
|
||||
Aaron Huslage <huslage@gmail.com>
|
||||
Abel Muiño <amuino@gmail.com>
|
||||
Adam Miller <admiller@redhat.com>
|
||||
Adam Singer <financeCoding@gmail.com>
|
||||
Aditya <aditya@netroy.in>
|
||||
Adrian Mouat <adrian.mouat@gmail.com>
|
||||
alambike <alambike@gmail.com>
|
||||
Aleksa Sarai <cyphar@cyphar.com>
|
||||
Alexander Larsson <alexl@redhat.com>
|
||||
Alexandr Morozov <lk4d4math@gmail.com>
|
||||
Alexey Kotlyarov <alexey@infoxchange.net.au>
|
||||
Alexey Shamrin <shamrin@gmail.com>
|
||||
Alex Gaynor <alex.gaynor@gmail.com>
|
||||
Alexis THOMAS <fr.alexisthomas@gmail.com>
|
||||
almoehi <almoehi@users.noreply.github.com>
|
||||
Al Tobey <al@ooyala.com>
|
||||
amangoel <amangoel@gmail.com>
|
||||
Andrea Luzzardi <aluzzardi@gmail.com>
|
||||
Andreas Savvides <andreas@editd.com>
|
||||
Andreas Tiefenthaler <at@an-ti.eu>
|
||||
Andrea Turli <andrea.turli@gmail.com>
|
||||
Andrew Duckworth <grillopress@gmail.com>
|
||||
Andrew Macgregor <andrew.macgregor@agworld.com.au>
|
||||
Andrew Munsell <andrew@wizardapps.net>
|
||||
Andrews Medina <andrewsmedina@gmail.com>
|
||||
Andrew Williams <williams.andrew@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@metaliveblog.com>
|
||||
Andy Smith <github@anarkystic.com>
|
||||
Anthony Bishopric <git@anthonybishopric.com>
|
||||
Anton Nikitin <anton.k.nikitin@gmail.com>
|
||||
Antony Messerli <amesserl@rackspace.com>
|
||||
apocas <petermdias@gmail.com>
|
||||
Arnaud Porterie <icecrime@gmail.com>
|
||||
Asbjørn Enge <asbjorn@hanafjedle.net>
|
||||
Barnaby Gray <barnaby@pickle.me.uk>
|
||||
Barry Allard <barry.allard@gmail.com>
|
||||
Bartłomiej Piotrowski <b@bpiotrowski.pl>
|
||||
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>
|
||||
Bhiraj Butala <abhiraj.butala@gmail.com>
|
||||
bin liu <liubin0329@users.noreply.github.com>
|
||||
Bouke Haarsma <bouke@webatoom.nl>
|
||||
Brandon Liu <bdon@bdon.org>
|
||||
Brandon Philips <brandon@ifup.org>
|
||||
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>
|
||||
Briehan Lombaard <briehan.lombaard@gmail.com>
|
||||
Bruno Bigras <bigras.bruno@gmail.com>
|
||||
Bryan Matsuo <bryan.matsuo@gmail.com>
|
||||
Bryan Murphy <bmurphy1976@gmail.com>
|
||||
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>
|
||||
Chia-liang Kao <clkao@clkao.org>
|
||||
Chris St. Pierre <chris.a.st.pierre@gmail.com>
|
||||
Christopher Currie <codemonkey+github@gmail.com>
|
||||
@@ -83,7 +59,6 @@ Colin Dunklau <colin.dunklau@gmail.com>
|
||||
Colin Rice <colin@daedrum.net>
|
||||
Cory Forsyth <cory.forsyth@gmail.com>
|
||||
cressie176 <github@stephen-cresswell.net>
|
||||
Dafydd Crosby <dtcrsby@gmail.com>
|
||||
Dan Buch <d.buch@modcloth.com>
|
||||
Dan Hirsch <thequux@upstandinghackers.com>
|
||||
Daniel Exner <dex@dragonslave.de>
|
||||
@@ -95,45 +70,30 @@ 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 Gageot <david@gageot.net>
|
||||
David Mcanulty <github@hellspark.com>
|
||||
David Röthlisberger <david@rothlis.net>
|
||||
David Sissitka <me@dsissitka.com>
|
||||
Deni Bertovic <deni@kset.org>
|
||||
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>
|
||||
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>
|
||||
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>
|
||||
Erik Hollensbe <erik+github@hollensbe.org>
|
||||
Erno Hopearuoho <erno.hopearuoho@gmail.com>
|
||||
eugenkrizo <eugen.krizo@gmail.com>
|
||||
Evan Hazlett <ejhazlett@gmail.com>
|
||||
Evan Krall <krall@yelp.com>
|
||||
Evan Phoenix <evan@fallingsnow.net>
|
||||
Evan Wies <evan@neomantra.net>
|
||||
@@ -144,7 +104,6 @@ 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>
|
||||
Flavio Castelli <fcastelli@suse.com>
|
||||
Francisco Souza <f@souza.cc>
|
||||
@@ -156,11 +115,8 @@ Gabe Rosenhouse <gabe@missionst.com>
|
||||
Gabriel Monroy <gabriel@opdemand.com>
|
||||
Galen Sampson <galen.sampson@gmail.com>
|
||||
Gareth Rushgrove <gareth@morethanseven.net>
|
||||
Geoffrey Bachelet <grosfrais@gmail.com>
|
||||
Gereon Frey <gereon.frey@dynport.de>
|
||||
German DZ <germ@ndz.com.ar>
|
||||
Gert van Valkenhoef <g.h.m.van.valkenhoef@rug.nl>
|
||||
Goffert van Gool <goffert@phusion.nl>
|
||||
Graydon Hoare <graydon@pobox.com>
|
||||
Greg Thornton <xdissent@me.com>
|
||||
grunny <mwgrunny@gmail.com>
|
||||
@@ -169,40 +125,28 @@ Gurjeet Singh <gurjeet@singh.im>
|
||||
Guruprasad <lgp171188@gmail.com>
|
||||
Harley Laue <losinggeneration@gmail.com>
|
||||
Hector Castro <hectcastro@gmail.com>
|
||||
Hobofan <goisser94@gmail.com>
|
||||
Hunter Blanks <hunter@twilio.com>
|
||||
Ian Truslove <ian.truslove@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>
|
||||
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 Mills <prologic@shortcircuit.net.au>
|
||||
James Turnbull <james@lovedthanlost.net>
|
||||
jaseg <jaseg@jaseg.net>
|
||||
Jason McVetta <jason.mcvetta@gmail.com>
|
||||
Jason Plum <jplum@devonit.com>
|
||||
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
||||
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||
Jeff Lindsay <progrium@gmail.com>
|
||||
Jeremy Grosser <jeremy@synack.me>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
|
||||
Jesse Dubay <jesse@thefortytwo.net>
|
||||
Jilles Oldenbeuving <ojilles@gmail.com>
|
||||
Jim Alateras <jima@comware.com.au>
|
||||
Jimmy Cuadra <jimmy@jimmycuadra.com>
|
||||
Joe Beda <joe.github@bedafamily.com>
|
||||
Joel Handwell <joelhandwell@gmail.com>
|
||||
Joe Shaw <joe@joeshaw.org>
|
||||
Joe Van Dyk <joe@tanga.com>
|
||||
Joffrey F <joffrey@docker.com>
|
||||
Joffrey F <joffrey@dotcloud.com>
|
||||
Johan Euphrosine <proppy@google.com>
|
||||
Johannes 'fish' Ziemke <github@freigeist.org>
|
||||
Johan Rydberg <johan.rydberg@gmail.com>
|
||||
@@ -211,9 +155,7 @@ John Feminella <jxf@jxf.me>
|
||||
John Gardiner Myers <jgmyers@proofpoint.com>
|
||||
John Warwick <jwarwick@gmail.com>
|
||||
Jonas Pfenniger <jonas@pfenniger.name>
|
||||
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>
|
||||
@@ -228,18 +170,13 @@ Julien Barbier <write0@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>
|
||||
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 Wallace <kevin@pentabarf.net>
|
||||
Keyvan Fatehi <keyvanfatehi@gmail.com>
|
||||
kim0 <email.ahmedkamal@googlemail.com>
|
||||
Kim BKC Carlbacker <kim.carlbacker@gmail.com>
|
||||
@@ -247,20 +184,14 @@ Kimbro Staken <kstaken@kstaken.com>
|
||||
Kiran Gangadharan <kiran.daredevil@gmail.com>
|
||||
Konstantin Pelykh <kpelykh@zettaset.com>
|
||||
Kyle Conroy <kyle.j.conroy@gmail.com>
|
||||
lalyos <lalyos@yahoo.com>
|
||||
Lance Chen <cyen0312@gmail.com>
|
||||
Lars R. Damerow <lars@pixar.com>
|
||||
Laurie Voss <github@seldo.com>
|
||||
Lewis Peckover <lew+github@lew.io>
|
||||
Liang-Chi Hsieh <viirya@gmail.com>
|
||||
Lokesh Mandvekar <lsm5@redhat.com>
|
||||
Louis Opter <kalessin@kalessin.fr>
|
||||
lukaspustina <lukas.pustina@centerdevice.com>
|
||||
lukemarsden <luke@digital-crocus.com>
|
||||
Mahesh Tiyyagura <tmahesh@gmail.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>
|
||||
Marcus Farkas <toothlessgear@finitebox.com>
|
||||
@@ -272,32 +203,23 @@ Marko Mikulicic <mmikulicic@gmail.com>
|
||||
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>
|
||||
Mathieu Le Marec - Pasquet <kiorky@cryptelium.net>
|
||||
Matt Apperson <me@mattapperson.com>
|
||||
Matt Bachmann <bachmann.matt@gmail.com>
|
||||
Matt Haggard <haggardii@gmail.com>
|
||||
Matthew Mueller <mattmuelle@gmail.com>
|
||||
Matthias Klumpp <matthias@tenstral.net>
|
||||
Matthias Kühnle <git.nivoc@neverbox.com>
|
||||
mattymo <raytrac3r@gmail.com>
|
||||
Maxime Petazzoni <max@signalfuse.com>
|
||||
Maxim Treskin <zerthurd@gmail.com>
|
||||
Max Shytikov <mshytikov@gmail.com>
|
||||
meejah <meejah@meejah.ca>
|
||||
Michael Brown <michael@netdirect.ca>
|
||||
Michael Crosby <michael@crosbymichael.com>
|
||||
Michael Gorsuch <gorsuch@github.com>
|
||||
Michael Neale <michael.neale@gmail.com>
|
||||
Michael Stapelberg <michael+gh@stapelberg.de>
|
||||
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
||||
Mike Gaffney <mike@uberu.com>
|
||||
Mike MacCana <mike.maccana@gmail.com>
|
||||
Mike Naberezny <mike@naberezny.com>
|
||||
Mikhail Sobolev <mss@mawhrin.net>
|
||||
Mohit Soni <mosoni@ebay.com>
|
||||
Morgante Pell <morgante.pell@morgante.net>
|
||||
Morten Siebuhr <sbhr@sbhr.dk>
|
||||
Nan Monnand Deng <monnand@gmail.com>
|
||||
Nate Jones <nate@endot.org>
|
||||
@@ -309,26 +231,22 @@ Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
|
||||
Nick Stinemates <nick@stinemates.org>
|
||||
Nicolas Dudebout <nicolas.dudebout@gatech.edu>
|
||||
Nicolas Kaiser <nikai@nikai.net>
|
||||
noducks <onemannoducks@gmail.com>
|
||||
Nolan Darilek <nolan@thewordnerd.info>
|
||||
odk- <github@odkurzacz.org>
|
||||
Oguz Bilgic <fisyonet@gmail.com>
|
||||
Ole Reifschneider <mail@ole-reifschneider.de>
|
||||
O.S. Tezer <ostezer@gmail.com>
|
||||
O.S.Tezer <ostezer@gmail.com>
|
||||
pandrew <letters@paulnotcom.se>
|
||||
Pascal Borreli <pascal@borreli.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>
|
||||
Peter Braden <peterbraden@peterbraden.co.uk>
|
||||
Peter Waller <peter@scraperwiki.com>
|
||||
Phillip Alexander <git@phillipalexander.io>
|
||||
Phil Spitler <pspitler@gmail.com>
|
||||
Piergiuliano Bossi <pgbossi@gmail.com>
|
||||
Pierre-Alain RIVIERE <pariviere@ippon.fr>
|
||||
@@ -336,8 +254,6 @@ Piotr Bogdan <ppbogdan@gmail.com>
|
||||
pysqz <randomq@126.com>
|
||||
Quentin Brossard <qbrossard@gmail.com>
|
||||
Rafal Jeczalik <rjeczalik@gmail.com>
|
||||
Rajat Pandit <rp@rajatpandit.com>
|
||||
Ralph Bean <rbean@redhat.com>
|
||||
Ramkumar Ramachandra <artagnon@gmail.com>
|
||||
Ramon van Alteren <ramon@vanalteren.nl>
|
||||
Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
|
||||
@@ -347,71 +263,54 @@ Richo Healey <richo@psych0tik.net>
|
||||
Rick Bradley <rick@users.noreply.github.com>
|
||||
Robert Obryk <robryk@gmail.com>
|
||||
Roberto G. Hashioka <roberto.hashioka@docker.com>
|
||||
robpc <rpcann@gmail.com>
|
||||
Roberto Hashioka <roberto_hashioka@hotmail.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 Moriz <rmoriz@users.noreply.github.com>
|
||||
Rovanion Luckey <rovanion.luckey@gmail.com>
|
||||
Ryan Aslett <github@mixologic.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 J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
|
||||
Sam Rijs <srijs@airpost.net>
|
||||
Samuel Andaya <samuel@andaya.net>
|
||||
Scott Bessler <scottbessler@gmail.com>
|
||||
Scott Collier <emailscottcollier@gmail.com>
|
||||
Sean Cronin <seancron@gmail.com>
|
||||
Sean P. Kane <skane@newrelic.com>
|
||||
Sébastien Stormacq <sebsto@users.noreply.github.com>
|
||||
Shawn Landden <shawn@churchofgit.com>
|
||||
Shawn Siefkas <shawn.siefkas@meredith.com>
|
||||
Shih-Yuan Lee <fourdollars@gmail.com>
|
||||
shin- <joffrey@docker.com>
|
||||
Silas Sewell <silas@sewell.org>
|
||||
Simon Taranto <simon.taranto@gmail.com>
|
||||
Sindhu S <sindhus@live.in>
|
||||
Sjoerd Langkemper <sjoerd-github@linuxonly.nl>
|
||||
Solomon Hykes <solomon@docker.com>
|
||||
Solomon Hykes <solomon@dotcloud.com>
|
||||
Song Gao <song@gao.io>
|
||||
Soulou <leo@unbekandt.eu>
|
||||
Sridatta Thatipamala <sthatipamala@gmail.com>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||
Steeve Morin <steeve.morin@gmail.com>
|
||||
Stefan Praszalowicz <stefan@greplin.com>
|
||||
Steven Burgess <steven.a.burgess@hotmail.com>
|
||||
sudosurootdev <sudosurootdev@gmail.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au>
|
||||
Sven Dowideit <svendowideit@home.org.au>
|
||||
Sylvain Bellemare <sylvain.bellemare@ezeep.com>
|
||||
tang0th <tang0th@gmx.com>
|
||||
Tatsuki Sugiura <sugi@nemui.org>
|
||||
Tehmasp Chaudhri <tehmasp@gmail.com>
|
||||
Thatcher Peskens <thatcher@docker.com>
|
||||
Thatcher Peskens <thatcher@dotcloud.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 Schroeter <thomas@cliqz.com>
|
||||
Tianon Gravi <admwiggin@gmail.com>
|
||||
Tibor Vass <teabee89@gmail.com>
|
||||
Tim Bosse <taim@bosboot.org>
|
||||
Timothy Hobbs <timothyhobbs@seznam.cz>
|
||||
Tim Ruffles <oi@truffles.me.uk>
|
||||
Tim Bosse <maztaim@users.noreply.github.com>
|
||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||
tjmehta <tj@init.me>
|
||||
Tobias Bieniek <Tobias.Bieniek@gmx.de>
|
||||
Tobias Schmidt <ts@soundcloud.com>
|
||||
Tobias Schwab <tobias.schwab@dynport.de>
|
||||
Todd Lunter <tlunter@gmail.com>
|
||||
Tom Fotherby <tom+github@peopleperhour.com>
|
||||
Tom Hulihan <hulihan.tom159@gmail.com>
|
||||
Tommaso Visconti <tommaso.visconti@gmail.com>
|
||||
Tony Daws <tony@daws.ca>
|
||||
Travis Cline <travis.cline@gmail.com>
|
||||
Tyler Brock <tyler.brock@gmail.com>
|
||||
Tzu-Jung Lee <roylee17@gmail.com>
|
||||
@@ -420,35 +319,26 @@ 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 Mayers <vincent.mayers@inbloom.org>
|
||||
Vincent Woo <me@vincentwoo.com>
|
||||
Vinod Kulkarni <vinod.kulkarni@gmail.com>
|
||||
Vishnu Kannan <vishnuk@google.com>
|
||||
Vitor Monteiro <vmrmonteiro@gmail.com>
|
||||
Vivek Agarwal <me@vivek.im>
|
||||
Vladimir Bulyga <xx@ccxx.cc>
|
||||
Vladimir Kirillov <proger@wilab.org.ua>
|
||||
Vladimir Rutsky <altsysrq@gmail.com>
|
||||
Walter Leibbrandt <github@wrl.co.za>
|
||||
Vladimir Rutsky <iamironbob@gmail.com>
|
||||
Walter Stanish <walter@pratyeka.org>
|
||||
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>
|
||||
Will Rouesnel <w.rouesnel@gmail.com>
|
||||
Will Weaver <monkey@buildingbananas.com>
|
||||
Xiuming Chen <cc@cxm.cc>
|
||||
Yang Bai <hamo.by@gmail.com>
|
||||
Yasunori Mahata <nori@mahata.net>
|
||||
Yurii Rashkovskii <yrashk@gmail.com>
|
||||
Zain Memon <zain@inzain.net>
|
||||
Zaiste! <oh@zaiste.net>
|
||||
Zilin Du <zilin.du@gmail.com>
|
||||
zimbatm <zimbatm@zimbatm.com>
|
||||
zqh <zqhxuyuan@gmail.com>
|
||||
|
||||
-219
@@ -1,224 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 1.1.0 (2014-07-03)
|
||||
|
||||
#### Notable features since 1.0.1
|
||||
+ Add `.dockerignore` support
|
||||
+ Pause containers during `docker commit`
|
||||
+ Add `--tail` to `docker logs`
|
||||
|
||||
#### Builder
|
||||
+ Allow a tar file as context for `docker build`
|
||||
* Fix issue with white-spaces and multi-lines in `Dockerfiles`
|
||||
|
||||
#### Runtime
|
||||
* Overall performance improvements
|
||||
* Allow `/` as source of `docker run -v`
|
||||
* Fix port allocation
|
||||
* Fix bug in `docker save`
|
||||
* Add links information to `docker inspect`
|
||||
|
||||
#### Client
|
||||
* Improve command line parsing for `docker commit`
|
||||
|
||||
#### Remote API
|
||||
* Improve status code for the `start` and `stop` endpoints
|
||||
|
||||
## 1.0.1 (2014-06-19)
|
||||
|
||||
#### Notable features since 1.0.0
|
||||
* Enhance security for the LXC driver
|
||||
|
||||
#### Builder
|
||||
* Fix `ONBUILD` instruction passed to grandchildren
|
||||
|
||||
#### Runtime
|
||||
* Fix events subscription
|
||||
* Fix /etc/hostname file with host networking
|
||||
* Allow `-h` and `--net=none`
|
||||
* Fix issue with hotplug devices in `--privileged`
|
||||
|
||||
#### Client
|
||||
* Fix artifacts with events
|
||||
* Fix a panic with empty flags
|
||||
* Fix `docker cp` on Mac OS X
|
||||
|
||||
#### Miscellaneous
|
||||
* Fix compilation on Mac OS X
|
||||
* Fix several races
|
||||
|
||||
## 1.0.0 (2014-06-09)
|
||||
|
||||
#### Notable features since 0.12.0
|
||||
* Production support
|
||||
|
||||
## 0.12.0 (2014-06-05)
|
||||
|
||||
#### Notable features since 0.11.0
|
||||
* 40+ various improvements to stability, performance and usability
|
||||
* New `COPY` Dockerfile instruction to allow copying a local file from the context into the container without ever extracting if the file is a tar file
|
||||
* Inherit file permissions from the host on `ADD`
|
||||
* New `pause` and `unpause` commands to allow pausing and unpausing of containers using cgroup freezer
|
||||
* The `images` command has a `-f`/`--filter` option to filter the list of images
|
||||
* Add `--force-rm` to clean up after a failed build
|
||||
* Standardize JSON keys in Remote API to CamelCase
|
||||
* Pull from a docker run now assumes `latest` tag if not specified
|
||||
* Enhance security on Linux capabilities and device nodes
|
||||
|
||||
## 0.11.1 (2014-05-07)
|
||||
|
||||
#### Registry
|
||||
- Fix push and pull to private registry
|
||||
|
||||
## 0.11.0 (2014-05-07)
|
||||
|
||||
#### Notable features since 0.10.0
|
||||
|
||||
* SELinux support for mount and process labels
|
||||
* Linked containers can be accessed by hostname
|
||||
* Use the net `--net` flag to allow advanced network configuration such as host networking so that containers can use the host's network interfaces
|
||||
* Add a ping endpoint to the Remote API to do healthchecks of your docker daemon
|
||||
* Logs can now be returned with an optional timestamp
|
||||
* Docker now works with registries that support SHA-512
|
||||
* Multiple registry endpoints are supported to allow registry mirrors
|
||||
|
||||
## 0.10.0 (2014-04-08)
|
||||
|
||||
#### Builder
|
||||
- Fix printing multiple messages on a single line. Fixes broken output during builds.
|
||||
- Follow symlinks inside container's root for ADD build instructions.
|
||||
- Fix EXPOSE caching.
|
||||
|
||||
#### Documentation
|
||||
- Add the new options of `docker ps` to the documentation.
|
||||
- Add the options of `docker restart` to the documentation.
|
||||
- Update daemon docs and help messages for --iptables and --ip-forward.
|
||||
- Updated apt-cacher-ng docs example.
|
||||
- Remove duplicate description of --mtu from docs.
|
||||
- Add missing -t and -v for `docker images` to the docs.
|
||||
- Add fixes to the cli docs.
|
||||
- Update libcontainer docs.
|
||||
- Update images in docs to remove references to AUFS and LXC.
|
||||
- Update the nodejs_web_app in the docs to use the new epel RPM address.
|
||||
- Fix external link on security of containers.
|
||||
- Update remote API docs.
|
||||
- Add image size to history docs.
|
||||
- Be explicit about binding to all interfaces in redis example.
|
||||
- Document DisableNetwork flag in the 1.10 remote api.
|
||||
- Document that `--lxc-conf` is lxc only.
|
||||
- Add chef usage documentation.
|
||||
- Add example for an image with multiple for `docker load`.
|
||||
- Explain what `docker run -a` does in the docs.
|
||||
|
||||
#### Contrib
|
||||
- Add variable for DOCKER_LOGFILE to sysvinit and use append instead of overwrite in opening the logfile.
|
||||
- Fix init script cgroup mounting workarounds to be more similar to cgroupfs-mount and thus work properly.
|
||||
- Remove inotifywait hack from the upstart host-integration example because it's not necessary any more.
|
||||
- Add check-config script to contrib.
|
||||
- Fix fish shell completion.
|
||||
|
||||
#### Hack
|
||||
* Clean up "go test" output from "make test" to be much more readable/scannable.
|
||||
* Excluse more "definitely not unit tested Go source code" directories from hack/make/test.
|
||||
+ Generate md5 and sha256 hashes when building, and upload them via hack/release.sh.
|
||||
- Include contributed completions in Ubuntu PPA.
|
||||
+ Add cli integration tests.
|
||||
* Add tweaks to the hack scripts to make them simpler.
|
||||
|
||||
#### Remote API
|
||||
+ Add TLS auth support for API.
|
||||
* Move git clone from daemon to client.
|
||||
- Fix content-type detection in docker cp.
|
||||
* Split API into 2 go packages.
|
||||
|
||||
#### Runtime
|
||||
* Support hairpin NAT without going through Docker server.
|
||||
- devicemapper: succeed immediately when removing non-existing devices.
|
||||
- devicemapper: improve handling of devicemapper devices (add per device lock, increase sleep time and unlock while sleeping).
|
||||
- devicemapper: increase timeout in waitClose to 10 seconds.
|
||||
- devicemapper: ensure we shut down thin pool cleanly.
|
||||
- devicemapper: pass info, rather than hash to activateDeviceIfNeeded, deactivateDevice, setInitialized, deleteDevice.
|
||||
- devicemapper: avoid AB-BA deadlock.
|
||||
- devicemapper: make shutdown better/faster.
|
||||
- improve alpha sorting in mflag.
|
||||
- Remove manual http cookie management because the cookiejar is being used.
|
||||
- Use BSD raw mode on Darwin. Fixes nano, tmux and others.
|
||||
- Add FreeBSD support for the client.
|
||||
- Merge auth package into registry.
|
||||
- Add deprecation warning for -t on `docker pull`.
|
||||
- Remove goroutine leak on error.
|
||||
- Update parseLxcInfo to comply with new lxc1.0 format.
|
||||
- Fix attach exit on darwin.
|
||||
- Improve deprecation message.
|
||||
- Retry to retrieve the layer metadata up to 5 times for `docker pull`.
|
||||
- Only unshare the mount namespace for execin.
|
||||
- Merge existing config when committing.
|
||||
- Disable daemon startup timeout.
|
||||
- Fix issue #4681: add loopback interface when networking is disabled.
|
||||
- Add failing test case for issue #4681.
|
||||
- Send SIGTERM to child, instead of SIGKILL.
|
||||
- Show the driver and the kernel version in `docker info` even when not in debug mode.
|
||||
- Always symlink /dev/ptmx for libcontainer. This fixes console related problems.
|
||||
- Fix issue caused by the absence of /etc/apparmor.d.
|
||||
- Don't leave empty cidFile behind when failing to create the container.
|
||||
- Mount cgroups automatically if they're not mounted already.
|
||||
- Use mock for search tests.
|
||||
- Update to double-dash everywhere.
|
||||
- Move .dockerenv parsing to lxc driver.
|
||||
- Move all bind-mounts in the container inside the namespace.
|
||||
- Don't use separate bind mount for container.
|
||||
- Always symlink /dev/ptmx for libcontainer.
|
||||
- Don't kill by pid for other drivers.
|
||||
- Add initial logging to libcontainer.
|
||||
* Sort by port in `docker ps`.
|
||||
- Move networking drivers into runtime top level package.
|
||||
+ Add --no-prune to `docker rmi`.
|
||||
+ Add time since exit in `docker ps`.
|
||||
- graphdriver: add build tags.
|
||||
- Prevent allocation of previously allocated ports & prevent improve port allocation.
|
||||
* Add support for --since/--before in `docker ps`.
|
||||
- Clean up container stop.
|
||||
+ Add support for configurable dns search domains.
|
||||
- Add support for relative WORKDIR instructions.
|
||||
- Add --output flag for docker save.
|
||||
- Remove duplication of DNS entries in config merging.
|
||||
- Add cpuset.cpus to cgroups and native driver options.
|
||||
- Remove docker-ci.
|
||||
- Promote btrfs. btrfs is no longer considered experimental.
|
||||
- Add --input flag to `docker load`.
|
||||
- Return error when existing bridge doesn't match IP address.
|
||||
- Strip comments before parsing line continuations to avoid interpreting instructions as comments.
|
||||
- Fix TestOnlyLoopbackExistsWhenUsingDisableNetworkOption to ignore "DOWN" interfaces.
|
||||
- Add systemd implementation of cgroups and make containers show up as systemd units.
|
||||
- Fix commit and import when no repository is specified.
|
||||
- Remount /var/lib/docker as --private to fix scaling issue.
|
||||
- Use the environment's proxy when pinging the remote registry.
|
||||
- Reduce error level from harmless errors.
|
||||
* Allow --volumes-from to be individual files.
|
||||
- Fix expanding buffer in StdCopy.
|
||||
- Set error regardless of attach or stdin. This fixes #3364.
|
||||
- Add support for --env-file to load environment variables from files.
|
||||
- Symlink /etc/mtab and /proc/mounts.
|
||||
- Allow pushing a single tag.
|
||||
- Shut down containers cleanly at shutdown and wait forever for the containers to shut down. This makes container shutdown on daemon shutdown work properly via SIGTERM.
|
||||
- Don't throw error when starting an already running container.
|
||||
- Fix dynamic port allocation limit.
|
||||
- remove setupDev from libcontainer.
|
||||
- Add API version to `docker version`.
|
||||
- Return correct exit code when receiving signal and make SIGQUIT quit without cleanup.
|
||||
- Fix --volumes-from mount failure.
|
||||
- Allow non-privileged containers to create device nodes.
|
||||
- Skip login tests because of external dependency on a hosted service.
|
||||
- Deprecate `docker images --tree` and `docker images --viz`.
|
||||
- Deprecate `docker insert`.
|
||||
- Include base abstraction for apparmor. This fixes some apparmor related problems on Ubuntu 14.04.
|
||||
- Add specific error message when hitting 401 over HTTP on push.
|
||||
- Fix absolute volume check.
|
||||
- Remove volumes-from from the config.
|
||||
- Move DNS options to hostconfig.
|
||||
- Update the apparmor profile for libcontainer.
|
||||
- Add deprecation notice for `docker commit -run`.
|
||||
|
||||
## 0.9.1 (2014-03-24)
|
||||
|
||||
#### Builder
|
||||
|
||||
+36
-49
@@ -9,7 +9,7 @@ feels wrong or incomplete.
|
||||
When reporting [issues](https://github.com/dotcloud/docker/issues)
|
||||
on GitHub please include your host OS (Ubuntu 12.04, Fedora 19, etc),
|
||||
the output of `uname -a` and the output of `docker version` along with
|
||||
the output of `docker -D info`. Please include the steps required to reproduce
|
||||
the output of `docker info`. Please include the steps required to reproduce
|
||||
the problem if possible and applicable.
|
||||
This information will help us review and fix your issue faster.
|
||||
|
||||
@@ -17,7 +17,7 @@ This information will help us review and fix your issue faster.
|
||||
|
||||
For instructions on setting up your development environment, please
|
||||
see our dedicated [dev environment setup
|
||||
docs](http://docs.docker.com/contributing/devenvironment/).
|
||||
docs](http://docs.docker.io/en/latest/contributing/devenvironment/).
|
||||
|
||||
## Contribution guidelines
|
||||
|
||||
@@ -77,18 +77,19 @@ well as a clean documentation build. See ``docs/README.md`` for more
|
||||
information on building the docs and how docs 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 plugins that do this automatically.
|
||||
and maintenance. Always run `go fmt` before committing your changes. Most
|
||||
editors have plugins that do this automatically, and there's also a git
|
||||
pre-commit hook:
|
||||
|
||||
```
|
||||
curl -o .git/hooks/pre-commit https://raw.github.com/edsrzf/gofmt-git-hook/master/fmt-check && chmod +x .git/hooks/pre-commit
|
||||
```
|
||||
|
||||
Pull requests descriptions should be as clear as possible and include a
|
||||
reference to all the issues that they address.
|
||||
|
||||
Pull requests must not contain commits from other users or branches.
|
||||
|
||||
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
|
||||
@@ -103,8 +104,10 @@ 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 #XXX`
|
||||
or `Fixes #XXX`, 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.
|
||||
Add your name to the AUTHORS file, but make sure the list is sorted and your
|
||||
name and email address match your git configuration. The AUTHORS file is
|
||||
regenerated occasionally from the git commit history, so a mismatch may result
|
||||
in your changes being overwritten.
|
||||
|
||||
### Merge approval
|
||||
|
||||
@@ -123,46 +126,33 @@ For more details see [MAINTAINERS.md](hack/MAINTAINERS.md)
|
||||
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/)):
|
||||
can certify the below:
|
||||
|
||||
```
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
Docker Developer Certificate of Origin 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
660 York Street, Suite 102,
|
||||
San Francisco, CA 94110 USA
|
||||
By making a contribution to the Docker Project ("Project"), I represent and
|
||||
warrant that:
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
a. The contribution was created in whole or in part by me and I have the right
|
||||
to submit the contribution on my own behalf or on behalf of a third party who
|
||||
has authorized me to submit this contribution to the Project; 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 and authorization 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) that I have identified in
|
||||
the contribution; or
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
c. The contribution was provided directly to me by some other person who
|
||||
represented and warranted (a) or (b) and I have not modified it.
|
||||
|
||||
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.
|
||||
d. I understand and agree that this Project and the contribution are publicly
|
||||
known and that a record of the contribution (including all personal
|
||||
information I submit with it, including my sign-off record) 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:
|
||||
@@ -175,7 +165,7 @@ One way to automate this, is customise your get ``commit.template`` by adding
|
||||
a ``prepare-commit-msg`` hook to your docker checkout:
|
||||
|
||||
```
|
||||
curl -o .git/hooks/prepare-commit-msg https://raw.githubusercontent.com/dotcloud/docker/master/contrib/prepare-commit-msg.hook && chmod +x .git/hooks/prepare-commit-msg
|
||||
curl -o .git/hooks/prepare-commit-msg https://raw.github.com/dotcloud/docker/master/contrib/prepare-commit-msg.hook && chmod +x .git/hooks/prepare-commit-msg
|
||||
```
|
||||
|
||||
* Note: the above script expects to find your GitHub user name in ``git config --get github.user``
|
||||
@@ -185,12 +175,9 @@ curl -o .git/hooks/prepare-commit-msg https://raw.githubusercontent.com/dotcloud
|
||||
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.
|
||||
* Your patch is a single line change to documentation.
|
||||
|
||||
If you have any questions, please refer to the FAQ in the [docs](http://docs.docker.com)
|
||||
If you have any questions, please refer to the FAQ in the [docs](http://docs.docker.io)
|
||||
|
||||
### How can I become a maintainer?
|
||||
|
||||
|
||||
+10
-18
@@ -6,13 +6,13 @@
|
||||
# docker build -t docker .
|
||||
#
|
||||
# # Mount your source in an interactive container for quick testing:
|
||||
# docker run -v `pwd`:/go/src/github.com/dotcloud/docker --privileged -i -t docker bash
|
||||
# docker run -v `pwd`:/go/src/github.com/dotcloud/docker -privileged -i -t docker bash
|
||||
#
|
||||
# # Run the test suite:
|
||||
# docker run --privileged docker hack/make.sh test
|
||||
# docker run -privileged docker hack/make.sh test
|
||||
#
|
||||
# # Publish a release:
|
||||
# docker run --privileged \
|
||||
# docker run -privileged \
|
||||
# -e AWS_S3_BUCKET=baz \
|
||||
# -e AWS_ACCESS_KEY=foo \
|
||||
# -e AWS_SECRET_KEY=bar \
|
||||
@@ -24,7 +24,7 @@
|
||||
#
|
||||
|
||||
docker-version 0.6.1
|
||||
FROM ubuntu:14.04
|
||||
FROM ubuntu:13.10
|
||||
MAINTAINER Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
|
||||
# Packaged dependencies
|
||||
@@ -41,15 +41,17 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
||||
libapparmor-dev \
|
||||
libcap-dev \
|
||||
libsqlite3-dev \
|
||||
lxc=1.0* \
|
||||
mercurial \
|
||||
pandoc \
|
||||
reprepro \
|
||||
ruby1.9.1 \
|
||||
ruby1.9.1-dev \
|
||||
s3cmd=1.1.0* \
|
||||
--no-install-recommends
|
||||
|
||||
# Get and compile LXC 0.8 (since it is the most stable)
|
||||
RUN git clone --no-checkout https://github.com/lxc/lxc.git /usr/local/lxc && cd /usr/local/lxc && git checkout -q lxc-0.8.0
|
||||
RUN cd /usr/local/lxc && ./autogen.sh && ./configure --disable-docs && make && make install
|
||||
|
||||
# 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
|
||||
@@ -66,10 +68,7 @@ ENV GOPATH /go:/go/src/github.com/dotcloud/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
|
||||
ENV DOCKER_CROSSPLATFORMS linux/386 linux/arm darwin/amd64 darwin/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'
|
||||
@@ -80,22 +79,15 @@ RUN go get code.google.com/p/go.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/dotcloud/docker
|
||||
ENV DOCKER_BUILDTAGS apparmor selinux
|
||||
ENV DOCKER_BUILDTAGS apparmor
|
||||
|
||||
# Wrap all commands in the "docker-in-docker" script to allow nested containers
|
||||
ENTRYPOINT ["hack/dind"]
|
||||
|
||||
@@ -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.
|
||||
|
||||
+2
-4
@@ -1,9 +1,7 @@
|
||||
Solomon Hykes <solomon@docker.com> (@shykes)
|
||||
Solomon Hykes <solomon@dotcloud.com> (@shykes)
|
||||
Guillaume Charmes <guillaume@dotcloud.com> (@creack)
|
||||
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,19 +1,9 @@
|
||||
.PHONY: all binary build cross default docs docs-build docs-shell shell test test-unit test-integration test-integration-cli validate
|
||||
.PHONY: all binary build cross default docs docs-build docs-shell shell test test-integration
|
||||
|
||||
# to allow `make BINDDIR=. shell` or `make BINDDIR= test`
|
||||
BINDDIR := bundles
|
||||
# to allow `make DOCSPORT=9000 docs`
|
||||
DOCSPORT := 8000
|
||||
|
||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
||||
GITCOMMIT := $(shell git rev-parse --short HEAD 2>/dev/null)
|
||||
DOCKER_IMAGE := docker$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||
DOCKER_DOCS_IMAGE := docker-docs$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||
DOCKER_MOUNT := $(if $(BINDDIR),-v "$(CURDIR)/$(BINDDIR):/go/src/github.com/dotcloud/docker/$(BINDDIR)")
|
||||
|
||||
DOCKER_RUN_DOCKER := docker run --rm -it --privileged -e TESTFLAGS -e TESTDIRS -e DOCKER_GRAPHDRIVER -e DOCKER_EXECDRIVER $(DOCKER_MOUNT) "$(DOCKER_IMAGE)"
|
||||
# to allow `make DOCSDIR=docs docs-shell`
|
||||
DOCKER_RUN_DOCS := docker run --rm -it $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR)) -e AWS_S3_BUCKET
|
||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
|
||||
DOCKER_IMAGE := docker:$(GIT_BRANCH)
|
||||
DOCKER_DOCS_IMAGE := docker-docs:$(GIT_BRANCH)
|
||||
DOCKER_RUN_DOCKER := docker run --rm -i -t --privileged -e TESTFLAGS -v "$(CURDIR)/bundles:/go/src/github.com/dotcloud/docker/bundles" "$(DOCKER_IMAGE)"
|
||||
|
||||
default: binary
|
||||
|
||||
@@ -27,29 +17,17 @@ 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
|
||||
docker run --rm -i -t -p 8000:8000 "$(DOCKER_DOCS_IMAGE)"
|
||||
|
||||
docs-shell: docs-build
|
||||
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" bash
|
||||
|
||||
docs-release: docs-build
|
||||
$(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" ./release.sh
|
||||
docker run --rm -i -t -p 8000:8000 "$(DOCKER_DOCS_IMAGE)" bash
|
||||
|
||||
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
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh test test-integration
|
||||
|
||||
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
|
||||
|
||||
@@ -57,10 +35,6 @@ 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:
|
||||
|
||||
@@ -18,13 +18,7 @@ It benefits directly from the experience accumulated over several years
|
||||
of large-scale operation and support of hundreds of thousands of
|
||||
applications and databases.
|
||||
|
||||

|
||||
|
||||
## Security Disclosure
|
||||
|
||||
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.
|
||||

|
||||
|
||||
## Better than VMs
|
||||
|
||||
@@ -148,10 +142,11 @@ 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.
|
||||
We also offer an interactive tutorial for quickly learning the basics of
|
||||
using Docker.
|
||||
|
||||
For up-to-date install instructions, see the [Docs](http://docs.docker.com).
|
||||
For up-to-date install instructions and online tutorials, see the
|
||||
[Getting Started page](http://www.docker.io/gettingstarted/).
|
||||
|
||||
Usage examples
|
||||
==============
|
||||
@@ -160,7 +155,7 @@ 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
|
||||
examples](http://docs.docker.io/en/latest/examples/) in the
|
||||
documentation.
|
||||
|
||||
Under the hood
|
||||
@@ -195,9 +190,3 @@ 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
|
||||
|
||||
|
||||
Licensing
|
||||
=========
|
||||
Docker is licensed under the Apache License, Version 2.0. See LICENSE for full license text.
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
This directory contains code pertaining to the Docker API:
|
||||
|
||||
- Used by the docker client when comunicating with the docker deamon
|
||||
|
||||
- Used by third party tools wishing to interface with the docker deamon
|
||||
@@ -1,6 +1,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -17,3 +20,46 @@ func TestJsonContentType(t *testing.T) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,112 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
flag "github.com/dotcloud/docker/pkg/mflag"
|
||||
"github.com/dotcloud/docker/pkg/term"
|
||||
"github.com/dotcloud/docker/registry"
|
||||
)
|
||||
|
||||
var funcMap = template.FuncMap{
|
||||
"json": func(v interface{}) string {
|
||||
a, _ := json.Marshal(v)
|
||||
return string(a)
|
||||
},
|
||||
}
|
||||
|
||||
func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) {
|
||||
if len(name) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
|
||||
method := reflect.ValueOf(cli).MethodByName(methodName)
|
||||
if !method.IsValid() {
|
||||
return nil, false
|
||||
}
|
||||
return method.Interface().(func(...string) error), true
|
||||
}
|
||||
|
||||
func (cli *DockerCli) ParseCommands(args ...string) error {
|
||||
if len(args) > 0 {
|
||||
method, exists := cli.getMethod(args[0])
|
||||
if !exists {
|
||||
fmt.Println("Error: Command not found:", args[0])
|
||||
return cli.CmdHelp(args[1:]...)
|
||||
}
|
||||
return method(args[1:]...)
|
||||
}
|
||||
return cli.CmdHelp(args...)
|
||||
}
|
||||
|
||||
func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet {
|
||||
flags := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||
flags.Usage = func() {
|
||||
fmt.Fprintf(cli.err, "\nUsage: docker %s %s\n\n%s\n\n", name, 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 NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsConfig *tls.Config) *DockerCli {
|
||||
var (
|
||||
isTerminal = false
|
||||
terminalFd uintptr
|
||||
scheme = "http"
|
||||
)
|
||||
|
||||
if tlsConfig != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
if in != nil {
|
||||
if file, ok := out.(*os.File); ok {
|
||||
terminalFd = file.Fd()
|
||||
isTerminal = term.IsTerminal(terminalFd)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = out
|
||||
}
|
||||
return &DockerCli{
|
||||
proto: proto,
|
||||
addr: addr,
|
||||
in: in,
|
||||
out: out,
|
||||
err: err,
|
||||
isTerminal: isTerminal,
|
||||
terminalFd: terminalFd,
|
||||
tlsConfig: tlsConfig,
|
||||
scheme: scheme,
|
||||
}
|
||||
}
|
||||
|
||||
type DockerCli struct {
|
||||
proto string
|
||||
addr string
|
||||
configFile *registry.ConfigFile
|
||||
in io.ReadCloser
|
||||
out io.Writer
|
||||
err io.Writer
|
||||
isTerminal bool
|
||||
terminalFd uintptr
|
||||
tlsConfig *tls.Config
|
||||
scheme string
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/dotcloud/docker/api"
|
||||
"github.com/dotcloud/docker/dockerversion"
|
||||
"github.com/dotcloud/docker/pkg/term"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
)
|
||||
|
||||
func (cli *DockerCli) dial() (net.Conn, error) {
|
||||
if cli.tlsConfig != nil && cli.proto != "unix" {
|
||||
return tls.Dial(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) error {
|
||||
defer func() {
|
||||
if started != nil {
|
||||
close(started)
|
||||
}
|
||||
}()
|
||||
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), nil)
|
||||
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.isTerminal && os.Getenv("NORAW") == "" {
|
||||
oldState, err = term.SetRawTerminal(cli.terminalFd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer term.RestoreTerminal(cli.terminalFd, oldState)
|
||||
}
|
||||
|
||||
if stdout != nil || stderr != nil {
|
||||
receiveStdout = utils.Go(func() (err error) {
|
||||
defer func() {
|
||||
if in != nil {
|
||||
if setRawTerminal && cli.isTerminal {
|
||||
term.RestoreTerminal(cli.terminalFd, 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 {
|
||||
_, err = io.Copy(stdout, br)
|
||||
} else {
|
||||
_, err = utils.StdCopy(stdout, stderr, br)
|
||||
}
|
||||
utils.Debugf("[hijack] End of stdout")
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
sendStdin := utils.Go(func() error {
|
||||
if in != nil {
|
||||
io.Copy(rwc, in)
|
||||
utils.Debugf("[hijack] End of stdin")
|
||||
}
|
||||
if tcpc, ok := rwc.(*net.TCPConn); ok {
|
||||
if err := tcpc.CloseWrite(); err != nil {
|
||||
utils.Debugf("Couldn't send EOF: %s\n", err)
|
||||
}
|
||||
} else if unixc, ok := rwc.(*net.UnixConn); ok {
|
||||
if err := unixc.CloseWrite(); err != nil {
|
||||
utils.Debugf("Couldn't send EOF: %s\n", err)
|
||||
}
|
||||
}
|
||||
// Discard errors due to pipe interruption
|
||||
return nil
|
||||
})
|
||||
|
||||
if stdout != nil || stderr != nil {
|
||||
if err := <-receiveStdout; err != nil {
|
||||
utils.Debugf("Error receiveStdout: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !cli.isTerminal {
|
||||
if err := <-sendStdin; err != nil {
|
||||
utils.Debugf("Error sendStdin: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
gosignal "os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/dotcloud/docker/api"
|
||||
"github.com/dotcloud/docker/dockerversion"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/pkg/term"
|
||||
"github.com/dotcloud/docker/registry"
|
||||
"github.com/dotcloud/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 {
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: cli.tlsConfig,
|
||||
Dial: func(network, addr string) (net.Conn, error) {
|
||||
return net.Dial(cli.proto, cli.addr)
|
||||
},
|
||||
}
|
||||
return &http.Client{Transport: tr}
|
||||
}
|
||||
|
||||
func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
|
||||
params := bytes.NewBuffer(nil)
|
||||
if data != nil {
|
||||
if env, ok := data.(engine.Env); ok {
|
||||
if err := env.Encode(params); err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
} else {
|
||||
buf, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
if _, err := params.Write(buf); 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
|
||||
}
|
||||
return nil, -1, 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("http://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.terminalFd, cli.isTerminal)
|
||||
}
|
||||
if stdout != nil || stderr != nil {
|
||||
// When TTY is ON, use regular copy
|
||||
if setRawTerminal {
|
||||
_, err = io.Copy(stdout, resp.Body)
|
||||
} else {
|
||||
_, err = utils.StdCopy(stdout, stderr, resp.Body)
|
||||
}
|
||||
utils.Debugf("[stream] End of stdout")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) resizeTty(id string) {
|
||||
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))
|
||||
if _, _, err := readBody(cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil, false)); err != nil {
|
||||
utils.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) {
|
||||
steam, _, 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(steam); err != nil {
|
||||
return false, -1, err
|
||||
}
|
||||
|
||||
state := result.GetSubEnv("State")
|
||||
return state.GetBool("Running"), state.GetInt("ExitCode"), nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) monitorTtySize(id string) error {
|
||||
cli.resizeTty(id)
|
||||
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
gosignal.Notify(sigchan, syscall.SIGWINCH)
|
||||
go func() {
|
||||
for _ = range sigchan {
|
||||
cli.resizeTty(id)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) getTtySize() (int, int) {
|
||||
if !cli.isTerminal {
|
||||
return 0, 0
|
||||
}
|
||||
ws, err := term.GetWinsize(cli.terminalFd)
|
||||
if err != nil {
|
||||
utils.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
|
||||
}
|
||||
+7
-11
@@ -2,18 +2,16 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"mime"
|
||||
"strings"
|
||||
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/pkg/version"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
APIVERSION version.Version = "1.13"
|
||||
DEFAULTHTTPHOST = "127.0.0.1"
|
||||
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
||||
APIVERSION = "1.10"
|
||||
DEFAULTHTTPHOST = "127.0.0.1"
|
||||
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
||||
)
|
||||
|
||||
func ValidateHost(val string) (string, error) {
|
||||
@@ -25,13 +23,11 @@ func ValidateHost(val string) (string, error) {
|
||||
}
|
||||
|
||||
//TODO remove, used on < 1.5 in getContainersJSON
|
||||
func DisplayablePorts(ports *engine.Table) string {
|
||||
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")))
|
||||
result = append(result, fmt.Sprintf("%d/%s", port.GetInt("PublicPort"), 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")))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/nat"
|
||||
"github.com/dotcloud/docker/runconfig"
|
||||
)
|
||||
|
||||
type Container struct {
|
||||
Config runconfig.Config
|
||||
HostConfig runconfig.HostConfig
|
||||
State struct {
|
||||
Running bool
|
||||
ExitCode int
|
||||
}
|
||||
NetworkSettings struct {
|
||||
Ports nat.PortMap
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,21 @@
|
||||
package server
|
||||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"code.google.com/p/go.net/websocket"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"expvar"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/auth"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/pkg/listenbuffer"
|
||||
"github.com/dotcloud/docker/pkg/systemd"
|
||||
"github.com/dotcloud/docker/pkg/user"
|
||||
"github.com/dotcloud/docker/pkg/version"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"github.com/gorilla/mux"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@@ -19,18 +26,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"code.google.com/p/go.net/websocket"
|
||||
|
||||
"github.com/dotcloud/docker/api"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/pkg/listenbuffer"
|
||||
"github.com/dotcloud/docker/pkg/systemd"
|
||||
"github.com/dotcloud/docker/pkg/user"
|
||||
"github.com/dotcloud/docker/pkg/version"
|
||||
"github.com/dotcloud/docker/registry"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -122,17 +117,17 @@ func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter
|
||||
var (
|
||||
authConfig, err = ioutil.ReadAll(r.Body)
|
||||
job = eng.Job("auth")
|
||||
stdoutBuffer = bytes.NewBuffer(nil)
|
||||
status string
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
job.Setenv("authConfig", string(authConfig))
|
||||
job.Stdout.Add(stdoutBuffer)
|
||||
job.Stdout.AddString(&status)
|
||||
if err = job.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
if status := engine.Tail(stdoutBuffer, 1); status != "" {
|
||||
if status != "" {
|
||||
var env engine.Env
|
||||
env.Set("Status", status)
|
||||
return writeJSON(w, http.StatusOK, env)
|
||||
@@ -165,36 +160,6 @@ func postContainersKill(eng *engine.Engine, version version.Version, w http.Resp
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersPause(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
job := eng.Job("pause", vars["name"])
|
||||
if err := job.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainersUnpause(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
job := eng.Job("unpause", vars["name"])
|
||||
if err := job.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainersExport(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
@@ -218,8 +183,6 @@ func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseW
|
||||
job = eng.Job("images")
|
||||
)
|
||||
|
||||
job.Setenv("filters", r.Form.Get("filters"))
|
||||
// FIXME this parameter could just be a match filter
|
||||
job.Setenv("filter", r.Form.Get("filter"))
|
||||
job.Setenv("all", r.Form.Get("all"))
|
||||
|
||||
@@ -276,10 +239,9 @@ func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWrite
|
||||
return err
|
||||
}
|
||||
|
||||
var job = eng.Job("events")
|
||||
var job = eng.Job("events", r.RemoteAddr)
|
||||
streamJSON(job, w, true)
|
||||
job.Setenv("since", r.Form.Get("since"))
|
||||
job.Setenv("until", r.Form.Get("until"))
|
||||
return job.Run()
|
||||
}
|
||||
|
||||
@@ -351,7 +313,7 @@ func getContainersJSON(eng *engine.Engine, version version.Version, w http.Respo
|
||||
for _, out := range outs.Data {
|
||||
ports := engine.NewTable("", 0)
|
||||
ports.ReadListFrom([]byte(out.Get("Ports")))
|
||||
out.Set("Ports", api.DisplayablePorts(ports))
|
||||
out.Set("Ports", displayablePorts(ports))
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if _, err = outs.WriteListTo(w); err != nil {
|
||||
@@ -361,54 +323,6 @@ func getContainersJSON(eng *engine.Engine, version version.Version, w http.Respo
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainersLogs(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
var (
|
||||
inspectJob = eng.Job("container_inspect", vars["name"])
|
||||
logsJob = eng.Job("logs", vars["name"])
|
||||
c, err = inspectJob.Stdout.AddEnv()
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logsJob.Setenv("follow", r.Form.Get("follow"))
|
||||
logsJob.Setenv("tail", r.Form.Get("tail"))
|
||||
logsJob.Setenv("stdout", r.Form.Get("stdout"))
|
||||
logsJob.Setenv("stderr", r.Form.Get("stderr"))
|
||||
logsJob.Setenv("timestamps", r.Form.Get("timestamps"))
|
||||
// Validate args here, because we can't return not StatusOK after job.Run() call
|
||||
stdout, stderr := logsJob.GetenvBool("stdout"), logsJob.GetenvBool("stderr")
|
||||
if !(stdout || stderr) {
|
||||
return fmt.Errorf("Bad parameters: you must choose at least one stream")
|
||||
}
|
||||
if err = inspectJob.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var outStream, errStream io.Writer
|
||||
outStream = utils.NewWriteFlusher(w)
|
||||
|
||||
if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version.GreaterThanOrEqualTo("1.6") {
|
||||
errStream = utils.NewStdWriter(outStream, utils.Stderr)
|
||||
outStream = utils.NewStdWriter(outStream, utils.Stdout)
|
||||
} else {
|
||||
errStream = outStream
|
||||
}
|
||||
|
||||
logsJob.Stdout.Add(outStream)
|
||||
logsJob.Stderr.Set(errStream)
|
||||
if err := logsJob.Run(); err != nil {
|
||||
fmt.Fprintf(outStream, "Error running logs job: %s\n", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postImagesTag(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
@@ -431,32 +345,26 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit
|
||||
return err
|
||||
}
|
||||
var (
|
||||
config engine.Env
|
||||
env engine.Env
|
||||
job = eng.Job("commit", r.Form.Get("container"))
|
||||
stdoutBuffer = bytes.NewBuffer(nil)
|
||||
config engine.Env
|
||||
env engine.Env
|
||||
job = eng.Job("commit", r.Form.Get("container"))
|
||||
)
|
||||
if err := config.Decode(r.Body); err != nil {
|
||||
utils.Errorf("%s", err)
|
||||
}
|
||||
|
||||
if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") {
|
||||
job.Setenv("pause", "1")
|
||||
} else {
|
||||
job.Setenv("pause", r.FormValue("pause"))
|
||||
}
|
||||
|
||||
job.Setenv("repo", r.Form.Get("repo"))
|
||||
job.Setenv("tag", r.Form.Get("tag"))
|
||||
job.Setenv("author", r.Form.Get("author"))
|
||||
job.Setenv("comment", r.Form.Get("comment"))
|
||||
job.SetenvSubEnv("config", &config)
|
||||
|
||||
job.Stdout.Add(stdoutBuffer)
|
||||
var id string
|
||||
job.Stdout.AddString(&id)
|
||||
if err := job.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
env.Set("Id", engine.Tail(stdoutBuffer, 1))
|
||||
env.Set("Id", id)
|
||||
return writeJSON(w, http.StatusCreated, env)
|
||||
}
|
||||
|
||||
@@ -472,13 +380,13 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon
|
||||
job *engine.Job
|
||||
)
|
||||
authEncoded := r.Header.Get("X-Registry-Auth")
|
||||
authConfig := ®istry.AuthConfig{}
|
||||
authConfig := &auth.AuthConfig{}
|
||||
if authEncoded != "" {
|
||||
authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
||||
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
||||
// for a pull it is not an error if no auth was given
|
||||
// to increase compatibility with the existing api it is defaulting to be empty
|
||||
authConfig = ®istry.AuthConfig{}
|
||||
authConfig = &auth.AuthConfig{}
|
||||
}
|
||||
}
|
||||
if image != "" { //pull
|
||||
@@ -520,7 +428,7 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons
|
||||
}
|
||||
var (
|
||||
authEncoded = r.Header.Get("X-Registry-Auth")
|
||||
authConfig = ®istry.AuthConfig{}
|
||||
authConfig = &auth.AuthConfig{}
|
||||
metaHeaders = map[string][]string{}
|
||||
)
|
||||
|
||||
@@ -529,7 +437,7 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons
|
||||
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
||||
// for a search it is not an error if no auth was given
|
||||
// to increase compatibility with the existing api it is defaulting to be empty
|
||||
authConfig = ®istry.AuthConfig{}
|
||||
authConfig = &auth.AuthConfig{}
|
||||
}
|
||||
}
|
||||
for k, v := range r.Header {
|
||||
@@ -546,6 +454,31 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons
|
||||
return job.Run()
|
||||
}
|
||||
|
||||
func postImagesInsert(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
job := eng.Job("insert", vars["name"], r.Form.Get("url"), r.Form.Get("path"))
|
||||
if version.GreaterThan("1.0") {
|
||||
job.SetenvBool("json", true)
|
||||
streamJSON(job, w, false)
|
||||
} else {
|
||||
job.Stdout.Add(w)
|
||||
}
|
||||
if err := job.Run(); err != nil {
|
||||
if !job.Stdout.Used() {
|
||||
return err
|
||||
}
|
||||
sf := utils.NewStreamFormatter(version.GreaterThan("1.0"))
|
||||
w.Write(sf.FormatError(err))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func postImagesPush(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
@@ -560,7 +493,7 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
authConfig := ®istry.AuthConfig{}
|
||||
authConfig := &auth.AuthConfig{}
|
||||
|
||||
authEncoded := r.Header.Get("X-Registry-Auth")
|
||||
if authEncoded != "" {
|
||||
@@ -568,7 +501,7 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response
|
||||
authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
||||
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
||||
// to increase compatibility to existing api it is defaulting to be empty
|
||||
authConfig = ®istry.AuthConfig{}
|
||||
authConfig = &auth.AuthConfig{}
|
||||
}
|
||||
} else {
|
||||
// the old format is supported for compatibility if there was no authConfig header
|
||||
@@ -580,7 +513,6 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response
|
||||
job := eng.Job("push", vars["name"])
|
||||
job.SetenvJson("metaHeaders", metaHeaders)
|
||||
job.SetenvJson("authConfig", authConfig)
|
||||
job.Setenv("tag", r.Form.Get("tag"))
|
||||
if version.GreaterThan("1.0") {
|
||||
job.SetenvBool("json", true)
|
||||
streamJSON(job, w, true)
|
||||
@@ -621,17 +553,17 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
out engine.Env
|
||||
job = eng.Job("create", r.Form.Get("name"))
|
||||
outWarnings []string
|
||||
stdoutBuffer = bytes.NewBuffer(nil)
|
||||
warnings = bytes.NewBuffer(nil)
|
||||
out engine.Env
|
||||
job = eng.Job("create", r.Form.Get("name"))
|
||||
outWarnings []string
|
||||
outId string
|
||||
warnings = bytes.NewBuffer(nil)
|
||||
)
|
||||
if err := job.DecodeEnv(r.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
// Read container ID from the first line of stdout
|
||||
job.Stdout.Add(stdoutBuffer)
|
||||
job.Stdout.AddString(&outId)
|
||||
// Read warnings from stderr
|
||||
job.Stderr.Add(warnings)
|
||||
if err := job.Run(); err != nil {
|
||||
@@ -642,7 +574,7 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re
|
||||
for scanner.Scan() {
|
||||
outWarnings = append(outWarnings, scanner.Text())
|
||||
}
|
||||
out.Set("Id", engine.Tail(stdoutBuffer, 1))
|
||||
out.Set("Id", outId)
|
||||
out.SetList("Warnings", outWarnings)
|
||||
return writeJSON(w, http.StatusCreated, out)
|
||||
}
|
||||
@@ -691,7 +623,6 @@ func deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWr
|
||||
var job = eng.Job("image_delete", vars["name"])
|
||||
streamJSON(job, w, false)
|
||||
job.Setenv("force", r.Form.Get("force"))
|
||||
job.Setenv("noprune", r.Form.Get("noprune"))
|
||||
|
||||
return job.Run()
|
||||
}
|
||||
@@ -700,24 +631,17 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
var (
|
||||
name = vars["name"]
|
||||
job = eng.Job("start", name)
|
||||
)
|
||||
|
||||
name := vars["name"]
|
||||
job := eng.Job("start", name)
|
||||
// allow a nil body for backwards compatibility
|
||||
if r.Body != nil {
|
||||
if api.MatchesContentType(r.Header.Get("Content-Type"), "application/json") {
|
||||
if MatchesContentType(r.Header.Get("Content-Type"), "application/json") {
|
||||
if err := job.DecodeEnv(r.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := job.Run(); err != nil {
|
||||
if err.Error() == "Container already started" {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
@@ -734,10 +658,6 @@ func postContainersStop(eng *engine.Engine, version version.Version, w http.Resp
|
||||
job := eng.Job("stop", vars["name"])
|
||||
job.Setenv("t", r.Form.Get("t"))
|
||||
if err := job.Run(); err != nil {
|
||||
if err.Error() == "Container already stopped" {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
@@ -749,16 +669,20 @@ func postContainersWait(eng *engine.Engine, version version.Version, w http.Resp
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
var (
|
||||
env engine.Env
|
||||
stdoutBuffer = bytes.NewBuffer(nil)
|
||||
job = eng.Job("wait", vars["name"])
|
||||
env engine.Env
|
||||
status string
|
||||
job = eng.Job("wait", vars["name"])
|
||||
)
|
||||
job.Stdout.Add(stdoutBuffer)
|
||||
job.Stdout.AddString(&status)
|
||||
if err := job.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
env.Set("StatusCode", engine.Tail(stdoutBuffer, 1))
|
||||
// Parse a 16-bit encoded integer to map typical unix exit status.
|
||||
_, err := strconv.ParseInt(status, 10, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env.Set("StatusCode", status)
|
||||
return writeJSON(w, http.StatusOK, env)
|
||||
}
|
||||
|
||||
@@ -784,7 +708,7 @@ func postContainersAttach(eng *engine.Engine, version version.Version, w http.Re
|
||||
}
|
||||
|
||||
var (
|
||||
job = eng.Job("container_inspect", vars["name"])
|
||||
job = eng.Job("inspect", vars["name"], "container")
|
||||
c, err = job.Stdout.AddEnv()
|
||||
)
|
||||
if err != nil {
|
||||
@@ -834,7 +758,7 @@ func postContainersAttach(eng *engine.Engine, version version.Version, w http.Re
|
||||
job.Stdout.Add(outStream)
|
||||
job.Stderr.Set(errStream)
|
||||
if err := job.Run(); err != nil {
|
||||
fmt.Fprintf(outStream, "Error attaching: %s\n", err)
|
||||
fmt.Fprintf(outStream, "Error: %s\n", err)
|
||||
|
||||
}
|
||||
return nil
|
||||
@@ -848,7 +772,7 @@ func wsContainersAttach(eng *engine.Engine, version version.Version, w http.Resp
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
if err := eng.Job("container_inspect", vars["name"]).Run(); err != nil {
|
||||
if err := eng.Job("inspect", vars["name"], "container").Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -864,7 +788,7 @@ func wsContainersAttach(eng *engine.Engine, version version.Version, w http.Resp
|
||||
job.Stdout.Add(ws)
|
||||
job.Stderr.Set(ws)
|
||||
if err := job.Run(); err != nil {
|
||||
utils.Errorf("Error attaching websocket: %s", err)
|
||||
utils.Errorf("Error: %s", err)
|
||||
}
|
||||
})
|
||||
h.ServeHTTP(w, r)
|
||||
@@ -876,11 +800,9 @@ func getContainersByName(eng *engine.Engine, version version.Version, w http.Res
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
var job = eng.Job("container_inspect", vars["name"])
|
||||
if version.LessThan("1.12") {
|
||||
job.SetenvBool("raw", true)
|
||||
}
|
||||
var job = eng.Job("inspect", vars["name"], "container")
|
||||
streamJSON(job, w, false)
|
||||
job.SetenvBool("conflict", true) //conflict=true to detect conflict between containers and images in the job
|
||||
return job.Run()
|
||||
}
|
||||
|
||||
@@ -888,11 +810,9 @@ func getImagesByName(eng *engine.Engine, version version.Version, w http.Respons
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
var job = eng.Job("image_inspect", vars["name"])
|
||||
if version.LessThan("1.12") {
|
||||
job.SetenvBool("raw", true)
|
||||
}
|
||||
var job = eng.Job("inspect", vars["name"], "image")
|
||||
streamJSON(job, w, false)
|
||||
job.SetenvBool("conflict", true) //conflict=true to detect conflict between containers and images in the job
|
||||
return job.Run()
|
||||
}
|
||||
|
||||
@@ -902,9 +822,9 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite
|
||||
}
|
||||
var (
|
||||
authEncoded = r.Header.Get("X-Registry-Auth")
|
||||
authConfig = ®istry.AuthConfig{}
|
||||
authConfig = &auth.AuthConfig{}
|
||||
configFileEncoded = r.Header.Get("X-Registry-Config")
|
||||
configFile = ®istry.ConfigFile{}
|
||||
configFile = &auth.ConfigFile{}
|
||||
job = eng.Job("build")
|
||||
)
|
||||
|
||||
@@ -917,7 +837,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite
|
||||
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
|
||||
// for a pull it is not an error if no auth was given
|
||||
// to increase compatibility with the existing api it is defaulting to be empty
|
||||
authConfig = ®istry.AuthConfig{}
|
||||
authConfig = &auth.AuthConfig{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -926,7 +846,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite
|
||||
if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil {
|
||||
// for a pull it is not an error if no auth was given
|
||||
// to increase compatibility with the existing api it is defaulting to be empty
|
||||
configFile = ®istry.ConfigFile{}
|
||||
configFile = &auth.ConfigFile{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -936,20 +856,12 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite
|
||||
} else {
|
||||
job.Stdout.Add(utils.NewWriteFlusher(w))
|
||||
}
|
||||
|
||||
if r.FormValue("forcerm") == "1" && version.GreaterThanOrEqualTo("1.12") {
|
||||
job.Setenv("rm", "1")
|
||||
} else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
|
||||
job.Setenv("rm", "1")
|
||||
} else {
|
||||
job.Setenv("rm", r.FormValue("rm"))
|
||||
}
|
||||
job.Stdin.Add(r.Body)
|
||||
job.Setenv("remote", r.FormValue("remote"))
|
||||
job.Setenv("t", r.FormValue("t"))
|
||||
job.Setenv("q", r.FormValue("q"))
|
||||
job.Setenv("nocache", r.FormValue("nocache"))
|
||||
job.Setenv("forcerm", r.FormValue("forcerm"))
|
||||
job.Setenv("rm", r.FormValue("rm"))
|
||||
job.SetenvJson("authConfig", authConfig)
|
||||
job.SetenvJson("configFile", configFile)
|
||||
|
||||
@@ -970,7 +882,7 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp
|
||||
|
||||
var copyData engine.Env
|
||||
|
||||
if contentType := r.Header.Get("Content-Type"); api.MatchesContentType(contentType, "application/json") {
|
||||
if contentType := r.Header.Get("Content-Type"); MatchesContentType(contentType, "application/json") {
|
||||
if err := copyData.Decode(r.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -981,9 +893,6 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp
|
||||
if copyData.Get("Resource") == "" {
|
||||
return fmt.Errorf("Path cannot be empty")
|
||||
}
|
||||
|
||||
origResource := copyData.Get("Resource")
|
||||
|
||||
if copyData.Get("Resource")[0] == '/' {
|
||||
copyData.Set("Resource", copyData.Get("Resource")[1:])
|
||||
}
|
||||
@@ -994,8 +903,6 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp
|
||||
utils.Errorf("%s", err.Error())
|
||||
if strings.Contains(err.Error(), "No such container") {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
} else if strings.Contains(err.Error(), "no such file or directory") {
|
||||
return fmt.Errorf("Could not find the file %s in container %s", origResource, vars["name"])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -1011,11 +918,6 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
|
||||
}
|
||||
|
||||
func ping(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
_, err := w.Write([]byte{'O', 'K'})
|
||||
return err
|
||||
}
|
||||
|
||||
func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, enableCors bool, dockerVersion version.Version) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// log the request
|
||||
@@ -1027,25 +929,25 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local
|
||||
|
||||
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
|
||||
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
|
||||
if len(userAgent) == 2 && !dockerVersion.Equal(version.Version(userAgent[1])) {
|
||||
if len(userAgent) == 2 && !dockerVersion.Equal(userAgent[1]) {
|
||||
utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion)
|
||||
}
|
||||
}
|
||||
version := version.Version(mux.Vars(r)["version"])
|
||||
if version == "" {
|
||||
version = api.APIVERSION
|
||||
version = APIVERSION
|
||||
}
|
||||
if enableCors {
|
||||
writeCorsHeaders(w, r)
|
||||
}
|
||||
|
||||
if version.GreaterThan(api.APIVERSION) {
|
||||
http.Error(w, fmt.Errorf("client and server don't have same version (client : %s, server: %s)", version, api.APIVERSION).Error(), http.StatusNotFound)
|
||||
if version.GreaterThan(APIVERSION) {
|
||||
http.Error(w, fmt.Errorf("client and server don't have same version (client : %s, server: %s)", version, APIVERSION).Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := handlerFunc(eng, version, w, r, mux.Vars(r)); err != nil {
|
||||
utils.Errorf("Error making handler: %s", err)
|
||||
utils.Errorf("Error: %s", err)
|
||||
httpError(w, err)
|
||||
}
|
||||
}
|
||||
@@ -1084,7 +986,6 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
|
||||
}
|
||||
m := map[string]map[string]HttpApiFunc{
|
||||
"GET": {
|
||||
"/_ping": ping,
|
||||
"/events": getEvents,
|
||||
"/info": getInfo,
|
||||
"/version": getVersion,
|
||||
@@ -1100,7 +1001,6 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
|
||||
"/containers/{name:.*}/changes": getContainersChanges,
|
||||
"/containers/{name:.*}/json": getContainersByName,
|
||||
"/containers/{name:.*}/top": getContainersTop,
|
||||
"/containers/{name:.*}/logs": getContainersLogs,
|
||||
"/containers/{name:.*}/attach/ws": wsContainersAttach,
|
||||
},
|
||||
"POST": {
|
||||
@@ -1108,13 +1008,12 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
|
||||
"/commit": postCommit,
|
||||
"/build": postBuild,
|
||||
"/images/create": postImagesCreate,
|
||||
"/images/{name:.*}/insert": postImagesInsert,
|
||||
"/images/load": postImagesLoad,
|
||||
"/images/{name:.*}/push": postImagesPush,
|
||||
"/images/{name:.*}/tag": postImagesTag,
|
||||
"/containers/create": postContainersCreate,
|
||||
"/containers/{name:.*}/kill": postContainersKill,
|
||||
"/containers/{name:.*}/pause": postContainersPause,
|
||||
"/containers/{name:.*}/unpause": postContainersUnpause,
|
||||
"/containers/{name:.*}/restart": postContainersRestart,
|
||||
"/containers/{name:.*}/start": postContainersStart,
|
||||
"/containers/{name:.*}/stop": postContainersStop,
|
||||
@@ -1230,9 +1129,9 @@ func changeGroup(addr string, nameOrGid string) error {
|
||||
|
||||
// ListenAndServe sets up the required http.Server and gets it listening for
|
||||
// each addr passed in and does protocol specific checking.
|
||||
func ListenAndServe(proto, addr string, job *engine.Job) error {
|
||||
var l net.Listener
|
||||
r, err := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version"))
|
||||
func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors bool, dockerVersion, socketGroup string) error {
|
||||
r, err := createRouter(eng, logging, enableCors, dockerVersion)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1247,58 +1146,22 @@ func ListenAndServe(proto, addr string, job *engine.Job) error {
|
||||
}
|
||||
}
|
||||
|
||||
var oldmask int
|
||||
if proto == "unix" {
|
||||
oldmask = syscall.Umask(0777)
|
||||
}
|
||||
|
||||
if job.GetenvBool("BufferRequests") {
|
||||
l, err = listenbuffer.NewListenBuffer(proto, addr, activationLock)
|
||||
} else {
|
||||
l, err = net.Listen(proto, addr)
|
||||
}
|
||||
|
||||
if proto == "unix" {
|
||||
syscall.Umask(oldmask)
|
||||
}
|
||||
l, err := listenbuffer.NewListenBuffer(proto, addr, activationLock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if proto != "unix" && (job.GetenvBool("Tls") || job.GetenvBool("TlsVerify")) {
|
||||
tlsCert := job.Getenv("TlsCert")
|
||||
tlsKey := job.Getenv("TlsKey")
|
||||
cert, err := tls.LoadX509KeyPair(tlsCert, tlsKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?",
|
||||
tlsCert, tlsKey, err)
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
NextProtos: []string{"http/1.1"},
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
if job.GetenvBool("TlsVerify") {
|
||||
certPool := x509.NewCertPool()
|
||||
file, err := ioutil.ReadFile(job.Getenv("TlsCa"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Couldn't read CA certificate: %s", err)
|
||||
}
|
||||
certPool.AppendCertsFromPEM(file)
|
||||
|
||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
tlsConfig.ClientCAs = certPool
|
||||
}
|
||||
l = tls.NewListener(l, tlsConfig)
|
||||
}
|
||||
|
||||
// Basic error and sanity checking
|
||||
switch proto {
|
||||
case "tcp":
|
||||
if !strings.HasPrefix(addr, "127.0.0.1") && !job.GetenvBool("TlsVerify") {
|
||||
if !strings.HasPrefix(addr, "127.0.0.1") {
|
||||
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
||||
}
|
||||
case "unix":
|
||||
socketGroup := job.Getenv("SocketGroup")
|
||||
if err := os.Chmod(addr, 0660); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if socketGroup != "" {
|
||||
if err := changeGroup(addr, socketGroup); err != nil {
|
||||
if socketGroup == "docker" {
|
||||
@@ -1309,9 +1172,6 @@ func ListenAndServe(proto, addr string, job *engine.Job) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := os.Chmod(addr, 0660); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("Invalid protocol format.")
|
||||
}
|
||||
@@ -1323,23 +1183,21 @@ func ListenAndServe(proto, addr string, job *engine.Job) error {
|
||||
// ServeApi loops through all of the protocols sent in to docker and spawns
|
||||
// off a go routine to setup a serving http.Server for each.
|
||||
func ServeApi(job *engine.Job) engine.Status {
|
||||
if len(job.Args) == 0 {
|
||||
return job.Errorf("usage: %s PROTO://ADDR [PROTO://ADDR ...]", job.Name)
|
||||
}
|
||||
var (
|
||||
protoAddrs = job.Args
|
||||
chErrors = make(chan error, len(protoAddrs))
|
||||
)
|
||||
activationLock = make(chan struct{})
|
||||
|
||||
if err := job.Eng.Register("acceptconnections", AcceptConnections); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
|
||||
for _, protoAddr := range protoAddrs {
|
||||
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
||||
if len(protoAddrParts) != 2 {
|
||||
return job.Errorf("usage: %s PROTO://ADDR [PROTO://ADDR ...]", job.Name)
|
||||
}
|
||||
go func() {
|
||||
log.Printf("Listening for HTTP on %s (%s)\n", protoAddrParts[0], protoAddrParts[1])
|
||||
chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], job)
|
||||
chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version"), job.Getenv("SocketGroup"))
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -1358,9 +1216,7 @@ func AcceptConnections(job *engine.Job) engine.Status {
|
||||
go systemd.SdNotify("READY=1")
|
||||
|
||||
// close the lock so the listeners start accepting connections
|
||||
if activationLock != nil {
|
||||
close(activationLock)
|
||||
}
|
||||
close(activationLock)
|
||||
|
||||
return engine.StatusOK
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
Victor Vieux <vieux@docker.com> (@vieux)
|
||||
Johan Euphrosine <proppy@google.com> (@proppy)
|
||||
@@ -1,353 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/dotcloud/docker/api"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
if r.HeaderMap.Get("Content-Type") != "application/json" {
|
||||
t.Fatalf("%#v\n", r)
|
||||
}
|
||||
}
|
||||
|
||||
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.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", "/containers/"+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 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")
|
||||
}
|
||||
if r.HeaderMap.Get("Content-Type") != "application/json" {
|
||||
t.Fatalf("%#v\n", r)
|
||||
}
|
||||
var stdout_json struct {
|
||||
Since int
|
||||
Until int
|
||||
}
|
||||
if err := json.Unmarshal(r.Body.Bytes(), &stdout_json); err != nil {
|
||||
t.Fatalf("%#v", err)
|
||||
}
|
||||
if stdout_json.Since != 1 {
|
||||
t.Fatalf("since != 1: %#v", stdout_json.Since)
|
||||
}
|
||||
if stdout_json.Until != 0 {
|
||||
t.Fatalf("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 serveRequest(method, target string, 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, api.APIVERSION, 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
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
This code provides helper functions for dealing with archive files.
|
||||
|
||||
**TODO**: Move this to either `pkg` or (if not possible) to `utils`.
|
||||
+48
-97
@@ -1,12 +1,14 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/bzip2"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/pkg/system"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -15,10 +17,6 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/dotcloud/docker/pkg/system"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -27,9 +25,7 @@ type (
|
||||
Compression int
|
||||
TarOptions struct {
|
||||
Includes []string
|
||||
Excludes []string
|
||||
Compression Compression
|
||||
NoLchown bool
|
||||
}
|
||||
)
|
||||
|
||||
@@ -44,27 +40,27 @@ const (
|
||||
Xz
|
||||
)
|
||||
|
||||
func IsArchive(header []byte) bool {
|
||||
compression := DetectCompression(header)
|
||||
if compression != Uncompressed {
|
||||
return true
|
||||
}
|
||||
r := tar.NewReader(bytes.NewBuffer(header))
|
||||
_, err := r.Next()
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func DetectCompression(source []byte) Compression {
|
||||
sourceLen := len(source)
|
||||
for compression, m := range map[Compression][]byte{
|
||||
Bzip2: {0x42, 0x5A, 0x68},
|
||||
Gzip: {0x1F, 0x8B, 0x08},
|
||||
Xz: {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00},
|
||||
} {
|
||||
if len(source) < len(m) {
|
||||
fail := false
|
||||
if len(m) > sourceLen {
|
||||
utils.Debugf("Len too short")
|
||||
continue
|
||||
}
|
||||
if bytes.Compare(m, source[:len(m)]) == 0 {
|
||||
i := 0
|
||||
for _, b := range m {
|
||||
if b != source[i] {
|
||||
fail = true
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if !fail {
|
||||
return compression
|
||||
}
|
||||
}
|
||||
@@ -78,24 +74,31 @@ func xzDecompress(archive io.Reader) (io.ReadCloser, error) {
|
||||
}
|
||||
|
||||
func DecompressStream(archive io.Reader) (io.ReadCloser, error) {
|
||||
buf := bufio.NewReader(archive)
|
||||
bs, err := buf.Peek(10)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
buf := make([]byte, 10)
|
||||
totalN := 0
|
||||
for totalN < 10 {
|
||||
n, err := archive.Read(buf[totalN:])
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil, fmt.Errorf("Tarball too short")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
totalN += n
|
||||
utils.Debugf("[tar autodetect] n: %d", n)
|
||||
}
|
||||
utils.Debugf("[tar autodetect] n: %v", bs)
|
||||
|
||||
compression := DetectCompression(bs)
|
||||
compression := DetectCompression(buf)
|
||||
wrap := io.MultiReader(bytes.NewReader(buf), archive)
|
||||
|
||||
switch compression {
|
||||
case Uncompressed:
|
||||
return ioutil.NopCloser(buf), nil
|
||||
return ioutil.NopCloser(wrap), nil
|
||||
case Gzip:
|
||||
return gzip.NewReader(buf)
|
||||
return gzip.NewReader(wrap)
|
||||
case Bzip2:
|
||||
return ioutil.NopCloser(bzip2.NewReader(buf)), nil
|
||||
return ioutil.NopCloser(bzip2.NewReader(wrap)), nil
|
||||
case Xz:
|
||||
return xzDecompress(buf)
|
||||
return xzDecompress(wrap)
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension())
|
||||
}
|
||||
@@ -191,7 +194,7 @@ func addTarFile(path, name string, tw *tar.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, Lchown bool) error {
|
||||
func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader) error {
|
||||
// hdr.Mode is in linux format, which we can use for sycalls,
|
||||
// but for os.Foo() calls we need the mode converted to os.FileMode,
|
||||
// so use hdrInfo.Mode() (they differ for e.g. setuid bits)
|
||||
@@ -252,7 +255,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
|
||||
return fmt.Errorf("Unhandled tar header type %d\n", hdr.Typeflag)
|
||||
}
|
||||
|
||||
if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil && Lchown {
|
||||
if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -273,11 +276,11 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
|
||||
ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
|
||||
// syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and
|
||||
if hdr.Typeflag != tar.TypeSymlink {
|
||||
if err := system.UtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform {
|
||||
if err := system.UtimesNano(path, ts); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := system.LUtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform {
|
||||
if err := system.LUtimesNano(path, ts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -287,7 +290,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
|
||||
// Tar creates an archive from the directory at `path`, and returns it as a
|
||||
// stream of bytes.
|
||||
func Tar(path string, compression Compression) (io.ReadCloser, error) {
|
||||
return TarWithOptions(path, &TarOptions{Compression: compression})
|
||||
return TarFilter(path, &TarOptions{Compression: compression})
|
||||
}
|
||||
|
||||
func escapeName(name string) string {
|
||||
@@ -306,9 +309,9 @@ func escapeName(name string) string {
|
||||
return string(escaped)
|
||||
}
|
||||
|
||||
// TarWithOptions creates an archive from the directory at `path`, only including files whose relative
|
||||
// paths are included in `options.Includes` (if non-nil) or not in `options.Excludes`.
|
||||
func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) {
|
||||
// Tar creates an archive from the directory at `path`, only including files whose relative
|
||||
// paths are included in `filter`. If `filter` is nil, then all files are included.
|
||||
func TarFilter(srcPath string, options *TarOptions) (io.ReadCloser, error) {
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
|
||||
compressWriter, err := CompressStream(pipeWriter, options.Compression)
|
||||
@@ -340,21 +343,6 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, exclude := range options.Excludes {
|
||||
matched, err := filepath.Match(exclude, relFilePath)
|
||||
if err != nil {
|
||||
utils.Errorf("Error matching: %s (pattern: %s)", relFilePath, exclude)
|
||||
return err
|
||||
}
|
||||
if matched {
|
||||
utils.Debugf("Skipping excluded path: %s", relFilePath)
|
||||
if f.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := addTarFile(filePath, relFilePath, tw); err != nil {
|
||||
utils.Debugf("Can't add file %s to tar: %s\n", srcPath, err)
|
||||
}
|
||||
@@ -378,13 +366,10 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
|
||||
// and unpacks it into the directory at `dest`.
|
||||
// and unpacks it into the directory at `path`.
|
||||
// The archive may be compressed with one of the following algorithms:
|
||||
// identity (uncompressed), gzip, bzip2, xz.
|
||||
// If `dest` does not exist, it is created unless there are multiple entries in `archive`.
|
||||
// In the latter case, an error is returned.
|
||||
// If `dest` is an existing file, it gets overwritten.
|
||||
// If `dest` is an existing directory, its files get merged (with overwrite for conflicting files).
|
||||
// FIXME: specify behavior when target path exists vs. doesn't exist.
|
||||
func Untar(archive io.Reader, dest string, options *TarOptions) error {
|
||||
if archive == nil {
|
||||
return fmt.Errorf("Empty archive")
|
||||
@@ -398,22 +383,7 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error {
|
||||
|
||||
tr := tar.NewReader(decompressedArchive)
|
||||
|
||||
var (
|
||||
dirs []*tar.Header
|
||||
create bool
|
||||
multipleEntries bool
|
||||
)
|
||||
|
||||
if fi, err := os.Lstat(dest); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
// destination does not exist, so it is assumed it has to be created.
|
||||
create = true
|
||||
} else if !fi.IsDir() {
|
||||
// destination exists and is not a directory, so it will be overwritten.
|
||||
create = true
|
||||
}
|
||||
var dirs []*tar.Header
|
||||
|
||||
// Iterate through the files in the archive.
|
||||
for {
|
||||
@@ -426,11 +396,6 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Return an error if destination needs to be created and there is more than 1 entry in the tar stream.
|
||||
if create && multipleEntries {
|
||||
return fmt.Errorf("Trying to untar an archive with multiple entries to an inexistant target `%s`: did you mean `%s` instead?", dest, filepath.Dir(dest))
|
||||
}
|
||||
|
||||
// Normalize name, for safety and for a simple is-root check
|
||||
hdr.Name = filepath.Clean(hdr.Name)
|
||||
|
||||
@@ -439,28 +404,20 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error {
|
||||
parent := filepath.Dir(hdr.Name)
|
||||
parentPath := filepath.Join(dest, parent)
|
||||
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
|
||||
err = os.MkdirAll(parentPath, 0777)
|
||||
err = os.MkdirAll(parentPath, 600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var path string
|
||||
if create {
|
||||
path = dest // we are renaming hdr.Name to dest
|
||||
} else {
|
||||
path = filepath.Join(dest, hdr.Name)
|
||||
}
|
||||
path := filepath.Join(dest, hdr.Name)
|
||||
|
||||
// If path exits we almost always just want to remove and replace it
|
||||
// The only exception is when it is a directory *and* the file from
|
||||
// the layer is also a directory. Then we want to merge them (i.e.
|
||||
// just apply the metadata from the layer).
|
||||
if fi, err := os.Lstat(path); err == nil {
|
||||
if fi.IsDir() && hdr.Name == "." {
|
||||
continue
|
||||
}
|
||||
if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return err
|
||||
@@ -468,13 +425,10 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := createTarFile(path, dest, hdr, tr, options == nil || !options.NoLchown); err != nil {
|
||||
if err := createTarFile(path, dest, hdr, tr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Successfully added an entry. Predicting multiple entries for next iteration (not current one).
|
||||
multipleEntries = true
|
||||
|
||||
// Directory mtimes must be handled at the end to avoid further
|
||||
// file creation in them to modify the directory mtime
|
||||
if hdr.Typeflag == tar.TypeDir {
|
||||
@@ -498,7 +452,7 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error {
|
||||
// TarUntar aborts and returns the error.
|
||||
func TarUntar(src string, dst string) error {
|
||||
utils.Debugf("TarUntar(%s %s)", src, dst)
|
||||
archive, err := TarWithOptions(src, &TarOptions{Compression: Uncompressed})
|
||||
archive, err := TarFilter(src, &TarOptions{Compression: Uncompressed})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -663,9 +617,6 @@ func NewTempArchive(src Archive, dir string) (*TempArchive, error) {
|
||||
if _, err := io.Copy(f, src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = f.Sync(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := f.Seek(0, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+20
-123
@@ -3,6 +3,7 @@ package archive
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -10,8 +11,6 @@ import (
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
)
|
||||
|
||||
func TestCmdStreamLargeStderr(t *testing.T) {
|
||||
@@ -63,8 +62,8 @@ func TestCmdStreamGood(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error) {
|
||||
archive, err := TarWithOptions(origin, options)
|
||||
func tarUntar(t *testing.T, origin string, compression Compression) error {
|
||||
archive, err := Tar(origin, compression)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -72,29 +71,37 @@ func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error
|
||||
|
||||
buf := make([]byte, 10)
|
||||
if _, err := archive.Read(buf); err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
wrap := io.MultiReader(bytes.NewReader(buf), archive)
|
||||
|
||||
detectedCompression := DetectCompression(buf)
|
||||
compression := options.Compression
|
||||
if detectedCompression.Extension() != compression.Extension() {
|
||||
return nil, fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
|
||||
return fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
|
||||
}
|
||||
|
||||
tmp, err := ioutil.TempDir("", "docker-test-untar")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
if err := Untar(wrap, tmp, nil); err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
if _, err := os.Stat(tmp); err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
return ChangesDirs(origin, tmp)
|
||||
changes, err := ChangesDirs(origin, tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(changes) != 0 {
|
||||
t.Fatalf("Unexpected differences after tarUntar: %v", changes)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestTarUntar(t *testing.T) {
|
||||
@@ -114,90 +121,9 @@ func TestTarUntar(t *testing.T) {
|
||||
Uncompressed,
|
||||
Gzip,
|
||||
} {
|
||||
changes, err := tarUntar(t, origin, &TarOptions{
|
||||
Compression: c,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
if err := tarUntar(t, origin, c); err != nil {
|
||||
t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
|
||||
}
|
||||
|
||||
if len(changes) != 0 {
|
||||
t.Fatalf("Unexpected differences after tarUntar: %v", changes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTarWithOptions(t *testing.T) {
|
||||
origin, err := ioutil.TempDir("", "docker-test-untar-origin")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(origin)
|
||||
if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
opts *TarOptions
|
||||
numChanges int
|
||||
}{
|
||||
{&TarOptions{Includes: []string{"1"}}, 1},
|
||||
{&TarOptions{Excludes: []string{"2"}}, 1},
|
||||
}
|
||||
for _, testCase := range cases {
|
||||
changes, err := tarUntar(t, origin, testCase.opts)
|
||||
if err != nil {
|
||||
t.Fatalf("Error tar/untar when testing inclusion/exclusion: %s", err)
|
||||
}
|
||||
if len(changes) != testCase.numChanges {
|
||||
t.Errorf("Expected %d changes, got %d for %+v:",
|
||||
testCase.numChanges, len(changes), testCase.opts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTarUntarFile(t *testing.T) {
|
||||
origin, err := ioutil.TempDir("", "docker-test-untar-origin-file")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(origin)
|
||||
|
||||
if err := os.MkdirAll(path.Join(origin, "before"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(path.Join(origin, "after"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(origin, "before", "file"), []byte("hello world"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(origin, "after", "file2"), []byte("please overwrite me"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tar, err := TarWithOptions(path.Join(origin, "before"), &TarOptions{Compression: Uncompressed, Includes: []string{"file"}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := Untar(tar, path.Join(origin, "after", "file2"), nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
catCmd := exec.Command("cat", path.Join(origin, "after", "file2"))
|
||||
out, err := CmdStream(catCmd, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start command: %s", err)
|
||||
}
|
||||
if output, err := ioutil.ReadAll(out); err != nil {
|
||||
t.Error(err)
|
||||
} else if string(output) != "hello world" {
|
||||
t.Fatalf("Expected 'hello world', got '%s'", output)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,37 +132,8 @@ func TestTarUntarFile(t *testing.T) {
|
||||
// Failing prevents the archives from being uncompressed during ADD
|
||||
func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) {
|
||||
hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader}
|
||||
err := createTarFile("pax_global_header", "some_dir", &hdr, nil, true)
|
||||
err := createTarFile("pax_global_header", "some_dir", &hdr, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Some tar have both GNU specific (huge uid) and Ustar specific (long name) things.
|
||||
// Not supposed to happen (should use PAX instead of Ustar for long name) but it does and it should still work.
|
||||
func TestUntarUstarGnuConflict(t *testing.T) {
|
||||
f, err := os.Open("testdata/broken.tar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
found := false
|
||||
tr := tar.NewReader(f)
|
||||
// Iterate through the files in the archive.
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
// end of tar archive
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if hdr.Name == "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("%s not found in the archive", "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm")
|
||||
}
|
||||
}
|
||||
|
||||
+13
-25
@@ -3,16 +3,15 @@ package archive
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/pkg/system"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/dotcloud/docker/pkg/system"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
)
|
||||
|
||||
type ChangeType int
|
||||
@@ -294,23 +293,13 @@ func collectFileInfo(sourceDir string) (*FileInfo, error) {
|
||||
|
||||
// Compare two directories and generate an array of Change objects describing the changes
|
||||
func ChangesDirs(newDir, oldDir string) ([]Change, error) {
|
||||
var (
|
||||
oldRoot, newRoot *FileInfo
|
||||
err1, err2 error
|
||||
errs = make(chan error, 2)
|
||||
)
|
||||
go func() {
|
||||
oldRoot, err1 = collectFileInfo(oldDir)
|
||||
errs <- err1
|
||||
}()
|
||||
go func() {
|
||||
newRoot, err2 = collectFileInfo(newDir)
|
||||
errs <- err2
|
||||
}()
|
||||
for i := 0; i < 2; i++ {
|
||||
if err := <-errs; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oldRoot, err := collectFileInfo(oldDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newRoot, err := collectFileInfo(newDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newRoot.Changes(oldRoot), nil
|
||||
@@ -352,13 +341,12 @@ func ExportChanges(dir string, changes []Change) (Archive, error) {
|
||||
whiteOutDir := filepath.Dir(change.Path)
|
||||
whiteOutBase := filepath.Base(change.Path)
|
||||
whiteOut := filepath.Join(whiteOutDir, ".wh."+whiteOutBase)
|
||||
timestamp := time.Now()
|
||||
hdr := &tar.Header{
|
||||
Name: whiteOut[1:],
|
||||
Size: 0,
|
||||
ModTime: timestamp,
|
||||
AccessTime: timestamp,
|
||||
ChangeTime: timestamp,
|
||||
ModTime: time.Now(),
|
||||
AccessTime: time.Now(),
|
||||
ChangeTime: time.Now(),
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
utils.Debugf("Can't write whiteout header: %s\n", err)
|
||||
|
||||
@@ -138,7 +138,7 @@ func mutateSampleDir(t *testing.T, root string) {
|
||||
}
|
||||
|
||||
// Rewrite a file
|
||||
if err := ioutil.WriteFile(path.Join(root, "file2"), []byte("fileNN\n"), 0777); err != nil {
|
||||
if err := ioutil.WriteFile(path.Join(root, "file2"), []byte("fileN\n"), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -146,12 +146,12 @@ func mutateSampleDir(t *testing.T, root string) {
|
||||
if err := os.RemoveAll(path.Join(root, "file3")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(root, "file3"), []byte("fileMM\n"), 0404); err != nil {
|
||||
if err := ioutil.WriteFile(path.Join(root, "file3"), []byte("fileM\n"), 0404); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Touch file
|
||||
if err := os.Chtimes(path.Join(root, "file4"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil {
|
||||
if err := os.Chtimes(path.Join(root, "file4"), time.Now(), time.Now()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ func mutateSampleDir(t *testing.T, root string) {
|
||||
}
|
||||
|
||||
// Touch dir
|
||||
if err := os.Chtimes(path.Join(root, "dir3"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil {
|
||||
if err := os.Chtimes(path.Join(root, "dir3"), time.Now(), time.Now()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
+14
-5
@@ -2,14 +2,14 @@ package archive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes.
|
||||
@@ -18,6 +18,15 @@ import (
|
||||
func mkdev(major int64, minor int64) uint32 {
|
||||
return uint32(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff))
|
||||
}
|
||||
func timeToTimespec(time time.Time) (ts syscall.Timespec) {
|
||||
if time.IsZero() {
|
||||
// Return UTIME_OMIT special value
|
||||
ts.Sec = 0
|
||||
ts.Nsec = ((1 << 30) - 2)
|
||||
return
|
||||
}
|
||||
return syscall.NsecToTimespec(time.UnixNano())
|
||||
}
|
||||
|
||||
// ApplyLayer parses a diff in the standard layer format from `layer`, and
|
||||
// applies it to the directory `dest`.
|
||||
@@ -59,7 +68,7 @@ func ApplyLayer(dest string, layer ArchiveReader) error {
|
||||
parent := filepath.Dir(hdr.Name)
|
||||
parentPath := filepath.Join(dest, parent)
|
||||
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
|
||||
err = os.MkdirAll(parentPath, 0600)
|
||||
err = os.MkdirAll(parentPath, 600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -80,7 +89,7 @@ func ApplyLayer(dest string, layer ArchiveReader) error {
|
||||
}
|
||||
defer os.RemoveAll(aufsTempdir)
|
||||
}
|
||||
if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, true); err != nil {
|
||||
if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -127,7 +136,7 @@ func ApplyLayer(dest string, layer ArchiveReader) error {
|
||||
srcData = tmpFile
|
||||
}
|
||||
|
||||
if err := createTarFile(path, dest, srcHdr, srcData, true); err != nil {
|
||||
if err := createTarFile(path, dest, srcHdr, srcData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -1,16 +0,0 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func timeToTimespec(time time.Time) (ts syscall.Timespec) {
|
||||
if time.IsZero() {
|
||||
// Return UTIME_OMIT special value
|
||||
ts.Sec = 0
|
||||
ts.Nsec = ((1 << 30) - 2)
|
||||
return
|
||||
}
|
||||
return syscall.NsecToTimespec(time.UnixNano())
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// +build !linux
|
||||
|
||||
package archive
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func timeToTimespec(time time.Time) (ts syscall.Timespec) {
|
||||
nsec := int64(0)
|
||||
if !time.IsZero() {
|
||||
nsec = time.UnixNano()
|
||||
}
|
||||
return syscall.NsecToTimespec(nsec)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
Sam Alba <sam@dotcloud.com> (@samalba)
|
||||
Joffrey Fuhrer <joffrey@dotcloud.com> (@shin-)
|
||||
Ken Cochrane <ken@dotcloud.com> (@kencochrane)
|
||||
@@ -1,17 +1,16 @@
|
||||
package registry
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/dotcloud/docker/utils"
|
||||
)
|
||||
|
||||
// Where we store the config file
|
||||
@@ -153,16 +152,10 @@ func SaveConfig(configFile *ConfigFile) error {
|
||||
// try to register/login to the registry server
|
||||
func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) {
|
||||
var (
|
||||
status string
|
||||
reqBody []byte
|
||||
err error
|
||||
client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
},
|
||||
CheckRedirect: AddRequiredHeadersToRedirectedRequests,
|
||||
}
|
||||
status string
|
||||
reqBody []byte
|
||||
err error
|
||||
client = &http.Client{}
|
||||
reqStatusCode = 0
|
||||
serverAddress = authConfig.ServerAddress
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
package registry
|
||||
package auth
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
+108
-206
@@ -1,4 +1,4 @@
|
||||
package server
|
||||
package docker
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
@@ -6,6 +6,11 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/auth"
|
||||
"github.com/dotcloud/docker/registry"
|
||||
"github.com/dotcloud/docker/runconfig"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
@@ -16,17 +21,6 @@ import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/daemon"
|
||||
"github.com/dotcloud/docker/nat"
|
||||
"github.com/dotcloud/docker/pkg/symlink"
|
||||
"github.com/dotcloud/docker/pkg/system"
|
||||
"github.com/dotcloud/docker/registry"
|
||||
"github.com/dotcloud/docker/runconfig"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -40,8 +34,8 @@ type BuildFile interface {
|
||||
}
|
||||
|
||||
type buildFile struct {
|
||||
daemon *daemon.Daemon
|
||||
srv *Server
|
||||
runtime *Runtime
|
||||
srv *Server
|
||||
|
||||
image string
|
||||
maintainer string
|
||||
@@ -53,10 +47,9 @@ type buildFile struct {
|
||||
verbose bool
|
||||
utilizeCache bool
|
||||
rm bool
|
||||
forceRm bool
|
||||
|
||||
authConfig *registry.AuthConfig
|
||||
configFile *registry.ConfigFile
|
||||
authConfig *auth.AuthConfig
|
||||
configFile *auth.ConfigFile
|
||||
|
||||
tmpContainers map[string]struct{}
|
||||
tmpImages map[string]struct{}
|
||||
@@ -71,20 +64,19 @@ type buildFile struct {
|
||||
|
||||
func (b *buildFile) clearTmp(containers map[string]struct{}) {
|
||||
for c := range containers {
|
||||
tmp := b.daemon.Get(c)
|
||||
if err := b.daemon.Destroy(tmp); err != nil {
|
||||
tmp := b.runtime.Get(c)
|
||||
if err := b.runtime.Destroy(tmp); err != nil {
|
||||
fmt.Fprintf(b.outStream, "Error removing intermediate container %s: %s\n", utils.TruncateID(c), err.Error())
|
||||
} else {
|
||||
delete(containers, c)
|
||||
fmt.Fprintf(b.outStream, "Removing intermediate container %s\n", utils.TruncateID(c))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdFrom(name string) error {
|
||||
image, err := b.daemon.Repositories().LookupImage(name)
|
||||
image, err := b.runtime.repositories.LookupImage(name)
|
||||
if err != nil {
|
||||
if b.daemon.Graph().IsNotExist(err) {
|
||||
if b.runtime.graph.IsNotExist(err) {
|
||||
remote, tag := utils.ParseRepositoryTag(name)
|
||||
pullRegistryAuth := b.authConfig
|
||||
if len(b.configFile.Configs) > 0 {
|
||||
@@ -104,7 +96,7 @@ func (b *buildFile) CmdFrom(name string) error {
|
||||
if err := job.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
image, err = b.daemon.Repositories().LookupImage(name)
|
||||
image, err = b.runtime.repositories.LookupImage(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -118,18 +110,13 @@ func (b *buildFile) CmdFrom(name string) error {
|
||||
b.config = image.Config
|
||||
}
|
||||
if b.config.Env == nil || len(b.config.Env) == 0 {
|
||||
b.config.Env = append(b.config.Env, "HOME=/", "PATH="+daemon.DefaultPathEnv)
|
||||
b.config.Env = append(b.config.Env, "HOME=/", "PATH="+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{}
|
||||
|
||||
for n, step := range onBuildTriggers {
|
||||
for n, step := range b.config.OnBuild {
|
||||
splitStep := strings.Split(step, " ")
|
||||
stepInstruction := strings.ToUpper(strings.Trim(splitStep[0], " "))
|
||||
switch stepInstruction {
|
||||
@@ -142,6 +129,7 @@ func (b *buildFile) CmdFrom(name string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
b.config.OnBuild = []string{}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -316,22 +304,8 @@ func (b *buildFile) CmdEntrypoint(args string) error {
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdExpose(args string) error {
|
||||
portsTab := strings.Split(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
|
||||
|
||||
ports := strings.Split(args, " ")
|
||||
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
|
||||
}
|
||||
|
||||
@@ -345,18 +319,11 @@ func (b *buildFile) CmdInsert(args string) error {
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdCopy(args string) error {
|
||||
return b.runContextCommand(args, false, false, "COPY")
|
||||
return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdWorkdir(workdir string) error {
|
||||
if workdir[0] == '/' {
|
||||
b.config.WorkingDir = workdir
|
||||
} else {
|
||||
if b.config.WorkingDir == "" {
|
||||
b.config.WorkingDir = "/"
|
||||
}
|
||||
b.config.WorkingDir = filepath.Join(b.config.WorkingDir, workdir)
|
||||
}
|
||||
b.config.WorkingDir = workdir
|
||||
return b.commit("", b.config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
|
||||
}
|
||||
|
||||
@@ -404,34 +371,15 @@ func (b *buildFile) checkPathForAddition(orig string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) addContext(container *daemon.Container, orig, dest string, decompress bool) error {
|
||||
func (b *buildFile) addContext(container *Container, orig, dest string, remote bool) error {
|
||||
var (
|
||||
err error
|
||||
destExists = true
|
||||
origPath = path.Join(b.contextPath, orig)
|
||||
destPath = path.Join(container.RootfsPath(), dest)
|
||||
origPath = path.Join(b.contextPath, orig)
|
||||
destPath = path.Join(container.BasefsPath(), 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 == "." {
|
||||
if strings.HasSuffix(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) {
|
||||
@@ -441,50 +389,48 @@ func (b *buildFile) addContext(container *daemon.Container, orig, dest string, d
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
return copyAsDirectory(origPath, destPath, destExists)
|
||||
if err := archive.CopyWithTar(origPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
|
||||
// If we are adding a remote file, do not try to untar it
|
||||
if !remote {
|
||||
// try to successfully untar the orig
|
||||
if err := archive.UntarPath(origPath, tarDest); err == nil {
|
||||
return nil
|
||||
} else if err != io.EOF {
|
||||
utils.Debugf("Couldn't untar %s to %s: %s", origPath, tarDest, err)
|
||||
}
|
||||
utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err)
|
||||
}
|
||||
|
||||
// If that fails, just copy it as a regular file
|
||||
// but do not use all the magic path handling for the tar path
|
||||
if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := archive.CopyWithTar(origPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resPath := destPath
|
||||
if destExists && destStat.IsDir() {
|
||||
resPath = path.Join(destPath, path.Base(origPath))
|
||||
}
|
||||
|
||||
return fixPermissions(resPath, 0, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) runContextCommand(args string, allowRemote bool, allowDecompression bool, cmdName string) error {
|
||||
func (b *buildFile) CmdAdd(args string) error {
|
||||
if b.context == nil {
|
||||
return fmt.Errorf("No context given. Impossible to use %s", cmdName)
|
||||
return fmt.Errorf("No context given. Impossible to use ADD")
|
||||
}
|
||||
tmp := strings.SplitN(args, " ", 2)
|
||||
if len(tmp) != 2 {
|
||||
return fmt.Errorf("Invalid %s format", cmdName)
|
||||
return fmt.Errorf("Invalid ADD format")
|
||||
}
|
||||
|
||||
orig, err := b.ReplaceEnvMatches(strings.Trim(tmp[0], " \t"))
|
||||
@@ -498,8 +444,7 @@ func (b *buildFile) runContextCommand(args string, allowRemote bool, allowDecomp
|
||||
}
|
||||
|
||||
cmd := b.config.Cmd
|
||||
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, orig, dest)}
|
||||
defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
|
||||
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
|
||||
b.config.Image = b.image
|
||||
|
||||
var (
|
||||
@@ -507,56 +452,37 @@ func (b *buildFile) runContextCommand(args string, allowRemote bool, allowDecomp
|
||||
destPath = dest
|
||||
remoteHash string
|
||||
isRemote bool
|
||||
decompress = true
|
||||
)
|
||||
|
||||
isRemote = utils.IsURL(orig)
|
||||
if isRemote && !allowRemote {
|
||||
return fmt.Errorf("Source can't be an URL for %s", cmdName)
|
||||
} else if utils.IsURL(orig) {
|
||||
// Initiate the download
|
||||
if utils.IsURL(orig) {
|
||||
isRemote = true
|
||||
resp, err := utils.Download(orig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a tmp dir
|
||||
tmpDirName, err := ioutil.TempDir(b.contextPath, "docker-remote")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
defer os.RemoveAll(tmpDirName)
|
||||
|
||||
// Download and dump result to tmp file
|
||||
if _, err := io.Copy(tmpFile, resp.Body); err != nil {
|
||||
if _, err = io.Copy(tmpFile, resp.Body); err != nil {
|
||||
tmpFile.Close()
|
||||
return err
|
||||
}
|
||||
tmpFile.Close()
|
||||
|
||||
// Remove the mtime of the newly created tmp file
|
||||
if err := system.UtimesNano(tmpFileName, make([]syscall.Timespec, 2)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
|
||||
tmpFile.Close()
|
||||
|
||||
// Process the checksum
|
||||
r, err := archive.Tar(tmpFileName, archive.Uncompressed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tarSum := &utils.TarSum{Reader: r, DisableCompression: true}
|
||||
if _, err := io.Copy(ioutil.Discard, tarSum); err != nil {
|
||||
return err
|
||||
}
|
||||
tarSum := utils.TarSum{Reader: r, DisableCompression: true}
|
||||
remoteHash = tarSum.Sum(nil)
|
||||
r.Close()
|
||||
|
||||
@@ -616,7 +542,7 @@ func (b *buildFile) runContextCommand(args string, allowRemote bool, allowDecomp
|
||||
hash = "file:" + h
|
||||
}
|
||||
}
|
||||
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, hash, dest)}
|
||||
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", hash, dest)}
|
||||
hit, err := b.probeCache()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -627,8 +553,8 @@ func (b *buildFile) runContextCommand(args string, allowRemote bool, allowDecomp
|
||||
}
|
||||
}
|
||||
|
||||
// Create the container
|
||||
container, _, err := b.daemon.Create(b.config, "")
|
||||
// Create the container and start it
|
||||
container, _, err := b.runtime.Create(b.config, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -639,31 +565,53 @@ func (b *buildFile) runContextCommand(args string, allowRemote bool, allowDecomp
|
||||
}
|
||||
defer container.Unmount()
|
||||
|
||||
if !allowDecompression || isRemote {
|
||||
decompress = false
|
||||
}
|
||||
if err := b.addContext(container, origPath, destPath, decompress); err != nil {
|
||||
if err := b.addContext(container, origPath, destPath, isRemote); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := b.commit(container.ID, cmd, fmt.Sprintf("%s %s in %s", cmdName, orig, dest)); err != nil {
|
||||
if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
|
||||
return err
|
||||
}
|
||||
b.config.Cmd = cmd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) CmdAdd(args string) error {
|
||||
return b.runContextCommand(args, true, true, "ADD")
|
||||
type StdoutFormater struct {
|
||||
io.Writer
|
||||
*utils.StreamFormatter
|
||||
}
|
||||
|
||||
func (b *buildFile) create() (*daemon.Container, error) {
|
||||
func (sf *StdoutFormater) Write(buf []byte) (int, error) {
|
||||
formattedBuf := sf.StreamFormatter.FormatStream(string(buf))
|
||||
n, err := sf.Writer.Write(formattedBuf)
|
||||
if n != len(formattedBuf) {
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
return len(buf), err
|
||||
}
|
||||
|
||||
type StderrFormater struct {
|
||||
io.Writer
|
||||
*utils.StreamFormatter
|
||||
}
|
||||
|
||||
func (sf *StderrFormater) Write(buf []byte) (int, error) {
|
||||
formattedBuf := sf.StreamFormatter.FormatStream("\033[91m" + string(buf) + "\033[0m")
|
||||
n, err := sf.Writer.Write(formattedBuf)
|
||||
if n != len(formattedBuf) {
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
return len(buf), err
|
||||
}
|
||||
|
||||
func (b *buildFile) create() (*Container, error) {
|
||||
if b.image == "" {
|
||||
return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
b.config.Image = b.image
|
||||
|
||||
// Create the container
|
||||
c, _, err := b.daemon.Create(b.config, "")
|
||||
// Create the container and start it
|
||||
c, _, err := b.runtime.Create(b.config, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -677,11 +625,12 @@ func (b *buildFile) create() (*daemon.Container, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (b *buildFile) run(c *daemon.Container) error {
|
||||
func (b *buildFile) run(c *Container) error {
|
||||
var errCh chan error
|
||||
|
||||
if b.verbose {
|
||||
errCh = utils.Go(func() error {
|
||||
return <-b.daemon.Attach(c, nil, nil, b.outStream, b.errStream)
|
||||
return <-c.Attach(nil, nil, b.outStream, b.errStream)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -697,7 +646,7 @@ func (b *buildFile) run(c *daemon.Container) error {
|
||||
}
|
||||
|
||||
// Wait for it to finish
|
||||
if ret, _ := c.State.WaitStop(-1 * time.Second); ret != 0 {
|
||||
if ret := c.Wait(); ret != 0 {
|
||||
err := &utils.JSONError{
|
||||
Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.config.Cmd, ret),
|
||||
Code: ret,
|
||||
@@ -727,7 +676,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
container, warnings, err := b.daemon.Create(b.config, "")
|
||||
container, warnings, err := b.runtime.Create(b.config, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -737,13 +686,13 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
||||
b.tmpContainers[container.ID] = struct{}{}
|
||||
fmt.Fprintf(b.outStream, " ---> Running in %s\n", utils.TruncateID(container.ID))
|
||||
id = container.ID
|
||||
|
||||
if err := container.Mount(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer container.Unmount()
|
||||
}
|
||||
container := b.daemon.Get(id)
|
||||
|
||||
container := b.runtime.Get(id)
|
||||
if container == nil {
|
||||
return fmt.Errorf("An error occured while creating the container")
|
||||
}
|
||||
@@ -752,7 +701,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
||||
autoConfig := *b.config
|
||||
autoConfig.Cmd = autoCmd
|
||||
// Commit the container
|
||||
image, err := b.daemon.Commit(container, "", "", "", b.maintainer, true, &autoConfig)
|
||||
image, err := b.runtime.Commit(container, "", "", "", b.maintainer, &autoConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -762,7 +711,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
||||
}
|
||||
|
||||
// Long lines can be split with a backslash
|
||||
var lineContinuation = regexp.MustCompile(`\\\s*\n`)
|
||||
var lineContinuation = regexp.MustCompile(`\s*\\\s*\n`)
|
||||
|
||||
func (b *buildFile) Build(context io.Reader) (string, error) {
|
||||
tmpdirPath, err := ioutil.TempDir("", "docker-build")
|
||||
@@ -793,27 +742,26 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
|
||||
if len(fileBytes) == 0 {
|
||||
return "", ErrDockerfileEmpty
|
||||
}
|
||||
var (
|
||||
dockerfile = lineContinuation.ReplaceAllString(stripComments(fileBytes), "")
|
||||
stepN = 0
|
||||
)
|
||||
dockerfile := string(fileBytes)
|
||||
dockerfile = lineContinuation.ReplaceAllString(dockerfile, "")
|
||||
stepN := 0
|
||||
for _, line := range strings.Split(dockerfile, "\n") {
|
||||
line = strings.Trim(strings.Replace(line, "\t", " ", -1), " \t\r\n")
|
||||
if len(line) == 0 {
|
||||
// Skip comments and empty line
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
if err := b.BuildStep(fmt.Sprintf("%d", stepN), line); err != nil {
|
||||
if b.forceRm {
|
||||
b.clearTmp(b.tmpContainers)
|
||||
}
|
||||
return "", err
|
||||
} else if b.rm {
|
||||
b.clearTmp(b.tmpContainers)
|
||||
}
|
||||
stepN += 1
|
||||
|
||||
}
|
||||
if b.image != "" {
|
||||
fmt.Fprintf(b.outStream, "Successfully built %s\n", utils.TruncateID(b.image))
|
||||
if b.rm {
|
||||
b.clearTmp(b.tmpContainers)
|
||||
}
|
||||
return b.image, nil
|
||||
}
|
||||
return "", fmt.Errorf("No image was generated. This may be because the Dockerfile does not, like, do anything.\n")
|
||||
@@ -844,54 +792,9 @@ func (b *buildFile) BuildStep(name, expression string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func stripComments(raw []byte) string {
|
||||
var (
|
||||
out []string
|
||||
lines = strings.Split(string(raw), "\n")
|
||||
)
|
||||
for _, l := range lines {
|
||||
if len(l) == 0 || l[0] == '#' {
|
||||
continue
|
||||
}
|
||||
out = append(out, l)
|
||||
}
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
|
||||
func copyAsDirectory(source, destination string, destinationExists bool) error {
|
||||
if err := archive.CopyWithTar(source, destination); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if destinationExists {
|
||||
files, err := ioutil.ReadDir(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if err := fixPermissions(filepath.Join(destination, file.Name()), 0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fixPermissions(destination, 0, 0)
|
||||
}
|
||||
|
||||
func fixPermissions(destination string, uid, gid int) error {
|
||||
return filepath.Walk(destination, func(path string, info os.FileInfo, err error) error {
|
||||
if err := os.Lchown(path, uid, gid); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, forceRm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *registry.AuthConfig, authConfigFile *registry.ConfigFile) BuildFile {
|
||||
func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *auth.AuthConfig, authConfigFile *auth.ConfigFile) BuildFile {
|
||||
return &buildFile{
|
||||
daemon: srv.daemon,
|
||||
runtime: srv.runtime,
|
||||
srv: srv,
|
||||
config: &runconfig.Config{},
|
||||
outStream: outStream,
|
||||
@@ -901,7 +804,6 @@ func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeC
|
||||
verbose: verbose,
|
||||
utilizeCache: utilizeCache,
|
||||
rm: rm,
|
||||
forceRm: forceRm,
|
||||
sf: sf,
|
||||
authConfig: auth,
|
||||
configFile: authConfigFile,
|
||||
+13
-48
@@ -1,37 +1,21 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/dotcloud/docker/api"
|
||||
apiserver "github.com/dotcloud/docker/api/server"
|
||||
"github.com/dotcloud/docker/daemon/networkdriver/bridge"
|
||||
"github.com/dotcloud/docker/dockerversion"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/registry"
|
||||
"github.com/dotcloud/docker/server"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
|
||||
"github.com/dotcloud/docker"
|
||||
"github.com/dotcloud/docker/api"
|
||||
"github.com/dotcloud/docker/networkdriver/lxc"
|
||||
)
|
||||
|
||||
func Register(eng *engine.Engine) error {
|
||||
if err := daemon(eng); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := remote(eng); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := eng.Register("version", dockerVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
return registry.NewService().Install(eng)
|
||||
func Register(eng *engine.Engine) {
|
||||
daemon(eng)
|
||||
remote(eng)
|
||||
}
|
||||
|
||||
// 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)
|
||||
func remote(eng *engine.Engine) {
|
||||
eng.Register("serveapi", api.ServeApi)
|
||||
}
|
||||
|
||||
// daemon: a default execution and storage backend for Docker on Linux,
|
||||
@@ -49,27 +33,8 @@ func remote(eng *engine.Engine) error {
|
||||
//
|
||||
// These components should be broken off into plugins of their own.
|
||||
//
|
||||
func daemon(eng *engine.Engine) error {
|
||||
if err := eng.Register("initserver", server.InitServer); err != nil {
|
||||
return err
|
||||
}
|
||||
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.Set("GitCommit", dockerversion.GITCOMMIT)
|
||||
v.Set("GoVersion", runtime.Version())
|
||||
v.Set("Os", runtime.GOOS)
|
||||
v.Set("Arch", runtime.GOARCH)
|
||||
if kernelVersion, err := utils.GetKernelVersion(); err == nil {
|
||||
v.Set("KernelVersion", kernelVersion.String())
|
||||
}
|
||||
if _, err := v.WriteTo(job.Stdout); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
return engine.StatusOK
|
||||
func daemon(eng *engine.Engine) {
|
||||
eng.Register("initserver", docker.InitServer)
|
||||
eng.Register("init_networkdriver", lxc.InitDriver)
|
||||
eng.Register("version", docker.GetVersion)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/runconfig"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func parse(t *testing.T, args string) (*runconfig.Config, *runconfig.HostConfig, error) {
|
||||
config, hostConfig, _, err := runconfig.Parse(strings.Split(args+" ubuntu bash", " "), nil)
|
||||
return config, hostConfig, err
|
||||
}
|
||||
|
||||
func mustParse(t *testing.T, args string) (*runconfig.Config, *runconfig.HostConfig) {
|
||||
config, hostConfig, err := parse(t, args)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return config, hostConfig
|
||||
}
|
||||
|
||||
func TestParseRunLinks(t *testing.T) {
|
||||
if _, hostConfig := mustParse(t, "-link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
|
||||
t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
|
||||
}
|
||||
if _, hostConfig := mustParse(t, "-link a:b -link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
|
||||
t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
|
||||
}
|
||||
if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
|
||||
t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
|
||||
}
|
||||
|
||||
if _, _, err := parse(t, "-link a"); err == nil {
|
||||
t.Fatalf("Error parsing links. `-link a` should be an error but is not")
|
||||
}
|
||||
if _, _, err := parse(t, "-link"); err == nil {
|
||||
t.Fatalf("Error parsing links. `-link` should be an error but is not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRunAttach(t *testing.T) {
|
||||
if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
||||
t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
||||
}
|
||||
if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr {
|
||||
t.Fatalf("Error parsing attach flags. Expect only Stdin and Stdout enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
||||
}
|
||||
if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
|
||||
t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
||||
}
|
||||
if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
|
||||
t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
|
||||
}
|
||||
|
||||
if _, _, err := parse(t, "-a"); err == nil {
|
||||
t.Fatalf("Error parsing attach flags, `-a` should be an error but is not")
|
||||
}
|
||||
if _, _, err := parse(t, "-a invalid"); err == nil {
|
||||
t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not")
|
||||
}
|
||||
if _, _, err := parse(t, "-a invalid -a stdout"); err == nil {
|
||||
t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
|
||||
}
|
||||
if _, _, err := parse(t, "-a stdout -a stderr -d"); err == nil {
|
||||
t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
|
||||
}
|
||||
if _, _, err := parse(t, "-a stdin -d"); err == nil {
|
||||
t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not")
|
||||
}
|
||||
if _, _, err := parse(t, "-a stdout -d"); err == nil {
|
||||
t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not")
|
||||
}
|
||||
if _, _, err := parse(t, "-a stderr -d"); err == nil {
|
||||
t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not")
|
||||
}
|
||||
if _, _, err := parse(t, "-d -rm"); err == nil {
|
||||
t.Fatalf("Error parsing attach flags, `-d -rm` should be an error but is not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRunVolumes(t *testing.T) {
|
||||
if config, hostConfig := mustParse(t, "-v /tmp"); hostConfig.Binds != nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp` should not mount-bind anything. Received %v", hostConfig.Binds)
|
||||
} else if _, exists := config.Volumes["/tmp"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
|
||||
}
|
||||
|
||||
if config, hostConfig := mustParse(t, "-v /tmp -v /var"); hostConfig.Binds != nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp -v /var` should not mount-bind anything. Received %v", hostConfig.Binds)
|
||||
} else if _, exists := config.Volumes["/tmp"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Recevied %v", config.Volumes)
|
||||
} else if _, exists := config.Volumes["/var"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /var` is missing from volumes. Received %v", config.Volumes)
|
||||
}
|
||||
|
||||
if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
|
||||
t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp` should mount-bind /hostTmp into /containeTmp. Received %v", hostConfig.Binds)
|
||||
} else if _, exists := config.Volumes["/containerTmp"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
|
||||
}
|
||||
|
||||
if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /hostVar:/containerVar"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" || hostConfig.Binds[1] != "/hostVar:/containerVar" {
|
||||
t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /hostVar:/containerVar` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
|
||||
} else if _, exists := config.Volumes["/containerTmp"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes)
|
||||
} else if _, exists := config.Volumes["/containerVar"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
|
||||
}
|
||||
|
||||
if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp:ro" || hostConfig.Binds[1] != "/hostVar:/containerVar:rw" {
|
||||
t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
|
||||
} else if _, exists := config.Volumes["/containerTmp"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes)
|
||||
} else if _, exists := config.Volumes["/containerVar"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
|
||||
}
|
||||
|
||||
if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /containerVar"); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
|
||||
t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /containerVar` should mount-bind only /hostTmp into /containeTmp. Received %v", hostConfig.Binds)
|
||||
} else if _, exists := config.Volumes["/containerTmp"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes)
|
||||
} else if _, exists := config.Volumes["/containerVar"]; !exists {
|
||||
t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
|
||||
}
|
||||
|
||||
if config, hostConfig := mustParse(t, ""); hostConfig.Binds != nil {
|
||||
t.Fatalf("Error parsing volume flags, without volume, nothing should be mount-binded. Received %v", hostConfig.Binds)
|
||||
} else if len(config.Volumes) != 0 {
|
||||
t.Fatalf("Error parsing volume flags, without volume, no volume should be present. Received %v", config.Volumes)
|
||||
}
|
||||
|
||||
if _, _, err := parse(t, "-v /"); err == nil {
|
||||
t.Fatalf("Expected error, but got none")
|
||||
}
|
||||
|
||||
if _, _, err := parse(t, "-v /:/"); err == nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v /:/` should fail but didn't")
|
||||
}
|
||||
if _, _, err := parse(t, "-v"); err == nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v` should fail but didn't")
|
||||
}
|
||||
if _, _, err := parse(t, "-v /tmp:"); err == nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp:` should fail but didn't")
|
||||
}
|
||||
if _, _, err := parse(t, "-v /tmp:ro"); err == nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp:ro` should fail but didn't")
|
||||
}
|
||||
if _, _, err := parse(t, "-v /tmp::"); err == nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp::` should fail but didn't")
|
||||
}
|
||||
if _, _, err := parse(t, "-v :"); err == nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v :` should fail but didn't")
|
||||
}
|
||||
if _, _, err := parse(t, "-v ::"); err == nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v ::` should fail but didn't")
|
||||
}
|
||||
if _, _, err := parse(t, "-v /tmp:/tmp:/tmp:/tmp"); err == nil {
|
||||
t.Fatalf("Error parsing volume flags, `-v /tmp:/tmp:/tmp:/tmp` should fail but didn't")
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package daemonconfig
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/daemon/networkdriver"
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"net"
|
||||
|
||||
"github.com/dotcloud/docker/engine"
|
||||
"github.com/dotcloud/docker/networkdriver"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -12,12 +13,11 @@ const (
|
||||
)
|
||||
|
||||
// FIXME: separate runtime configuration from http api configuration
|
||||
type Config struct {
|
||||
type DaemonConfig struct {
|
||||
Pidfile string
|
||||
Root string
|
||||
AutoRestart bool
|
||||
Dns []string
|
||||
DnsSearch []string
|
||||
EnableIptables bool
|
||||
EnableIpForward bool
|
||||
DefaultIp net.IP
|
||||
@@ -25,19 +25,15 @@ type Config struct {
|
||||
BridgeIP string
|
||||
InterContainerCommunication bool
|
||||
GraphDriver string
|
||||
GraphOptions []string
|
||||
ExecDriver string
|
||||
Mtu int
|
||||
DisableNetwork bool
|
||||
EnableSelinuxSupport bool
|
||||
Context map[string][]string
|
||||
Sockets []string
|
||||
}
|
||||
|
||||
// ConfigFromJob creates and returns a new DaemonConfig object
|
||||
// by parsing the contents of a job's environment.
|
||||
func ConfigFromJob(job *engine.Job) *Config {
|
||||
config := &Config{
|
||||
func DaemonConfigFromJob(job *engine.Job) *DaemonConfig {
|
||||
config := &DaemonConfig{
|
||||
Pidfile: job.Getenv("Pidfile"),
|
||||
Root: job.Getenv("Root"),
|
||||
AutoRestart: job.GetenvBool("AutoRestart"),
|
||||
@@ -49,27 +45,16 @@ func ConfigFromJob(job *engine.Job) *Config {
|
||||
InterContainerCommunication: job.GetenvBool("InterContainerCommunication"),
|
||||
GraphDriver: job.Getenv("GraphDriver"),
|
||||
ExecDriver: job.Getenv("ExecDriver"),
|
||||
EnableSelinuxSupport: job.GetenvBool("EnableSelinuxSupport"),
|
||||
}
|
||||
if graphOpts := job.GetenvList("GraphOptions"); graphOpts != nil {
|
||||
config.GraphOptions = graphOpts
|
||||
}
|
||||
|
||||
if dns := job.GetenvList("Dns"); dns != nil {
|
||||
config.Dns = dns
|
||||
}
|
||||
if dnsSearch := job.GetenvList("DnsSearch"); dnsSearch != nil {
|
||||
config.DnsSearch = dnsSearch
|
||||
}
|
||||
if mtu := job.GetenvInt("Mtu"); mtu != 0 {
|
||||
config.Mtu = mtu
|
||||
} else {
|
||||
config.Mtu = GetDefaultNetworkMtu()
|
||||
}
|
||||
config.DisableNetwork = config.BridgeIface == DisableNetworkBridge
|
||||
if sockets := job.GetenvList("Sockets"); sockets != nil {
|
||||
config.Sockets = sockets
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
+1146
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
package daemon
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/nat"
|
||||
@@ -132,14 +132,14 @@ func TestParseNetworkOptsUdp(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetFullName(t *testing.T) {
|
||||
name, err := GetFullContainerName("testing")
|
||||
name, err := getFullName("testing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if name != "/testing" {
|
||||
t.Fatalf("Expected /testing got %s", name)
|
||||
}
|
||||
if _, err := GetFullContainerName(""); err == nil {
|
||||
if _, err := getFullName(""); err == nil {
|
||||
t.Fatal("Error should not be nil")
|
||||
}
|
||||
}
|
||||
@@ -1,153 +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
|
||||
|
||||
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_TARGET_MASQUERADE
|
||||
NETFILTER_XT_MATCH_{ADDRTYPE,CONNTRACK}
|
||||
NF_NAT NF_NAT_NEEDED
|
||||
)
|
||||
check_flags "${flags[@]}"
|
||||
echo
|
||||
|
||||
echo 'Optional Features:'
|
||||
flags=(
|
||||
MEMCG_SWAP
|
||||
RESOURCE_COUNTERS
|
||||
)
|
||||
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
|
||||
|
||||
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 | sed 's/^/ /'
|
||||
} | sed 's/^/ /'
|
||||
echo
|
||||
|
||||
#echo 'Potential Future Features:'
|
||||
#check_flags USER_NS
|
||||
#echo
|
||||
@@ -309,6 +309,14 @@ _docker_info()
|
||||
return
|
||||
}
|
||||
|
||||
_docker_insert()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_inspect()
|
||||
{
|
||||
case "$prev" in
|
||||
@@ -384,8 +392,11 @@ _docker_port()
|
||||
_docker_ps()
|
||||
{
|
||||
case "$prev" in
|
||||
--since|--before)
|
||||
__docker_containers_all
|
||||
--since-id|--before-id)
|
||||
COMPREPLY=( $( compgen -W "$( __docker_q ps -a -q )" -- "$cur" ) )
|
||||
# TODO replace this with __docker_containers_all
|
||||
# see https://github.com/dotcloud/docker/issues/3565
|
||||
return
|
||||
;;
|
||||
-n)
|
||||
return
|
||||
@@ -396,7 +407,7 @@ _docker_ps()
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-q --quiet -s --size -a --all --no-trunc -l --latest --since --before -n" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "-q --quiet -s --size -a --all --no-trunc -l --latest --since-id --before-id -n" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
@@ -430,7 +441,9 @@ _docker_push()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
__docker_image_repos
|
||||
# TODO replace this with __docker_image_repos_and_tags
|
||||
# see https://github.com/dotcloud/docker/issues/3411
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -458,21 +471,10 @@ _docker_rm()
|
||||
{
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-f --force -l --link -v --volumes" -- "$cur" ) )
|
||||
return
|
||||
COMPREPLY=( $( compgen -W "-v --volumes -l --link" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local force=
|
||||
for arg in "${COMP_WORDS[@]}"; do
|
||||
case "$arg" in
|
||||
-f|--force)
|
||||
__docker_containers_all
|
||||
return
|
||||
;;
|
||||
esac
|
||||
done
|
||||
__docker_containers_stopped
|
||||
return
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -540,7 +542,7 @@ _docker_search()
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--no-trunc --automated -s --stars" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "--no-trunc -t --trusted -s --stars" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
|
||||
@@ -26,38 +26,36 @@ 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'
|
||||
docker ps -a --no-trunc | awk 'NR>1' | 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'
|
||||
docker ps -a --no-trunc | awk 'NR>1' | 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'
|
||||
docker ps -a --no-trunc | awk 'NR>1' | 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}'
|
||||
docker images | awk 'NR>1' | grep -v '<none>' | 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
|
||||
docker images | awk 'NR>1' | grep -v '<none>' | awk '{print $1}' | sort | 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 'Enable inter-container 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' -l mtu -d 'Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if not 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'
|
||||
@@ -71,21 +69,21 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from attach' -l sig-proxy -d
|
||||
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 -f -n '__fish_docker_no_subcommand' -a build -d 'Build a container from a Dockerfile'
|
||||
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' -s q -l quiet -d 'Suppress verbose build output'
|
||||
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 a -l author -d 'Author (eg. "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'
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a cp -d 'Copy files/folders from the containers filesystem to the host path'
|
||||
|
||||
# diff
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a diff -d "Inspect changes on a container's filesystem"
|
||||
@@ -102,16 +100,16 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from export' -a '(__fish_pri
|
||||
# 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' -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' -s a -l all -d 'show all images (by default filter out the intermediate images used to build)'
|
||||
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' -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
|
||||
@@ -120,11 +118,15 @@ complete -c docker -f -n '__fish_docker_no_subcommand' -a import -d 'Create a ne
|
||||
# info
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a info -d 'Display system-wide information'
|
||||
|
||||
# insert
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a insert -d 'Insert a file in an image'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from insert' -a '(__fish_print_docker_images)' -d "Image"
|
||||
|
||||
# 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"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from inspect' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||
|
||||
# kill
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a kill -d 'Kill a running container'
|
||||
@@ -136,9 +138,9 @@ complete -c docker -f -n '__fish_docker_no_subcommand' -a load -d 'Load an image
|
||||
|
||||
# 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'
|
||||
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'
|
||||
@@ -152,13 +154,13 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from port' -a '(__fish_print
|
||||
# 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' -l before-id -d 'Show only container created before Id, 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 sizes'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -l since -d 'Show only containers created since Id or Name, include non-running ones.'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from ps' -l since-id -d 'Show only containers created since Id, 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'
|
||||
@@ -178,14 +180,12 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from restart' -a '(__fish_pr
|
||||
|
||||
# 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
|
||||
@@ -225,7 +225,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from save' -a '(__fish_print
|
||||
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'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from search' -s t -l trusted -d 'Only show trusted builds'
|
||||
|
||||
# start
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a start -d 'Start a stopped container'
|
||||
|
||||
Regular → Executable
+7
-2
@@ -1,6 +1,6 @@
|
||||
#compdef docker
|
||||
#
|
||||
# zsh completion for docker (http://docker.com)
|
||||
# zsh completion for docker (http://docker.io)
|
||||
#
|
||||
# version: 0.2.2
|
||||
# author: Felix Riedel
|
||||
@@ -139,6 +139,11 @@ __docker_subcommand () {
|
||||
(history)
|
||||
_arguments ':images:__docker_images'
|
||||
;;
|
||||
(insert)
|
||||
_arguments '1:containers:__docker_containers' \
|
||||
'2:URL:(http:// file://)' \
|
||||
'3:file:_files'
|
||||
;;
|
||||
(kill)
|
||||
_arguments '*:containers:__docker_runningcontainers'
|
||||
;;
|
||||
@@ -169,7 +174,7 @@ __docker_subcommand () {
|
||||
(ps)
|
||||
_arguments '-a[Show all containers. Only running containers are shown by default]' \
|
||||
'-h[Show help]' \
|
||||
'--before-id=-[Show only container created before Id, include non-running one]:containers:__docker_containers' \
|
||||
'-before-id=-[Show only container created before Id, include non-running one]:containers:__docker_containers' \
|
||||
'-n=-[Show n last created containers, include non-running one]:n:(1 5 10 25 50)'
|
||||
;;
|
||||
(tag)
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
# /data volume is owned by sysadmin.
|
||||
# USAGE:
|
||||
# # Download data Dockerfile
|
||||
# wget http://raw.githubusercontent.com/dotcloud/docker/master/contrib/desktop-integration/data/Dockerfile
|
||||
# wget http://raw.github.com/dotcloud/docker/master/contrib/desktop-integration/data/Dockerfile
|
||||
#
|
||||
# # Build data image
|
||||
# docker build -t data .
|
||||
# docker build -t data -rm .
|
||||
#
|
||||
# # Create a data container. (eg: iceweasel-data)
|
||||
# docker run --name iceweasel-data data true
|
||||
# docker run -name iceweasel-data data true
|
||||
#
|
||||
# # List data from it
|
||||
# docker run --volumes-from iceweasel-data busybox ls -al /data
|
||||
# docker run -volumes-from iceweasel-data busybox ls -al /data
|
||||
|
||||
docker-version 0.6.5
|
||||
|
||||
|
||||
@@ -7,19 +7,19 @@
|
||||
# sound devices. Tested on Debian 7.2
|
||||
# USAGE:
|
||||
# # Download Iceweasel Dockerfile
|
||||
# wget http://raw.githubusercontent.com/dotcloud/docker/master/contrib/desktop-integration/iceweasel/Dockerfile
|
||||
# wget http://raw.github.com/dotcloud/docker/master/contrib/desktop-integration/iceweasel/Dockerfile
|
||||
#
|
||||
# # Build iceweasel image
|
||||
# docker build -t iceweasel .
|
||||
# docker build -t iceweasel -rm .
|
||||
#
|
||||
# # Run stateful data-on-host iceweasel. For ephemeral, remove -v /data/iceweasel:/data
|
||||
# docker run -v /data/iceweasel:/data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# -v /dev/snd:/dev/snd --lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \
|
||||
# -v /dev/snd:/dev/snd -lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \
|
||||
# -e DISPLAY=unix$DISPLAY iceweasel
|
||||
#
|
||||
# # To run stateful dockerized data containers
|
||||
# docker run --volumes-from iceweasel-data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# -v /dev/snd:/dev/snd --lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \
|
||||
# docker run -volumes-from iceweasel-data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# -v /dev/snd:/dev/snd -lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \
|
||||
# -e DISPLAY=unix$DISPLAY iceweasel
|
||||
|
||||
docker-version 0.6.5
|
||||
|
||||
@@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/daemon/graphdriver/devmapper"
|
||||
"github.com/dotcloud/docker/graphdriver/devmapper"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
|
||||
FROM ubuntu:12.10
|
||||
MAINTAINER Guillaume J. Charmes <guillaume@docker.com>
|
||||
MAINTAINER Guillaume J. Charmes <guillaume@dotcloud.com>
|
||||
|
||||
RUN apt-get update && apt-get install -y wget git mercurial
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM busybox
|
||||
MAINTAINER Guillaume J. Charmes <guillaume@docker.com>
|
||||
MAINTAINER Guillaume J. Charmes <guillaume@dotcloud.com>
|
||||
ADD manager /usr/bin/
|
||||
ENTRYPOINT ["/usr/bin/manager"]
|
||||
|
||||
@@ -70,7 +70,7 @@ func main() {
|
||||
bufErr := bytes.NewBuffer(nil)
|
||||
|
||||
// Instanciate the Docker CLI
|
||||
cli := docker.NewDockerCli(nil, bufOut, bufErr, "unix", "/var/run/docker.sock", false, nil)
|
||||
cli := docker.NewDockerCli(nil, bufOut, bufErr, "unix", "/var/run/docker.sock")
|
||||
// Retrieve the container info
|
||||
if err := cli.CmdInspect(flag.Arg(0)); err != nil {
|
||||
// As of docker v0.6.3, CmdInspect always returns nil
|
||||
|
||||
@@ -11,9 +11,6 @@ DOCKER_OPTS=${DOCKER_OPTS:-}
|
||||
start() {
|
||||
checkpath -f -m 0644 -o root:docker "$DOCKER_LOGFILE"
|
||||
|
||||
ulimit -n 1048576
|
||||
ulimit -u 1048576
|
||||
|
||||
ebegin "Starting docker daemon"
|
||||
start-stop-daemon --start --background \
|
||||
--exec "$DOCKER_BINARY" \
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[Unit]
|
||||
Description=Docker Application Container Engine
|
||||
Documentation=http://docs.docker.com
|
||||
Documentation=http://docs.docker.io
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[Unit]
|
||||
Description=Docker Application Container Engine
|
||||
Documentation=http://docs.docker.com
|
||||
Documentation=http://docs.docker.io
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
# Provides: docker
|
||||
# Required-Start: $syslog $remote_fs
|
||||
# Required-Stop: $syslog $remote_fs
|
||||
# Should-Start: cgroupfs-mount cgroup-lite
|
||||
# Should-Stop: cgroupfs-mount cgroup-lite
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Create lightweight, portable, self-sufficient containers.
|
||||
@@ -22,10 +20,7 @@ BASE=$(basename $0)
|
||||
|
||||
# modify these in /etc/default/$BASE (/etc/default/docker)
|
||||
DOCKER=/usr/bin/$BASE
|
||||
# This is the pid file managed by docker itself
|
||||
DOCKER_PIDFILE=/var/run/$BASE.pid
|
||||
# This is the pid file created/managed by start-stop-daemon
|
||||
DOCKER_SSD_PIDFILE=/var/run/$BASE-ssd.pid
|
||||
DOCKER_LOGFILE=/var/log/$BASE.log
|
||||
DOCKER_OPTS=
|
||||
DOCKER_DESC="Docker"
|
||||
@@ -88,15 +83,11 @@ case "$1" in
|
||||
touch "$DOCKER_LOGFILE"
|
||||
chgrp docker "$DOCKER_LOGFILE"
|
||||
|
||||
ulimit -n 1048576
|
||||
ulimit -u 1048576
|
||||
|
||||
log_begin_msg "Starting $DOCKER_DESC: $BASE"
|
||||
start-stop-daemon --start --background \
|
||||
--no-close \
|
||||
--exec "$DOCKER" \
|
||||
--pidfile "$DOCKER_SSD_PIDFILE" \
|
||||
--make-pidfile \
|
||||
--pidfile "$DOCKER_PIDFILE" \
|
||||
-- \
|
||||
-d -p "$DOCKER_PIDFILE" \
|
||||
$DOCKER_OPTS \
|
||||
@@ -107,13 +98,13 @@ case "$1" in
|
||||
stop)
|
||||
fail_unless_root
|
||||
log_begin_msg "Stopping $DOCKER_DESC: $BASE"
|
||||
start-stop-daemon --stop --pidfile "$DOCKER_SSD_PIDFILE"
|
||||
start-stop-daemon --stop --pidfile "$DOCKER_PIDFILE"
|
||||
log_end_msg $?
|
||||
;;
|
||||
|
||||
restart)
|
||||
fail_unless_root
|
||||
docker_pid=`cat "$DOCKER_SSD_PIDFILE" 2>/dev/null`
|
||||
docker_pid=`cat "$DOCKER_PIDFILE" 2>/dev/null`
|
||||
[ -n "$docker_pid" ] \
|
||||
&& ps -p $docker_pid > /dev/null 2>&1 \
|
||||
&& $0 stop
|
||||
@@ -126,7 +117,7 @@ case "$1" in
|
||||
;;
|
||||
|
||||
status)
|
||||
status_of_proc -p "$DOCKER_SSD_PIDFILE" "$DOCKER" docker
|
||||
status_of_proc -p "$DOCKER_PIDFILE" "$DOCKER" docker
|
||||
;;
|
||||
|
||||
*)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#DOCKER="/usr/local/bin/docker"
|
||||
|
||||
# Use DOCKER_OPTS to modify the daemon startup options.
|
||||
#DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4"
|
||||
#DOCKER_OPTS="-dns 8.8.8.8 -dns 8.8.4.4"
|
||||
|
||||
# If you need Docker to use an HTTP proxy, it can also be specified here.
|
||||
#export http_proxy="http://127.0.0.1:3128/"
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
#
|
||||
# /etc/rc.d/init.d/docker
|
||||
#
|
||||
# Daemon for docker.com
|
||||
#
|
||||
# Daemon for docker.io
|
||||
#
|
||||
# chkconfig: 2345 95 95
|
||||
# description: Daemon for docker.com
|
||||
# description: Daemon for docker.io
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: docker
|
||||
@@ -16,7 +16,7 @@
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: start and stop docker
|
||||
# Description: Daemon for docker.com
|
||||
# Description: Daemon for docker.io
|
||||
### END INIT INFO
|
||||
|
||||
# Source function library.
|
||||
@@ -49,13 +49,6 @@ start() {
|
||||
$exec -d $other_args &>> $logfile &
|
||||
pid=$!
|
||||
touch $lockfile
|
||||
# wait up to 10 seconds for the pidfile to exist. see
|
||||
# https://github.com/dotcloud/docker/issues/5359
|
||||
tries=0
|
||||
while [ ! -f $pidfile -a $tries -lt 10 ]; do
|
||||
sleep 1
|
||||
tries=$((tries + 1))
|
||||
done
|
||||
success
|
||||
echo
|
||||
else
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
description "Docker daemon"
|
||||
|
||||
start on (local-filesystems and net-device-up IFACE!=lo)
|
||||
start on filesystem
|
||||
stop on runlevel [!2345]
|
||||
limit nofile 524288 1048576
|
||||
limit nproc 524288 1048576
|
||||
|
||||
respawn
|
||||
|
||||
@@ -37,5 +35,5 @@ script
|
||||
if [ -f /etc/default/$UPSTART_JOB ]; then
|
||||
. /etc/default/$UPSTART_JOB
|
||||
fi
|
||||
exec "$DOCKER" -d $DOCKER_OPTS
|
||||
"$DOCKER" -d $DOCKER_OPTS
|
||||
end script
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
[ $(id -u) -eq 0 ] || {
|
||||
printf >&2 '%s requires root\n' "$0"
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
printf >&2 '%s: [-r release] [-m mirror] [-s]\n' "$0"
|
||||
exit 1
|
||||
}
|
||||
|
||||
tmp() {
|
||||
TMP=$(mktemp -d ${TMPDIR:-/var/tmp}/alpine-docker-XXXXXXXXXX)
|
||||
ROOTFS=$(mktemp -d ${TMPDIR:-/var/tmp}/alpine-docker-rootfs-XXXXXXXXXX)
|
||||
trap "rm -rf $TMP $ROOTFS" EXIT TERM INT
|
||||
}
|
||||
|
||||
apkv() {
|
||||
curl -s $REPO/$ARCH/APKINDEX.tar.gz | tar -Oxz |
|
||||
grep '^P:apk-tools-static$' -A1 | tail -n1 | cut -d: -f2
|
||||
}
|
||||
|
||||
getapk() {
|
||||
curl -s $REPO/$ARCH/apk-tools-static-$(apkv).apk |
|
||||
tar -xz -C $TMP sbin/apk.static
|
||||
}
|
||||
|
||||
mkbase() {
|
||||
$TMP/sbin/apk.static --repository $REPO --update-cache --allow-untrusted \
|
||||
--root $ROOTFS --initdb add alpine-base
|
||||
}
|
||||
|
||||
conf() {
|
||||
printf '%s\n' $REPO > $ROOTFS/etc/apk/repositories
|
||||
}
|
||||
|
||||
pack() {
|
||||
local id
|
||||
id=$(tar --numeric-owner -C $ROOTFS -c . | docker import - alpine:$REL)
|
||||
|
||||
docker tag $id alpine:latest
|
||||
docker run -i -t alpine printf 'alpine:%s with id=%s created!\n' $REL $id
|
||||
}
|
||||
|
||||
save() {
|
||||
[ $SAVE -eq 1 ] || return
|
||||
|
||||
tar --numeric-owner -C $ROOTFS -c . | xz > rootfs.tar.xz
|
||||
}
|
||||
|
||||
while getopts "hr:m:s" opt; do
|
||||
case $opt in
|
||||
r)
|
||||
REL=$OPTARG
|
||||
;;
|
||||
m)
|
||||
MIRROR=$OPTARG
|
||||
;;
|
||||
s)
|
||||
SAVE=1
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
REL=${REL:-edge}
|
||||
MIRROR=${MIRROR:-http://nl.alpinelinux.org/alpine}
|
||||
SAVE=${SAVE:-0}
|
||||
REPO=$MIRROR/$REL/main
|
||||
ARCH=$(uname -m)
|
||||
|
||||
tmp
|
||||
getapk
|
||||
mkbase
|
||||
conf
|
||||
pack
|
||||
save
|
||||
+20
-3
@@ -9,13 +9,31 @@ hash pacstrap &>/dev/null || {
|
||||
exit 1
|
||||
}
|
||||
|
||||
ROOTFS=$(mktemp -d ${TMPDIR:-/var/tmp}/rootfs-archlinux-XXXXXXXXXX)
|
||||
hash expect &>/dev/null || {
|
||||
echo "Could not find expect. Run pacman -S expect"
|
||||
exit 1
|
||||
}
|
||||
|
||||
ROOTFS=$(mktemp -d /tmp/rootfs-archlinux-XXXXXXXXXX)
|
||||
chmod 755 $ROOTFS
|
||||
|
||||
# packages to ignore for space savings
|
||||
PKGIGNORE=linux,jfsutils,lvm2,cryptsetup,groff,man-db,man-pages,mdadm,pciutils,pcmciautils,reiserfsprogs,s-nail,xfsprogs
|
||||
|
||||
pacstrap -C ./mkimage-arch-pacman.conf -c -d -G -i $ROOTFS base haveged --ignore $PKGIGNORE
|
||||
expect <<EOF
|
||||
set timeout 60
|
||||
set send_slow {1 1}
|
||||
spawn pacstrap -C ./mkimage-arch-pacman.conf -c -d -G -i $ROOTFS base haveged --ignore $PKGIGNORE
|
||||
expect {
|
||||
"Install anyway?" { send n\r; exp_continue }
|
||||
"(default=all)" { send \r; exp_continue }
|
||||
"Proceed with installation?" { send "\r"; exp_continue }
|
||||
"skip the above package" {send "y\r"; exp_continue }
|
||||
"checking" { exp_continue }
|
||||
"loading" { exp_continue }
|
||||
"installing" { exp_continue }
|
||||
}
|
||||
EOF
|
||||
|
||||
arch-chroot $ROOTFS /bin/sh -c "haveged -w 1024; pacman-key --init; pkill haveged; pacman -Rs --noconfirm haveged; pacman-key --populate archlinux"
|
||||
arch-chroot $ROOTFS /bin/sh -c "ln -s /usr/share/zoneinfo/UTC /etc/localtime"
|
||||
@@ -39,7 +57,6 @@ mknod -m 666 $DEV/tty0 c 4 0
|
||||
mknod -m 666 $DEV/full c 1 7
|
||||
mknod -m 600 $DEV/initctl p
|
||||
mknod -m 666 $DEV/ptmx c 5 2
|
||||
ln -sf /proc/self/fd $DEV/fd
|
||||
|
||||
tar --numeric-owner -C $ROOTFS -c . | docker import - archlinux
|
||||
docker run -i -t archlinux echo Success.
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
# Generate a very minimal filesystem based on busybox-static,
|
||||
# and load it into the local docker under the name "busybox".
|
||||
|
||||
echo >&2
|
||||
echo >&2 'warning: this script is deprecated - see mkimage.sh and mkimage/busybox-static'
|
||||
echo >&2
|
||||
|
||||
BUSYBOX=$(which busybox)
|
||||
[ "$BUSYBOX" ] || {
|
||||
echo "Sorry, I could not locate busybox."
|
||||
@@ -14,7 +10,7 @@ BUSYBOX=$(which busybox)
|
||||
}
|
||||
|
||||
set -e
|
||||
ROOTFS=${TMPDIR:-/var/tmp}/rootfs-busybox-$$-$RANDOM
|
||||
ROOTFS=/tmp/rootfs-busybox-$$-$RANDOM
|
||||
mkdir $ROOTFS
|
||||
cd $ROOTFS
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@ die () {
|
||||
|
||||
ISO=${1}
|
||||
|
||||
ROOTFS=$(mktemp -d ${TMPDIR:-/var/tmp}/rootfs-crux-XXXXXXXXXX)
|
||||
CRUX=$(mktemp -d ${TMPDIR:-/var/tmp}/crux-XXXXXXXXXX)
|
||||
TMP=$(mktemp -d ${TMPDIR:-/var/tmp}/XXXXXXXXXX)
|
||||
ROOTFS=$(mktemp -d /tmp/rootfs-crux-XXXXXXXXXX)
|
||||
CRUX=$(mktemp -d /tmp/crux-XXXXXXXXXX)
|
||||
TMP=$(mktemp -d /tmp/XXXXXXXXXX)
|
||||
|
||||
VERSION=$(basename --suffix=.iso $ISO | sed 's/[^0-9.]*\([0-9.]*\).*/\1/')
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
echo >&2
|
||||
echo >&2 'warning: this script is deprecated - see mkimage.sh and mkimage/debootstrap'
|
||||
echo >&2
|
||||
|
||||
variant='minbase'
|
||||
include='iproute,iputils-ping'
|
||||
arch='amd64' # intentionally undocumented for now
|
||||
@@ -47,7 +43,7 @@ usage() {
|
||||
debianStable=wheezy
|
||||
debianUnstable=sid
|
||||
# this should match the name found at http://releases.ubuntu.com/
|
||||
ubuntuLatestLTS=trusty
|
||||
ubuntuLatestLTS=precise
|
||||
# this should match the name found at http://releases.tanglu.org/
|
||||
tangluLatest=aequorea
|
||||
|
||||
@@ -118,7 +114,7 @@ fi
|
||||
# will be filled in later, if [ -z "$skipDetection" ]
|
||||
lsbDist=''
|
||||
|
||||
target="${TMPDIR:-/var/tmp}/docker-rootfs-debootstrap-$suite-$$-$RANDOM"
|
||||
target="/tmp/docker-rootfs-debootstrap-$suite-$$-$RANDOM"
|
||||
|
||||
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
|
||||
returnTo="$(pwd -P)"
|
||||
@@ -223,7 +219,6 @@ if [ -z "$strictDebootstrap" ]; then
|
||||
|
||||
# make sure our packages lists are as up to date as we can get them
|
||||
sudo chroot . apt-get update
|
||||
sudo chroot . apt-get dist-upgrade -y
|
||||
fi
|
||||
|
||||
if [ "$justTar" ]; then
|
||||
|
||||
@@ -8,10 +8,6 @@
|
||||
|
||||
set -e
|
||||
|
||||
echo >&2
|
||||
echo >&2 'warning: this script is deprecated - see mkimage.sh and mkimage/rinse'
|
||||
echo >&2
|
||||
|
||||
repo="$1"
|
||||
distro="$2"
|
||||
mirror="$3"
|
||||
@@ -39,7 +35,7 @@ if [ ! "$repo" ] || [ ! "$distro" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
target="${TMPDIR:-/var/tmp}/docker-rootfs-rinse-$distro-$$-$RANDOM"
|
||||
target="/tmp/docker-rootfs-rinse-$distro-$$-$RANDOM"
|
||||
|
||||
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
|
||||
returnTo="$(pwd -P)"
|
||||
|
||||
@@ -15,7 +15,7 @@ SOCAT=$(which socat)
|
||||
|
||||
shopt -s extglob
|
||||
set -ex
|
||||
ROOTFS=`mktemp -d ${TMPDIR:-/var/tmp}/rootfs-busybox.XXXXXXXXXX`
|
||||
ROOTFS=`mktemp -d /tmp/rootfs-busybox.XXXXXXXXXX`
|
||||
trap "rm -rf $ROOTFS" INT QUIT TERM
|
||||
cd $ROOTFS
|
||||
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
mkimg="$(basename "$0")"
|
||||
|
||||
usage() {
|
||||
echo >&2 "usage: $mkimg [-d dir] [-t tag] script [script-args]"
|
||||
echo >&2 " ie: $mkimg -t someuser/debian debootstrap --variant=minbase jessie"
|
||||
echo >&2 " $mkimg -t someuser/ubuntu debootstrap --include=ubuntu-minimal --components main,universe trusty"
|
||||
echo >&2 " $mkimg -t someuser/busybox busybox-static"
|
||||
echo >&2 " $mkimg -t someuser/centos:5 rinse --distribution centos-5"
|
||||
echo >&2 " $mkimg -t someuser/mageia:4 mageia-urpmi --version=4"
|
||||
echo >&2 " $mkimg -t someuser/mageia:4 mageia-urpmi --version=4 --mirror=http://somemirror/"
|
||||
exit 1
|
||||
}
|
||||
|
||||
scriptDir="$(dirname "$(readlink -f "$BASH_SOURCE")")/mkimage"
|
||||
|
||||
optTemp=$(getopt --options '+d:t:h' --longoptions 'dir:,tag:,help' --name "$mkimg" -- "$@")
|
||||
eval set -- "$optTemp"
|
||||
unset optTemp
|
||||
|
||||
dir=
|
||||
tag=
|
||||
while true; do
|
||||
case "$1" in
|
||||
-d|--dir) dir="$2" ; shift 2 ;;
|
||||
-t|--tag) tag="$2" ; shift 2 ;;
|
||||
-h|--help) usage ;;
|
||||
--) shift ; break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
script="$1"
|
||||
[ "$script" ] || usage
|
||||
shift
|
||||
|
||||
if [ ! -x "$scriptDir/$script" ]; then
|
||||
echo >&2 "error: $script does not exist or is not executable"
|
||||
echo >&2 " see $scriptDir for possible scripts"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# don't mistake common scripts like .febootstrap-minimize as image-creators
|
||||
if [[ "$script" == .* ]]; then
|
||||
echo >&2 "error: $script is a script helper, not a script"
|
||||
echo >&2 " see $scriptDir for possible scripts"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
delDir=
|
||||
if [ -z "$dir" ]; then
|
||||
dir="$(mktemp -d ${TMPDIR:-/var/tmp}/docker-mkimage.XXXXXXXXXX)"
|
||||
delDir=1
|
||||
fi
|
||||
|
||||
rootfsDir="$dir/rootfs"
|
||||
( set -x; mkdir -p "$rootfsDir" )
|
||||
|
||||
# pass all remaining arguments to $script
|
||||
"$scriptDir/$script" "$rootfsDir" "$@"
|
||||
|
||||
# Docker mounts tmpfs at /dev and procfs at /proc so we can remove them
|
||||
rm -rf "$rootfsDir/dev" "$rootfsDir/proc"
|
||||
mkdir -p "$rootfsDir/dev" "$rootfsDir/proc"
|
||||
|
||||
# make sure /etc/resolv.conf has something useful in it
|
||||
mkdir -p "$rootfsDir/etc"
|
||||
cat > "$rootfsDir/etc/resolv.conf" <<'EOF'
|
||||
nameserver 8.8.8.8
|
||||
nameserver 8.8.4.4
|
||||
EOF
|
||||
|
||||
tarFile="$dir/rootfs.tar.xz"
|
||||
touch "$tarFile"
|
||||
|
||||
(
|
||||
set -x
|
||||
tar --numeric-owner -caf "$tarFile" -C "$rootfsDir" --transform='s,^./,,' .
|
||||
)
|
||||
|
||||
echo >&2 "+ cat > '$dir/Dockerfile'"
|
||||
cat > "$dir/Dockerfile" <<'EOF'
|
||||
FROM scratch
|
||||
ADD rootfs.tar.xz /
|
||||
EOF
|
||||
|
||||
# if our generated image has a decent shell, let's set a default command
|
||||
for shell in /bin/bash /usr/bin/fish /usr/bin/zsh /bin/sh; do
|
||||
if [ -x "$rootfsDir/$shell" ]; then
|
||||
( set -x; echo 'CMD ["'"$shell"'"]' >> "$dir/Dockerfile" )
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
( set -x; rm -rf "$rootfsDir" )
|
||||
|
||||
if [ "$tag" ]; then
|
||||
( set -x; docker build -t "$tag" "$dir" )
|
||||
elif [ "$delDir" ]; then
|
||||
# if we didn't specify a tag and we're going to delete our dir, let's just build an untagged image so that we did _something_
|
||||
( set -x; docker build "$dir" )
|
||||
fi
|
||||
|
||||
if [ "$delDir" ]; then
|
||||
( set -x; rm -rf "$dir" )
|
||||
fi
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
rootfsDir="$1"
|
||||
shift
|
||||
|
||||
(
|
||||
cd "$rootfsDir"
|
||||
|
||||
# effectively: febootstrap-minimize --keep-zoneinfo --keep-rpmdb --keep-services "$target"
|
||||
# locales
|
||||
rm -rf usr/{{lib,share}/locale,{lib,lib64}/gconv,bin/localedef,sbin/build-locale-archive}
|
||||
# docs
|
||||
rm -rf usr/share/{man,doc,info,gnome/help}
|
||||
# cracklib
|
||||
rm -rf usr/share/cracklib
|
||||
# i18n
|
||||
rm -rf usr/share/i18n
|
||||
# yum cache
|
||||
rm -rf var/cache/yum
|
||||
mkdir -p --mode=0755 var/cache/yum
|
||||
# sln
|
||||
rm -rf sbin/sln
|
||||
# ldconfig
|
||||
#rm -rf sbin/ldconfig
|
||||
rm -rf etc/ld.so.cache var/cache/ldconfig
|
||||
mkdir -p --mode=0755 var/cache/ldconfig
|
||||
)
|
||||
@@ -1,34 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
rootfsDir="$1"
|
||||
shift
|
||||
|
||||
busybox="$(which busybox 2>/dev/null || true)"
|
||||
if [ -z "$busybox" ]; then
|
||||
echo >&2 'error: busybox: not found'
|
||||
echo >&2 ' install it with your distribution "busybox-static" package'
|
||||
exit 1
|
||||
fi
|
||||
if ! ldd "$busybox" 2>&1 | grep -q 'not a dynamic executable'; then
|
||||
echo >&2 "error: '$busybox' appears to be a dynamic executable"
|
||||
echo >&2 ' you should install your distribution "busybox-static" package instead'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$rootfsDir/bin"
|
||||
rm -f "$rootfsDir/bin/busybox" # just in case
|
||||
cp "$busybox" "$rootfsDir/bin/busybox"
|
||||
|
||||
(
|
||||
cd "$rootfsDir"
|
||||
|
||||
IFS=$'\n'
|
||||
modules=( $(bin/busybox --list-modules) )
|
||||
unset IFS
|
||||
|
||||
for module in "${modules[@]}"; do
|
||||
mkdir -p "$(dirname "$module")"
|
||||
ln -sf /bin/busybox "$module"
|
||||
done
|
||||
)
|
||||
@@ -1,176 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
rootfsDir="$1"
|
||||
shift
|
||||
|
||||
# we have to do a little fancy footwork to make sure "rootfsDir" becomes the second non-option argument to debootstrap
|
||||
|
||||
before=()
|
||||
while [ $# -gt 0 ] && [[ "$1" == -* ]]; do
|
||||
before+=( "$1" )
|
||||
shift
|
||||
done
|
||||
|
||||
suite="$1"
|
||||
shift
|
||||
|
||||
(
|
||||
set -x
|
||||
debootstrap "${before[@]}" "$suite" "$rootfsDir" "$@"
|
||||
)
|
||||
|
||||
# now for some Docker-specific tweaks
|
||||
|
||||
# prevent init scripts from running during install/update
|
||||
echo >&2 "+ echo exit 101 > '$rootfsDir/usr/sbin/policy-rc.d'"
|
||||
cat > "$rootfsDir/usr/sbin/policy-rc.d" <<'EOF'
|
||||
#!/bin/sh
|
||||
|
||||
# For most Docker users, "apt-get install" only happens during "docker build",
|
||||
# where starting services doesn't work and often fails in humorous ways. This
|
||||
# prevents those failures by stopping the services from attempting to start.
|
||||
|
||||
exit 101
|
||||
EOF
|
||||
chmod +x "$rootfsDir/usr/sbin/policy-rc.d"
|
||||
|
||||
# prevent upstart scripts from running during install/update
|
||||
(
|
||||
set -x
|
||||
chroot "$rootfsDir" dpkg-divert --local --rename --add /sbin/initctl
|
||||
cp -a "$rootfsDir/usr/sbin/policy-rc.d" "$rootfsDir/sbin/initctl"
|
||||
sed -i 's/^exit.*/exit 0/' "$rootfsDir/sbin/initctl"
|
||||
)
|
||||
|
||||
# shrink a little, since apt makes us cache-fat (wheezy: ~157.5MB vs ~120MB)
|
||||
( set -x; chroot "$rootfsDir" apt-get clean )
|
||||
|
||||
# Ubuntu 10.04 sucks... :)
|
||||
if strings "$rootfsDir/usr/bin/dpkg" | grep -q unsafe-io; then
|
||||
# force dpkg not to call sync() after package extraction (speeding up installs)
|
||||
echo >&2 "+ echo force-unsafe-io > '$rootfsDir/etc/dpkg/dpkg.cfg.d/docker-apt-speedup'"
|
||||
cat > "$rootfsDir/etc/dpkg/dpkg.cfg.d/docker-apt-speedup" <<-'EOF'
|
||||
# For most Docker users, package installs happen during "docker build", which
|
||||
# doesn't survive power loss and gets restarted clean afterwards anyhow, so
|
||||
# this minor tweak gives us a nice speedup (much nicer on spinning disks,
|
||||
# obviously).
|
||||
|
||||
force-unsafe-io
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [ -d "$rootfsDir/etc/apt/apt.conf.d" ]; then
|
||||
# _keep_ us lean by effectively running "apt-get clean" after every install
|
||||
aptGetClean='"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true";'
|
||||
echo >&2 "+ cat > '$rootfsDir/etc/apt/apt.conf.d/docker-clean'"
|
||||
cat > "$rootfsDir/etc/apt/apt.conf.d/docker-clean" <<-EOF
|
||||
# Since for most Docker users, package installs happen in "docker build" steps,
|
||||
# they essentially become individual layers due to the way Docker handles
|
||||
# layering, especially using CoW filesystems. What this means for us is that
|
||||
# the caches that APT keeps end up just wasting space in those layers, making
|
||||
# our layers unnecessarily large (especially since we'll normally never use
|
||||
# these caches again and will instead just "docker build" again and make a brand
|
||||
# new image).
|
||||
|
||||
# Ideally, these would just be invoking "apt-get clean", but in our testing,
|
||||
# that ended up being cyclic and we got stuck on APT's lock, so we get this fun
|
||||
# creation that's essentially just "apt-get clean".
|
||||
DPkg::Post-Invoke { ${aptGetClean} };
|
||||
APT::Update::Post-Invoke { ${aptGetClean} };
|
||||
|
||||
Dir::Cache::pkgcache "";
|
||||
Dir::Cache::srcpkgcache "";
|
||||
|
||||
# Note that we do realize this isn't the ideal way to do this, and are always
|
||||
# open to better suggestions (https://github.com/dotcloud/docker/issues).
|
||||
EOF
|
||||
|
||||
# remove apt-cache translations for fast "apt-get update"
|
||||
echo >&2 "+ echo Acquire::Languages 'none' > '$rootfsDir/etc/apt/apt.conf.d/docker-no-languages'"
|
||||
cat > "$rootfsDir/etc/apt/apt.conf.d/docker-no-languages" <<-'EOF'
|
||||
# In Docker, we don't often need the "Translations" files, so we're just wasting
|
||||
# time and space by downloading them, and this inhibits that. For users that do
|
||||
# need them, it's a simple matter to delete this file and "apt-get update". :)
|
||||
|
||||
Acquire::Languages "none";
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [ -z "$DONT_TOUCH_SOURCES_LIST" ]; then
|
||||
# tweak sources.list, where appropriate
|
||||
lsbDist=
|
||||
if [ -z "$lsbDist" -a -r "$rootfsDir/etc/os-release" ]; then
|
||||
lsbDist="$(. "$rootfsDir/etc/os-release" && echo "$ID")"
|
||||
fi
|
||||
if [ -z "$lsbDist" -a -r "$rootfsDir/etc/lsb-release" ]; then
|
||||
lsbDist="$(. "$rootfsDir/etc/lsb-release" && echo "$DISTRIB_ID")"
|
||||
fi
|
||||
if [ -z "$lsbDist" -a -r "$rootfsDir/etc/debian_version" ]; then
|
||||
lsbDist='Debian'
|
||||
fi
|
||||
# normalize to lowercase for easier matching
|
||||
lsbDist="$(echo "$lsbDist" | tr '[:upper:]' '[:lower:]')"
|
||||
case "$lsbDist" in
|
||||
debian)
|
||||
# updates and security!
|
||||
if [ "$suite" != 'sid' -a "$suite" != 'unstable' ]; then
|
||||
(
|
||||
set -x
|
||||
sed -i "
|
||||
p;
|
||||
s/ $suite / ${suite}-updates /
|
||||
" "$rootfsDir/etc/apt/sources.list"
|
||||
echo "deb http://security.debian.org $suite/updates main" >> "$rootfsDir/etc/apt/sources.list"
|
||||
# LTS
|
||||
if [ "$suite" = 'squeeze' ]; then
|
||||
head -1 "$rootfsDir/etc/apt/sources.list" \
|
||||
| sed "s/ $suite / ${suite}-lts /" \
|
||||
>> "$rootfsDir/etc/apt/sources.list"
|
||||
fi
|
||||
)
|
||||
fi
|
||||
;;
|
||||
ubuntu)
|
||||
# add the updates and security repositories
|
||||
(
|
||||
set -x
|
||||
sed -i "
|
||||
p;
|
||||
s/ $suite / ${suite}-updates /; p;
|
||||
s/ $suite-updates / ${suite}-security /
|
||||
" "$rootfsDir/etc/apt/sources.list"
|
||||
)
|
||||
;;
|
||||
tanglu)
|
||||
# add the updates repository
|
||||
if [ "$suite" != 'devel' ]; then
|
||||
(
|
||||
set -x
|
||||
sed -i "
|
||||
p;
|
||||
s/ $suite / ${suite}-updates /
|
||||
" "$rootfsDir/etc/apt/sources.list"
|
||||
)
|
||||
fi
|
||||
;;
|
||||
steamos)
|
||||
# add contrib and non-free if "main" is the only component
|
||||
(
|
||||
set -x
|
||||
sed -i "s/ $suite main$/ $suite main contrib non-free/" "$rootfsDir/etc/apt/sources.list"
|
||||
)
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
(
|
||||
set -x
|
||||
|
||||
# make sure we're fully up-to-date
|
||||
chroot "$rootfsDir" bash -c 'apt-get update && apt-get dist-upgrade -y'
|
||||
|
||||
# delete all the apt list files since they're big and get stale quickly
|
||||
rm -rf "$rootfsDir/var/lib/apt/lists"/*
|
||||
# this forces "apt-get update" in dependent images, which is also good
|
||||
)
|
||||
@@ -1,61 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Needs to be run from Mageia 4 or greater for kernel support for docker.
|
||||
#
|
||||
# Mageia 4 does not have docker available in official repos, so please
|
||||
# install and run the docker binary manually.
|
||||
#
|
||||
# Tested working versions are for Mageia 2 onwards (inc. cauldron).
|
||||
#
|
||||
set -e
|
||||
|
||||
rootfsDir="$1"
|
||||
shift
|
||||
|
||||
optTemp=$(getopt --options '+v:,m:' --longoptions 'version:,mirror:' --name mageia-urpmi -- "$@")
|
||||
eval set -- "$optTemp"
|
||||
unset optTemp
|
||||
|
||||
installversion=
|
||||
mirror=
|
||||
while true; do
|
||||
case "$1" in
|
||||
-v|--version) installversion="$2" ; shift 2 ;;
|
||||
-m|--mirror) mirror="$2" ; shift 2 ;;
|
||||
--) shift ; break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z $installversion ]; then
|
||||
# Attempt to match host version
|
||||
if [ -r /etc/mageia-release ]; then
|
||||
installversion="$(sed 's/^[^0-9\]*\([0-9.]\+\).*$/\1/' /etc/mageia-release)"
|
||||
else
|
||||
echo "Error: no version supplied and unable to detect host mageia version"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z $mirror ]; then
|
||||
# No mirror provided, default to mirrorlist
|
||||
mirror="--mirrorlist https://mirrors.mageia.org/api/mageia.$installversion.x86_64.list"
|
||||
fi
|
||||
|
||||
(
|
||||
set -x
|
||||
urpmi.addmedia --distrib \
|
||||
$mirror \
|
||||
--urpmi-root "$rootfsDir"
|
||||
urpmi basesystem-minimal urpmi \
|
||||
--auto \
|
||||
--no-suggests \
|
||||
--urpmi-root "$rootfsDir" \
|
||||
--root "$rootfsDir"
|
||||
)
|
||||
|
||||
"$(dirname "$BASH_SOURCE")/.febootstrap-minimize" "$rootfsDir"
|
||||
|
||||
if [ -d "$rootfsDir/etc/sysconfig" ]; then
|
||||
# allow networking init scripts inside the container to work without extra steps
|
||||
echo 'NETWORKING=yes' > "$rootfsDir/etc/sysconfig/network"
|
||||
fi
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
rootfsDir="$1"
|
||||
shift
|
||||
|
||||
# specifying --arch below is safe because "$@" can override it and the "latest" one wins :)
|
||||
|
||||
(
|
||||
set -x
|
||||
rinse --directory "$rootfsDir" --arch amd64 "$@"
|
||||
)
|
||||
|
||||
"$(dirname "$BASH_SOURCE")/.febootstrap-minimize" "$rootfsDir"
|
||||
|
||||
if [ -d "$rootfsDir/etc/sysconfig" ]; then
|
||||
# allow networking init scripts inside the container to work without extra steps
|
||||
echo 'NETWORKING=yes' > "$rootfsDir/etc/sysconfig/network"
|
||||
fi
|
||||
|
||||
# make sure we're fully up-to-date, too
|
||||
(
|
||||
set -x
|
||||
chroot "$rootfsDir" yum update -y
|
||||
)
|
||||
@@ -10,7 +10,7 @@
|
||||
# can configure the list of syscalls. When run, this script produces output
|
||||
# which, when stored in a file, can be passed to docker as follows:
|
||||
#
|
||||
# docker run --lxc-conf="lxc.seccomp=$file" <rest of arguments>
|
||||
# docker run -lxc-conf="lxc.seccomp=$file" <rest of arguments>
|
||||
#
|
||||
# The included sample file shows how to cut about a quarter of all syscalls,
|
||||
# which affecting most applications.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<array>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>^\s*(ONBUILD\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR|COPY)\s</string>
|
||||
<string>^\s*(ONBUILD\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR)\s</string>
|
||||
<key>captures</key>
|
||||
<dict>
|
||||
<key>0</key>
|
||||
|
||||
@@ -11,7 +11,7 @@ let b:current_syntax = "dockerfile"
|
||||
|
||||
syntax case ignore
|
||||
|
||||
syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|VOLUME|WORKDIR|COPY)\s/
|
||||
syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|VOLUME|WORKDIR)\s/
|
||||
highlight link dockerfileKeyword Keyword
|
||||
|
||||
syntax region dockerfileString start=/\v"/ skip=/\v\\./ end=/\v"/
|
||||
|
||||
@@ -31,20 +31,20 @@ stop on runlevel [!2345]
|
||||
respawn
|
||||
|
||||
script
|
||||
/usr/bin/docker -d -H=tcp://0.0.0.0:2375
|
||||
/usr/bin/docker -d -H=tcp://0.0.0.0:4243
|
||||
end script
|
||||
```
|
||||
|
||||
Once that's done, you need to set up a SSH tunnel between your host machine and the vagrant machine that's running Docker. This can be done by running the following command in a host terminal:
|
||||
|
||||
```
|
||||
ssh -L 2375:localhost:2375 -p 2222 vagrant@localhost
|
||||
ssh -L 4243:localhost:4243 -p 2222 vagrant@localhost
|
||||
```
|
||||
|
||||
(The first 2375 is what your host can connect to, the second 2375 is what port Docker is running on in the vagrant machine, and the 2222 is the port Vagrant is providing for SSH. If VirtualBox is the VM you're using, you can see what value "2222" should be by going to: Network > Adapter 1 > Advanced > Port Forwarding in the VirtualBox GUI.)
|
||||
(The first 4243 is what your host can connect to, the second 4243 is what port Docker is running on in the vagrant machine, and the 2222 is the port Vagrant is providing for SSH. If VirtualBox is the VM you're using, you can see what value "2222" should be by going to: Network > Adapter 1 > Advanced > Port Forwarding in the VirtualBox GUI.)
|
||||
|
||||
Note that because the port has been changed, to run docker commands from within the command line you must run them like this:
|
||||
|
||||
```
|
||||
sudo docker -H 0.0.0.0:2375 < commands for docker >
|
||||
sudo docker -H 0.0.0.0:4243 < commands for docker >
|
||||
```
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Gurjeet Singh <gurjeet@singh.im> (gurjeet.singh.im)
|
||||
@@ -0,0 +1,23 @@
|
||||
# ZFS Storage Driver
|
||||
|
||||
This is a placeholder to declare the presence and status of ZFS storage driver
|
||||
for containers.
|
||||
|
||||
The current development is done in Gurjeet Singh's fork of Docker, under the
|
||||
branch named [zfs_driver].
|
||||
|
||||
[zfs_driver]: https://github.com/gurjeet/docker/tree/zfs_driver
|
||||
|
||||
|
||||
# Status
|
||||
|
||||
Alpha: The code is now capable of creating, running and destroying containers
|
||||
and images.
|
||||
|
||||
The code is under development. Contributions in the form of suggestions,
|
||||
code-reviews, and patches are welcome.
|
||||
|
||||
Please send the communication to gurjeet@singh.im and CC at least one Docker
|
||||
mailing list.
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
This directory contains code pertaining to running containers and storing images
|
||||
|
||||
Code pertaining to running containers:
|
||||
|
||||
- execdriver
|
||||
- networkdriver
|
||||
|
||||
Code pertaining to storing images:
|
||||
|
||||
- graphdriver
|
||||
@@ -1,153 +0,0 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/dotcloud/docker/utils"
|
||||
)
|
||||
|
||||
func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
|
||||
var (
|
||||
cStdout, cStderr io.ReadCloser
|
||||
nJobs int
|
||||
errors = make(chan error, 3)
|
||||
)
|
||||
|
||||
if stdin != nil && container.Config.OpenStdin {
|
||||
nJobs += 1
|
||||
if cStdin, err := container.StdinPipe(); err != nil {
|
||||
errors <- err
|
||||
} else {
|
||||
go func() {
|
||||
utils.Debugf("attach: stdin: begin")
|
||||
defer utils.Debugf("attach: stdin: end")
|
||||
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
||||
if container.Config.StdinOnce && !container.Config.Tty {
|
||||
defer cStdin.Close()
|
||||
} else {
|
||||
defer func() {
|
||||
if cStdout != nil {
|
||||
cStdout.Close()
|
||||
}
|
||||
if cStderr != nil {
|
||||
cStderr.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
if container.Config.Tty {
|
||||
_, err = utils.CopyEscapable(cStdin, stdin)
|
||||
} else {
|
||||
_, err = io.Copy(cStdin, stdin)
|
||||
}
|
||||
if err == io.ErrClosedPipe {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
utils.Errorf("attach: stdin: %s", err)
|
||||
}
|
||||
errors <- err
|
||||
}()
|
||||
}
|
||||
}
|
||||
if stdout != nil {
|
||||
nJobs += 1
|
||||
if p, err := container.StdoutPipe(); err != nil {
|
||||
errors <- err
|
||||
} else {
|
||||
cStdout = p
|
||||
go func() {
|
||||
utils.Debugf("attach: stdout: begin")
|
||||
defer utils.Debugf("attach: stdout: end")
|
||||
// If we are in StdinOnce mode, then close stdin
|
||||
if container.Config.StdinOnce && stdin != nil {
|
||||
defer stdin.Close()
|
||||
}
|
||||
if stdinCloser != nil {
|
||||
defer stdinCloser.Close()
|
||||
}
|
||||
_, err := io.Copy(stdout, cStdout)
|
||||
if err == io.ErrClosedPipe {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
utils.Errorf("attach: stdout: %s", err)
|
||||
}
|
||||
errors <- err
|
||||
}()
|
||||
}
|
||||
} else {
|
||||
go func() {
|
||||
if stdinCloser != nil {
|
||||
defer stdinCloser.Close()
|
||||
}
|
||||
if cStdout, err := container.StdoutPipe(); err != nil {
|
||||
utils.Errorf("attach: stdout pipe: %s", err)
|
||||
} else {
|
||||
io.Copy(&utils.NopWriter{}, cStdout)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if stderr != nil {
|
||||
nJobs += 1
|
||||
if p, err := container.StderrPipe(); err != nil {
|
||||
errors <- err
|
||||
} else {
|
||||
cStderr = p
|
||||
go func() {
|
||||
utils.Debugf("attach: stderr: begin")
|
||||
defer utils.Debugf("attach: stderr: end")
|
||||
// If we are in StdinOnce mode, then close stdin
|
||||
if container.Config.StdinOnce && stdin != nil {
|
||||
defer stdin.Close()
|
||||
}
|
||||
if stdinCloser != nil {
|
||||
defer stdinCloser.Close()
|
||||
}
|
||||
_, err := io.Copy(stderr, cStderr)
|
||||
if err == io.ErrClosedPipe {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
utils.Errorf("attach: stderr: %s", err)
|
||||
}
|
||||
errors <- err
|
||||
}()
|
||||
}
|
||||
} else {
|
||||
go func() {
|
||||
if stdinCloser != nil {
|
||||
defer stdinCloser.Close()
|
||||
}
|
||||
|
||||
if cStderr, err := container.StderrPipe(); err != nil {
|
||||
utils.Errorf("attach: stdout pipe: %s", err)
|
||||
} else {
|
||||
io.Copy(&utils.NopWriter{}, cStderr)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return utils.Go(func() error {
|
||||
defer func() {
|
||||
if cStdout != nil {
|
||||
cStdout.Close()
|
||||
}
|
||||
if cStderr != nil {
|
||||
cStderr.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// FIXME: how to clean up the stdin goroutine without the unwanted side effect
|
||||
// of closing the passed stdin? Add an intermediary io.Pipe?
|
||||
for i := 0; i < nJobs; i += 1 {
|
||||
utils.Debugf("attach: waiting for job %d/%d", i+1, nJobs)
|
||||
if err := <-errors; err != nil {
|
||||
utils.Errorf("attach: job %d returned error %s, aborting all jobs", i+1, err)
|
||||
return err
|
||||
}
|
||||
utils.Debugf("attach: job %d completed successfully", i+1)
|
||||
}
|
||||
utils.Debugf("attach: all jobs completed successfully")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
-1172
File diff suppressed because it is too large
Load Diff
-1095
File diff suppressed because it is too large
Load Diff
@@ -1,22 +0,0 @@
|
||||
// +build !exclude_graphdriver_aufs
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/daemon/graphdriver"
|
||||
"github.com/dotcloud/docker/daemon/graphdriver/aufs"
|
||||
"github.com/dotcloud/docker/graph"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
)
|
||||
|
||||
// Given the graphdriver ad, if it is aufs, then migrate it.
|
||||
// If aufs driver is not built, this func is a noop.
|
||||
func migrateIfAufs(driver graphdriver.Driver, root string) error {
|
||||
if ad, ok := driver.(*aufs.Driver); ok {
|
||||
utils.Debugf("Migrating existing containers")
|
||||
if err := ad.Migrate(root, graph.SetupInitLayer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
// +build !exclude_graphdriver_btrfs
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
_ "github.com/dotcloud/docker/daemon/graphdriver/btrfs"
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
// +build !exclude_graphdriver_devicemapper
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
_ "github.com/dotcloud/docker/daemon/graphdriver/devmapper"
|
||||
)
|
||||
@@ -1,11 +0,0 @@
|
||||
// +build exclude_graphdriver_aufs
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/daemon/graphdriver"
|
||||
)
|
||||
|
||||
func migrateIfAufs(driver graphdriver.Driver, root string) error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package execdrivers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/daemon/execdriver"
|
||||
"github.com/dotcloud/docker/daemon/execdriver/lxc"
|
||||
"github.com/dotcloud/docker/daemon/execdriver/native"
|
||||
"github.com/dotcloud/docker/pkg/sysinfo"
|
||||
"path"
|
||||
)
|
||||
|
||||
func NewDriver(name, root, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
|
||||
switch name {
|
||||
case "lxc":
|
||||
// we want to give the lxc driver the full docker root because it needs
|
||||
// to access and write config and template files in /var/lib/docker/containers/*
|
||||
// to be backwards compatible
|
||||
return lxc.NewDriver(root, sysInfo.AppArmor)
|
||||
case "native":
|
||||
return native.NewDriver(path.Join(root, "execdriver", "native"), initPath)
|
||||
}
|
||||
return nil, fmt.Errorf("unknown exec driver %s", name)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
Dinesh Subhraveti <dineshs@altiscale.com> (@dineshs-altiscale)
|
||||
@@ -1,62 +0,0 @@
|
||||
// +build amd64
|
||||
|
||||
package lxc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/libcontainer/namespaces"
|
||||
"github.com/docker/libcontainer/security/capabilities"
|
||||
"github.com/docker/libcontainer/utils"
|
||||
"github.com/dotcloud/docker/daemon/execdriver"
|
||||
"github.com/dotcloud/docker/daemon/execdriver/native/template"
|
||||
"github.com/dotcloud/docker/pkg/system"
|
||||
)
|
||||
|
||||
func setHostname(hostname string) error {
|
||||
return syscall.Sethostname([]byte(hostname))
|
||||
}
|
||||
|
||||
func finalizeNamespace(args *execdriver.InitArgs) error {
|
||||
if err := utils.CloseExecFrom(3); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We use the native drivers default template so that things like caps are consistent
|
||||
// across both drivers
|
||||
container := template.New()
|
||||
|
||||
if !args.Privileged {
|
||||
// drop capabilities in bounding set before changing user
|
||||
if err := capabilities.DropBoundingSet(container.Capabilities); err != nil {
|
||||
return fmt.Errorf("drop bounding set %s", err)
|
||||
}
|
||||
|
||||
// preserve existing capabilities while we change users
|
||||
if err := system.SetKeepCaps(); err != nil {
|
||||
return fmt.Errorf("set keep caps %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := namespaces.SetupUser(args.User); err != nil {
|
||||
return fmt.Errorf("setup user %s", err)
|
||||
}
|
||||
|
||||
if !args.Privileged {
|
||||
if err := system.ClearKeepCaps(); err != nil {
|
||||
return fmt.Errorf("clear keep caps %s", err)
|
||||
}
|
||||
|
||||
// drop all other capabilities
|
||||
if err := capabilities.DropCapabilities(container.Capabilities); err != nil {
|
||||
return fmt.Errorf("drop capabilities %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := setupWorkingDirectory(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// +build !linux !amd64
|
||||
|
||||
package lxc
|
||||
|
||||
import "github.com/dotcloud/docker/daemon/execdriver"
|
||||
|
||||
func setHostname(hostname string) error {
|
||||
panic("Not supported on darwin")
|
||||
}
|
||||
|
||||
func finalizeNamespace(args *execdriver.InitArgs) error {
|
||||
panic("Not supported on darwin")
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/libcontainer"
|
||||
"github.com/dotcloud/docker/pkg/units"
|
||||
)
|
||||
|
||||
type Action func(*libcontainer.Config, interface{}, string) error
|
||||
|
||||
var actions = map[string]Action{
|
||||
"cap.add": addCap, // add a cap
|
||||
"cap.drop": dropCap, // drop a cap
|
||||
|
||||
"ns.add": addNamespace, // add a namespace
|
||||
"ns.drop": dropNamespace, // drop a namespace when cloning
|
||||
|
||||
"net.join": joinNetNamespace, // join another containers net namespace
|
||||
|
||||
"cgroups.cpu_shares": cpuShares, // set the cpu shares
|
||||
"cgroups.memory": memory, // set the memory limit
|
||||
"cgroups.memory_reservation": memoryReservation, // set the memory reservation
|
||||
"cgroups.memory_swap": memorySwap, // set the memory swap limit
|
||||
"cgroups.cpuset.cpus": cpusetCpus, // set the cpus used
|
||||
|
||||
"systemd.slice": systemdSlice, // set parent Slice used for systemd unit
|
||||
|
||||
"apparmor_profile": apparmorProfile, // set the apparmor profile to apply
|
||||
|
||||
"fs.readonly": readonlyFs, // make the rootfs of the container read only
|
||||
}
|
||||
|
||||
func cpusetCpus(container *libcontainer.Config, context interface{}, value string) error {
|
||||
if container.Cgroups == nil {
|
||||
return fmt.Errorf("cannot set cgroups when they are disabled")
|
||||
}
|
||||
container.Cgroups.CpusetCpus = value
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func systemdSlice(container *libcontainer.Config, context interface{}, value string) error {
|
||||
if container.Cgroups == nil {
|
||||
return fmt.Errorf("cannot set slice when cgroups are disabled")
|
||||
}
|
||||
container.Cgroups.Slice = value
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func apparmorProfile(container *libcontainer.Config, context interface{}, value string) error {
|
||||
container.AppArmorProfile = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func cpuShares(container *libcontainer.Config, context interface{}, value string) error {
|
||||
if container.Cgroups == nil {
|
||||
return fmt.Errorf("cannot set cgroups when they are disabled")
|
||||
}
|
||||
v, err := strconv.ParseInt(value, 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.Cgroups.CpuShares = v
|
||||
return nil
|
||||
}
|
||||
|
||||
func memory(container *libcontainer.Config, context interface{}, value string) error {
|
||||
if container.Cgroups == nil {
|
||||
return fmt.Errorf("cannot set cgroups when they are disabled")
|
||||
}
|
||||
|
||||
v, err := units.RAMInBytes(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.Cgroups.Memory = v
|
||||
return nil
|
||||
}
|
||||
|
||||
func memoryReservation(container *libcontainer.Config, context interface{}, value string) error {
|
||||
if container.Cgroups == nil {
|
||||
return fmt.Errorf("cannot set cgroups when they are disabled")
|
||||
}
|
||||
|
||||
v, err := units.RAMInBytes(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.Cgroups.MemoryReservation = v
|
||||
return nil
|
||||
}
|
||||
|
||||
func memorySwap(container *libcontainer.Config, context interface{}, value string) error {
|
||||
if container.Cgroups == nil {
|
||||
return fmt.Errorf("cannot set cgroups when they are disabled")
|
||||
}
|
||||
v, err := strconv.ParseInt(value, 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.Cgroups.MemorySwap = v
|
||||
return nil
|
||||
}
|
||||
|
||||
func addCap(container *libcontainer.Config, context interface{}, value string) error {
|
||||
container.Capabilities = append(container.Capabilities, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func dropCap(container *libcontainer.Config, context interface{}, value string) error {
|
||||
// If the capability is specified multiple times, remove all instances.
|
||||
for i, capability := range container.Capabilities {
|
||||
if capability == value {
|
||||
container.Capabilities = append(container.Capabilities[:i], container.Capabilities[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
// The capability wasn't found so we will drop it anyways.
|
||||
return nil
|
||||
}
|
||||
|
||||
func addNamespace(container *libcontainer.Config, context interface{}, value string) error {
|
||||
container.Namespaces[value] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func dropNamespace(container *libcontainer.Config, context interface{}, value string) error {
|
||||
container.Namespaces[value] = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func readonlyFs(container *libcontainer.Config, context interface{}, value string) error {
|
||||
switch value {
|
||||
case "1", "true":
|
||||
container.MountConfig.ReadonlyFs = true
|
||||
default:
|
||||
container.MountConfig.ReadonlyFs = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func joinNetNamespace(container *libcontainer.Config, context interface{}, value string) error {
|
||||
var (
|
||||
running = context.(map[string]*exec.Cmd)
|
||||
cmd = running[value]
|
||||
)
|
||||
|
||||
if cmd == nil || cmd.Process == nil {
|
||||
return fmt.Errorf("%s is not a valid running container to join", value)
|
||||
}
|
||||
|
||||
nspath := filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "net")
|
||||
container.Networks = append(container.Networks, &libcontainer.Network{
|
||||
Type: "netns",
|
||||
NsPath: nspath,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// configureCustomOptions takes string commands from the user and allows modification of the
|
||||
// container's default configuration.
|
||||
//
|
||||
// TODO: this can be moved to a general utils or parser in pkg
|
||||
func ParseConfiguration(container *libcontainer.Config, running map[string]*exec.Cmd, opts []string) error {
|
||||
for _, opt := range opts {
|
||||
kv := strings.SplitN(opt, "=", 2)
|
||||
if len(kv) < 2 {
|
||||
return fmt.Errorf("invalid format for %s", opt)
|
||||
}
|
||||
|
||||
action, exists := actions[kv[0]]
|
||||
if !exists {
|
||||
return fmt.Errorf("%s is not a valid option for the native driver", kv[0])
|
||||
}
|
||||
|
||||
if err := action(container, running, kv[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/libcontainer/security/capabilities"
|
||||
"github.com/dotcloud/docker/daemon/execdriver/native/template"
|
||||
)
|
||||
|
||||
// Checks whether the expected capability is specified in the capabilities.
|
||||
func hasCapability(expected string, capabilities []string) bool {
|
||||
for _, capability := range capabilities {
|
||||
if capability == expected {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestSetReadonlyRootFs(t *testing.T) {
|
||||
var (
|
||||
container = template.New()
|
||||
opts = []string{
|
||||
"fs.readonly=true",
|
||||
}
|
||||
)
|
||||
|
||||
if container.MountConfig.ReadonlyFs {
|
||||
t.Fatal("container should not have a readonly rootfs by default")
|
||||
}
|
||||
if err := ParseConfiguration(container, nil, opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !container.MountConfig.ReadonlyFs {
|
||||
t.Fatal("container should have a readonly rootfs")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigurationsDoNotConflict(t *testing.T) {
|
||||
var (
|
||||
container1 = template.New()
|
||||
container2 = template.New()
|
||||
opts = []string{
|
||||
"cap.add=NET_ADMIN",
|
||||
}
|
||||
)
|
||||
|
||||
if err := ParseConfiguration(container1, nil, opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !hasCapability("NET_ADMIN", container1.Capabilities) {
|
||||
t.Fatal("container one should have NET_ADMIN enabled")
|
||||
}
|
||||
if hasCapability("NET_ADMIN", container2.Capabilities) {
|
||||
t.Fatal("container two should not have NET_ADMIN enabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCpusetCpus(t *testing.T) {
|
||||
var (
|
||||
container = template.New()
|
||||
opts = []string{
|
||||
"cgroups.cpuset.cpus=1,2",
|
||||
}
|
||||
)
|
||||
if err := ParseConfiguration(container, nil, opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := "1,2"; container.Cgroups.CpusetCpus != expected {
|
||||
t.Fatalf("expected %s got %s for cpuset.cpus", expected, container.Cgroups.CpusetCpus)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppArmorProfile(t *testing.T) {
|
||||
var (
|
||||
container = template.New()
|
||||
opts = []string{
|
||||
"apparmor_profile=koye-the-protector",
|
||||
}
|
||||
)
|
||||
if err := ParseConfiguration(container, nil, opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := "koye-the-protector"; container.AppArmorProfile != expected {
|
||||
t.Fatalf("expected profile %s got %s", expected, container.AppArmorProfile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCpuShares(t *testing.T) {
|
||||
var (
|
||||
container = template.New()
|
||||
opts = []string{
|
||||
"cgroups.cpu_shares=1048",
|
||||
}
|
||||
)
|
||||
if err := ParseConfiguration(container, nil, opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := int64(1048); container.Cgroups.CpuShares != expected {
|
||||
t.Fatalf("expected cpu shares %d got %d", expected, container.Cgroups.CpuShares)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemory(t *testing.T) {
|
||||
var (
|
||||
container = template.New()
|
||||
opts = []string{
|
||||
"cgroups.memory=500m",
|
||||
}
|
||||
)
|
||||
if err := ParseConfiguration(container, nil, opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := int64(500 * 1024 * 1024); container.Cgroups.Memory != expected {
|
||||
t.Fatalf("expected memory %d got %d", expected, container.Cgroups.Memory)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryReservation(t *testing.T) {
|
||||
var (
|
||||
container = template.New()
|
||||
opts = []string{
|
||||
"cgroups.memory_reservation=500m",
|
||||
}
|
||||
)
|
||||
if err := ParseConfiguration(container, nil, opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if expected := int64(500 * 1024 * 1024); container.Cgroups.MemoryReservation != expected {
|
||||
t.Fatalf("expected memory reservation %d got %d", expected, container.Cgroups.MemoryReservation)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddCap(t *testing.T) {
|
||||
var (
|
||||
container = template.New()
|
||||
opts = []string{
|
||||
"cap.add=MKNOD",
|
||||
"cap.add=SYS_ADMIN",
|
||||
}
|
||||
)
|
||||
if err := ParseConfiguration(container, nil, opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !hasCapability("MKNOD", container.Capabilities) {
|
||||
t.Fatal("container should have MKNOD enabled")
|
||||
}
|
||||
if !hasCapability("SYS_ADMIN", container.Capabilities) {
|
||||
t.Fatal("container should have SYS_ADMIN enabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDropCap(t *testing.T) {
|
||||
var (
|
||||
container = template.New()
|
||||
opts = []string{
|
||||
"cap.drop=MKNOD",
|
||||
}
|
||||
)
|
||||
// enabled all caps like in privileged mode
|
||||
container.Capabilities = capabilities.GetAllCapabilities()
|
||||
if err := ParseConfiguration(container, nil, opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if hasCapability("MKNOD", container.Capabilities) {
|
||||
t.Fatal("container should not have MKNOD enabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDropNamespace(t *testing.T) {
|
||||
var (
|
||||
container = template.New()
|
||||
opts = []string{
|
||||
"ns.drop=NEWNET",
|
||||
}
|
||||
)
|
||||
if err := ParseConfiguration(container, nil, opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if container.Namespaces["NEWNET"] {
|
||||
t.Fatal("container should not have NEWNET enabled")
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/apparmor"
|
||||
"github.com/docker/libcontainer/devices"
|
||||
"github.com/docker/libcontainer/mount"
|
||||
"github.com/docker/libcontainer/security/capabilities"
|
||||
"github.com/dotcloud/docker/daemon/execdriver"
|
||||
"github.com/dotcloud/docker/daemon/execdriver/native/configuration"
|
||||
"github.com/dotcloud/docker/daemon/execdriver/native/template"
|
||||
)
|
||||
|
||||
// createContainer populates and configures the container type with the
|
||||
// data provided by the execdriver.Command
|
||||
func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, error) {
|
||||
container := template.New()
|
||||
|
||||
container.Hostname = getEnv("HOSTNAME", c.Env)
|
||||
container.Tty = c.Tty
|
||||
container.User = c.User
|
||||
container.WorkingDir = c.WorkingDir
|
||||
container.Env = c.Env
|
||||
container.Cgroups.Name = c.ID
|
||||
container.Cgroups.AllowedDevices = c.AllowedDevices
|
||||
container.MountConfig.DeviceNodes = c.AutoCreatedDevices
|
||||
|
||||
// check to see if we are running in ramdisk to disable pivot root
|
||||
container.MountConfig.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != ""
|
||||
container.RestrictSys = true
|
||||
|
||||
if err := d.createNetwork(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.Privileged {
|
||||
if err := d.setPrivileged(container); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := d.setupCgroups(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := d.setupMounts(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := d.setupLabels(container, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmds := make(map[string]*exec.Cmd)
|
||||
d.Lock()
|
||||
for k, v := range d.activeContainers {
|
||||
cmds[k] = v.cmd
|
||||
}
|
||||
d.Unlock()
|
||||
|
||||
if err := configuration.ParseConfiguration(container, cmds, c.Config["native"]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Command) error {
|
||||
if c.Network.HostNetworking {
|
||||
container.Namespaces["NEWNET"] = false
|
||||
return nil
|
||||
}
|
||||
|
||||
container.Networks = []*libcontainer.Network{
|
||||
{
|
||||
Mtu: c.Network.Mtu,
|
||||
Address: fmt.Sprintf("%s/%d", "127.0.0.1", 0),
|
||||
Gateway: "localhost",
|
||||
Type: "loopback",
|
||||
},
|
||||
}
|
||||
|
||||
if c.Network.Interface != nil {
|
||||
vethNetwork := libcontainer.Network{
|
||||
Mtu: c.Network.Mtu,
|
||||
Address: fmt.Sprintf("%s/%d", c.Network.Interface.IPAddress, c.Network.Interface.IPPrefixLen),
|
||||
Gateway: c.Network.Interface.Gateway,
|
||||
Type: "veth",
|
||||
Bridge: c.Network.Interface.Bridge,
|
||||
VethPrefix: "veth",
|
||||
}
|
||||
container.Networks = append(container.Networks, &vethNetwork)
|
||||
}
|
||||
|
||||
if c.Network.ContainerID != "" {
|
||||
d.Lock()
|
||||
active := d.activeContainers[c.Network.ContainerID]
|
||||
d.Unlock()
|
||||
|
||||
if active == nil || active.cmd.Process == nil {
|
||||
return fmt.Errorf("%s is not a valid running container to join", c.Network.ContainerID)
|
||||
}
|
||||
cmd := active.cmd
|
||||
|
||||
nspath := filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "net")
|
||||
container.Networks = append(container.Networks, &libcontainer.Network{
|
||||
Type: "netns",
|
||||
NsPath: nspath,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *driver) setPrivileged(container *libcontainer.Config) (err error) {
|
||||
container.Capabilities = capabilities.GetAllCapabilities()
|
||||
container.Cgroups.AllowAllDevices = true
|
||||
|
||||
hostDeviceNodes, err := devices.GetHostDeviceNodes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.MountConfig.DeviceNodes = hostDeviceNodes
|
||||
|
||||
container.RestrictSys = false
|
||||
|
||||
if apparmor.IsEnabled() {
|
||||
container.AppArmorProfile = "unconfined"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *driver) setupCgroups(container *libcontainer.Config, c *execdriver.Command) error {
|
||||
if c.Resources != nil {
|
||||
container.Cgroups.CpuShares = c.Resources.CpuShares
|
||||
container.Cgroups.Memory = c.Resources.Memory
|
||||
container.Cgroups.MemoryReservation = c.Resources.Memory
|
||||
container.Cgroups.MemorySwap = c.Resources.MemorySwap
|
||||
container.Cgroups.CpusetCpus = c.Resources.Cpuset
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *driver) setupMounts(container *libcontainer.Config, c *execdriver.Command) error {
|
||||
for _, m := range c.Mounts {
|
||||
container.MountConfig.Mounts = append(container.MountConfig.Mounts, mount.Mount{
|
||||
Type: "bind",
|
||||
Source: m.Source,
|
||||
Destination: m.Destination,
|
||||
Writable: m.Writable,
|
||||
Private: m.Private,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *driver) setupLabels(container *libcontainer.Config, c *execdriver.Command) error {
|
||||
container.ProcessLabel = c.Config["process_label"][0]
|
||||
container.MountConfig.MountLabel = c.Config["mount_label"][0]
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
package native
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/apparmor"
|
||||
"github.com/docker/libcontainer/cgroups/fs"
|
||||
"github.com/docker/libcontainer/cgroups/systemd"
|
||||
"github.com/docker/libcontainer/namespaces"
|
||||
"github.com/dotcloud/docker/daemon/execdriver"
|
||||
"github.com/dotcloud/docker/pkg/system"
|
||||
)
|
||||
|
||||
const (
|
||||
DriverName = "native"
|
||||
Version = "0.2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
execdriver.RegisterInitFunc(DriverName, func(args *execdriver.InitArgs) error {
|
||||
var container *libcontainer.Config
|
||||
f, err := os.Open(filepath.Join(args.Root, "container.json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.NewDecoder(f).Decode(&container); err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
|
||||
rootfs, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
syncPipe, err := namespaces.NewSyncPipeFromFd(0, uintptr(args.Pipe))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := namespaces.Init(container, rootfs, args.Console, syncPipe, args.Args); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
type activeContainer struct {
|
||||
container *libcontainer.Config
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
type driver struct {
|
||||
root string
|
||||
initPath string
|
||||
activeContainers map[string]*activeContainer
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewDriver(root, initPath string) (*driver, error) {
|
||||
if err := os.MkdirAll(root, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// native driver root is at docker_root/execdriver/native. Put apparmor at docker_root
|
||||
if err := apparmor.InstallDefaultProfile(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &driver{
|
||||
root: root,
|
||||
initPath: initPath,
|
||||
activeContainers: make(map[string]*activeContainer),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
||||
// take the Command and populate the libcontainer.Config from it
|
||||
container, err := d.createContainer(c)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
d.Lock()
|
||||
d.activeContainers[c.ID] = &activeContainer{
|
||||
container: container,
|
||||
cmd: &c.Cmd,
|
||||
}
|
||||
d.Unlock()
|
||||
|
||||
var (
|
||||
dataPath = filepath.Join(d.root, c.ID)
|
||||
args = append([]string{c.Entrypoint}, c.Arguments...)
|
||||
)
|
||||
if err := d.createContainerRoot(c.ID); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
defer d.removeContainerRoot(c.ID)
|
||||
|
||||
if err := d.writeContainerFile(container, c.ID); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
term := getTerminal(c, pipes)
|
||||
|
||||
return namespaces.Exec(container, term, c.Rootfs, dataPath, args, func(container *libcontainer.Config, console, rootfs, dataPath, init string, child *os.File, args []string) *exec.Cmd {
|
||||
// we need to join the rootfs because namespaces will setup the rootfs and chroot
|
||||
initPath := filepath.Join(c.Rootfs, c.InitPath)
|
||||
|
||||
c.Path = d.initPath
|
||||
c.Args = append([]string{
|
||||
initPath,
|
||||
"-driver", DriverName,
|
||||
"-console", console,
|
||||
"-pipe", "3",
|
||||
"-root", filepath.Join(d.root, c.ID),
|
||||
"--",
|
||||
}, args...)
|
||||
|
||||
// set this to nil so that when we set the clone flags anything else is reset
|
||||
c.SysProcAttr = nil
|
||||
system.SetCloneFlags(&c.Cmd, uintptr(namespaces.GetNamespaceFlags(container.Namespaces)))
|
||||
c.ExtraFiles = []*os.File{child}
|
||||
|
||||
c.Env = container.Env
|
||||
c.Dir = c.Rootfs
|
||||
|
||||
return &c.Cmd
|
||||
}, func() {
|
||||
if startCallback != nil {
|
||||
c.ContainerPid = c.Process.Pid
|
||||
startCallback(c)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (d *driver) Kill(p *execdriver.Command, sig int) error {
|
||||
return syscall.Kill(p.Process.Pid, syscall.Signal(sig))
|
||||
}
|
||||
|
||||
func (d *driver) Pause(c *execdriver.Command) error {
|
||||
active := d.activeContainers[c.ID]
|
||||
if active == nil {
|
||||
return fmt.Errorf("active container for %s does not exist", c.ID)
|
||||
}
|
||||
active.container.Cgroups.Freezer = "FROZEN"
|
||||
if systemd.UseSystemd() {
|
||||
return systemd.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
|
||||
}
|
||||
return fs.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
|
||||
}
|
||||
|
||||
func (d *driver) Unpause(c *execdriver.Command) error {
|
||||
active := d.activeContainers[c.ID]
|
||||
if active == nil {
|
||||
return fmt.Errorf("active container for %s does not exist", c.ID)
|
||||
}
|
||||
active.container.Cgroups.Freezer = "THAWED"
|
||||
if systemd.UseSystemd() {
|
||||
return systemd.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
|
||||
}
|
||||
return fs.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
|
||||
}
|
||||
|
||||
func (d *driver) Terminate(p *execdriver.Command) error {
|
||||
// lets check the start time for the process
|
||||
state, err := libcontainer.GetState(filepath.Join(d.root, p.ID))
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
// TODO: Remove this part for version 1.2.0
|
||||
// This is added only to ensure smooth upgrades from pre 1.1.0 to 1.1.0
|
||||
data, err := ioutil.ReadFile(filepath.Join(d.root, p.ID, "start"))
|
||||
if err != nil {
|
||||
// if we don't have the data on disk then we can assume the process is gone
|
||||
// because this is only removed after we know the process has stopped
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
state = &libcontainer.State{InitStartTime: string(data)}
|
||||
}
|
||||
|
||||
currentStartTime, err := system.GetProcessStartTime(p.Process.Pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state.InitStartTime == currentStartTime {
|
||||
err = syscall.Kill(p.Process.Pid, 9)
|
||||
syscall.Wait4(p.Process.Pid, nil, 0, nil)
|
||||
}
|
||||
d.removeContainerRoot(p.ID)
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
func (d *driver) Info(id string) execdriver.Info {
|
||||
return &info{
|
||||
ID: id,
|
||||
driver: d,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *driver) Name() string {
|
||||
return fmt.Sprintf("%s-%s", DriverName, Version)
|
||||
}
|
||||
|
||||
func (d *driver) GetPidsForContainer(id string) ([]int, error) {
|
||||
d.Lock()
|
||||
active := d.activeContainers[id]
|
||||
d.Unlock()
|
||||
|
||||
if active == nil {
|
||||
return nil, fmt.Errorf("active container for %s does not exist", id)
|
||||
}
|
||||
c := active.container.Cgroups
|
||||
|
||||
if systemd.UseSystemd() {
|
||||
return systemd.GetPids(c)
|
||||
}
|
||||
return fs.GetPids(c)
|
||||
}
|
||||
|
||||
func (d *driver) writeContainerFile(container *libcontainer.Config, id string) error {
|
||||
data, err := json.Marshal(container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(filepath.Join(d.root, id, "container.json"), data, 0655)
|
||||
}
|
||||
|
||||
func (d *driver) createContainerRoot(id string) error {
|
||||
return os.MkdirAll(filepath.Join(d.root, id), 0655)
|
||||
}
|
||||
|
||||
func (d *driver) removeContainerRoot(id string) error {
|
||||
d.Lock()
|
||||
delete(d.activeContainers, id)
|
||||
d.Unlock()
|
||||
|
||||
return os.RemoveAll(filepath.Join(d.root, id))
|
||||
}
|
||||
|
||||
func getEnv(key string, env []string) string {
|
||||
for _, pair := range env {
|
||||
parts := strings.Split(pair, "=")
|
||||
if parts[0] == key {
|
||||
return parts[1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getTerminal(c *execdriver.Command, pipes *execdriver.Pipes) namespaces.Terminal {
|
||||
var term namespaces.Terminal
|
||||
if c.Tty {
|
||||
term = &dockerTtyTerm{
|
||||
pipes: pipes,
|
||||
}
|
||||
} else {
|
||||
term = &dockerStdTerm{
|
||||
pipes: pipes,
|
||||
}
|
||||
}
|
||||
c.Terminal = term
|
||||
return term
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/apparmor"
|
||||
"github.com/docker/libcontainer/cgroups"
|
||||
)
|
||||
|
||||
// New returns the docker default configuration for libcontainer
|
||||
func New() *libcontainer.Config {
|
||||
container := &libcontainer.Config{
|
||||
Capabilities: []string{
|
||||
"CHOWN",
|
||||
"DAC_OVERRIDE",
|
||||
"FOWNER",
|
||||
"MKNOD",
|
||||
"NET_RAW",
|
||||
"SETGID",
|
||||
"SETUID",
|
||||
"SETFCAP",
|
||||
"SETPCAP",
|
||||
"NET_BIND_SERVICE",
|
||||
"SYS_CHROOT",
|
||||
"KILL",
|
||||
},
|
||||
Namespaces: map[string]bool{
|
||||
"NEWNS": true,
|
||||
"NEWUTS": true,
|
||||
"NEWIPC": true,
|
||||
"NEWPID": true,
|
||||
"NEWNET": true,
|
||||
},
|
||||
Cgroups: &cgroups.Cgroup{
|
||||
Parent: "docker",
|
||||
AllowAllDevices: false,
|
||||
},
|
||||
MountConfig: &libcontainer.MountConfig{},
|
||||
}
|
||||
|
||||
if apparmor.IsEnabled() {
|
||||
container.AppArmorProfile = "docker-default"
|
||||
}
|
||||
|
||||
return container
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
Alexander Larsson <alexl@redhat.com> (@alexlarsson)
|
||||
@@ -1,28 +0,0 @@
|
||||
package btrfs
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/daemon/graphdriver/graphtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// This avoids creating a new driver for each test if all tests are run
|
||||
// Make sure to put new tests between TestBtrfsSetup and TestBtrfsTeardown
|
||||
func TestBtrfsSetup(t *testing.T) {
|
||||
graphtest.GetDriver(t, "btrfs")
|
||||
}
|
||||
|
||||
func TestBtrfsCreateEmpty(t *testing.T) {
|
||||
graphtest.DriverTestCreateEmpty(t, "btrfs")
|
||||
}
|
||||
|
||||
func TestBtrfsCreateBase(t *testing.T) {
|
||||
graphtest.DriverTestCreateBase(t, "btrfs")
|
||||
}
|
||||
|
||||
func TestBtrfsCreateSnap(t *testing.T) {
|
||||
graphtest.DriverTestCreateSnap(t, "btrfs")
|
||||
}
|
||||
|
||||
func TestBtrfsTeardown(t *testing.T) {
|
||||
graphtest.PutDriver(t)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
Alexander Larsson <alexl@redhat.com> (@alexlarsson)
|
||||
@@ -1,143 +0,0 @@
|
||||
## devicemapper - a storage backend based on Device Mapper
|
||||
|
||||
### Theory of operation
|
||||
|
||||
The device mapper graphdriver uses the device mapper thin provisioning
|
||||
module (dm-thinp) to implement CoW snapshots. For each devicemapper
|
||||
graph location (typically `/var/lib/docker/devicemapper`, $graph below)
|
||||
a thin pool is created based on two block devices, one for data and
|
||||
one for metadata. By default these block devices are created
|
||||
automatically by using loopback mounts of automatically creates sparse
|
||||
files.
|
||||
|
||||
The default loopback files used are `$graph/devicemapper/data` and
|
||||
`$graph/devicemapper/metadata`. Additional metadata required to map
|
||||
from docker entities to the corresponding devicemapper volumes is
|
||||
stored in the `$graph/devicemapper/json` file (encoded as Json).
|
||||
|
||||
In order to support multiple devicemapper graphs on a system the thin
|
||||
pool will be named something like: `docker-0:33-19478248-pool`, where
|
||||
the `0:30` part is the minor/major device nr and `19478248` is the
|
||||
inode number of the $graph directory.
|
||||
|
||||
On the thin pool docker automatically creates a base thin device,
|
||||
called something like `docker-0:33-19478248-base` of a fixed
|
||||
size. This is automatically formated on creation and contains just an
|
||||
empty filesystem. This device is the base of all docker images and
|
||||
containers. All base images are snapshots of this device and those
|
||||
images are then in turn used as snapshots for other images and
|
||||
eventually containers.
|
||||
|
||||
### options
|
||||
|
||||
The devicemapper backend supports some options that you can specify
|
||||
when starting the docker daemon using the --storage-opt flags.
|
||||
This uses the `dm` prefix and would be used somthing like `docker -d --storage-opt dm.foo=bar`.
|
||||
|
||||
Here is the list of supported options:
|
||||
|
||||
* `dm.basesize`
|
||||
|
||||
Specifies the size to use when creating the base device, which
|
||||
limits the size of images and containers. The default value is
|
||||
10G. Note, thin devices are inherently "sparse", so a 10G device
|
||||
which is mostly empty doesn't use 10 GB of space on the
|
||||
pool. However, the filesystem will use more space for the empty
|
||||
case the larger the device is.
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt dm.basesize=20G``
|
||||
|
||||
* `dm.loopdatasize`
|
||||
|
||||
Specifies the size to use when creating the loopback file for the
|
||||
"data" device which is used for the thin pool. The default size is
|
||||
100G. Note that the file is sparse, so it will not initially take
|
||||
up this much space.
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt dm.loopdatasize=200G``
|
||||
|
||||
* `dm.loopmetadatasize`
|
||||
|
||||
Specifies the size to use when creating the loopback file for the
|
||||
"metadadata" device which is used for the thin pool. The default size is
|
||||
2G. Note that the file is sparse, so it will not initially take
|
||||
up this much space.
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt dm.loopmetadatasize=4G``
|
||||
|
||||
* `dm.fs`
|
||||
|
||||
Specifies the filesystem type to use for the base device. The supported
|
||||
options are "ext4" and "xfs". The default is "ext4"
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt dm.fs=xfs``
|
||||
|
||||
* `dm.mkfsarg`
|
||||
|
||||
Specifies extra mkfs arguments to be used when creating the base device.
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt "dm.mkfsarg=-O ^has_journal"``
|
||||
|
||||
* `dm.mountopt`
|
||||
|
||||
Specifies extra mount options used when mounting the thin devices.
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt dm.mountopt=nodiscard``
|
||||
|
||||
* `dm.datadev`
|
||||
|
||||
Specifies a custom blockdevice to use for data for the thin pool.
|
||||
|
||||
If using a block device for device mapper storage, ideally both
|
||||
datadev and metadatadev should be specified to completely avoid
|
||||
using the loopback device.
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt dm.datadev=/dev/sdb1 --storage-opt dm.metadatadev=/dev/sdc1``
|
||||
|
||||
* `dm.metadatadev`
|
||||
|
||||
Specifies a custom blockdevice to use for metadata for the thin
|
||||
pool.
|
||||
|
||||
For best performance the metadata should be on a different spindle
|
||||
than the data, or even better on an SSD.
|
||||
|
||||
If setting up a new metadata pool it is required to be valid. This
|
||||
can be achieved by zeroing the first 4k to indicate empty
|
||||
metadata, like this:
|
||||
|
||||
``dd if=/dev/zero of=$metadata_dev bs=4096 count=1```
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt dm.datadev=/dev/sdb1 --storage-opt dm.metadatadev=/dev/sdc1``
|
||||
|
||||
* `dm.blkdiscard`
|
||||
|
||||
Enables or disables the use of blkdiscard when removing
|
||||
devicemapper devices. This is enabled by default (only) if using
|
||||
loopback devices and is required to res-parsify the loopback file
|
||||
on image/container removal.
|
||||
|
||||
Disabling this on loopback can lead to *much* faster container
|
||||
removal times, but will make the space used in /var/lib/docker
|
||||
directory not be returned to the system for other use when
|
||||
containers are removed.
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt dm.blkdiscard=false``
|
||||
@@ -1,37 +0,0 @@
|
||||
// +build linux,amd64
|
||||
|
||||
package devmapper
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/daemon/graphdriver/graphtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Reduce the size the the base fs and loopback for the tests
|
||||
DefaultDataLoopbackSize = 300 * 1024 * 1024
|
||||
DefaultMetaDataLoopbackSize = 200 * 1024 * 1024
|
||||
DefaultBaseFsSize = 300 * 1024 * 1024
|
||||
}
|
||||
|
||||
// This avoids creating a new driver for each test if all tests are run
|
||||
// Make sure to put new tests between TestDevmapperSetup and TestDevmapperTeardown
|
||||
func TestDevmapperSetup(t *testing.T) {
|
||||
graphtest.GetDriver(t, "devicemapper")
|
||||
}
|
||||
|
||||
func TestDevmapperCreateEmpty(t *testing.T) {
|
||||
graphtest.DriverTestCreateEmpty(t, "devicemapper")
|
||||
}
|
||||
|
||||
func TestDevmapperCreateBase(t *testing.T) {
|
||||
graphtest.DriverTestCreateBase(t, "devicemapper")
|
||||
}
|
||||
|
||||
func TestDevmapperCreateSnap(t *testing.T) {
|
||||
graphtest.DriverTestCreateSnap(t, "devicemapper")
|
||||
}
|
||||
|
||||
func TestDevmapperTeardown(t *testing.T) {
|
||||
graphtest.PutDriver(t)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user