mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-22 14:48:26 +00:00
Compare commits
314 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0baf609845 | |||
| e45e9e8fbc | |||
| 62fa0ac765 | |||
| 1b6403b9f1 | |||
| f0c8b90524 | |||
| 60ed011bed | |||
| f99e0cea8a | |||
| 984be835db | |||
| 9fc1c4efb9 | |||
| 012bf4129d | |||
| 0bd5953a88 | |||
| 197f2e0693 | |||
| 73e41874b6 | |||
| 64552d0b49 | |||
| 84b21b55b9 | |||
| 7ffbd7eaee | |||
| 469534c855 | |||
| 85d6e7c4ec | |||
| a1f16a3738 | |||
| 2ef2967570 | |||
| ceda1e75a4 | |||
| 2519689e99 | |||
| 009ba67dd0 | |||
| 4743f3b3aa | |||
| 759cdd8ed6 | |||
| ff770d33cd | |||
| 1d3f7cc012 | |||
| 5291de79ae | |||
| 821f9450fc | |||
| 6a04940f86 | |||
| cf13497ca8 | |||
| 2b15a888cd | |||
| 455ad3afe2 | |||
| b5086a7494 | |||
| 80157b35ac | |||
| 4ebcd0d544 | |||
| 3e4284bd7c | |||
| eb4798a10e | |||
| 6c90a239ed | |||
| 8e81f3711f | |||
| d10ed90080 | |||
| 7223584e10 | |||
| c4ba3a8352 | |||
| 33588c23c3 | |||
| 6a5cbd0dd4 | |||
| 495640005a | |||
| 5c408158a6 | |||
| ede1c3f0c2 | |||
| 09295a1453 | |||
| 1f6eca7b99 | |||
| 8f211fde46 | |||
| 5c70b1eadd | |||
| b3adf94d81 | |||
| 8e7ea1a8fd | |||
| c11128520c | |||
| 25fc200dcf | |||
| d17f27a13f | |||
| f2f2c492e1 | |||
| 18af7fdbba | |||
| 339f0a128a | |||
| 5a0fa9545a | |||
| 930f691919 | |||
| bf371ab2a5 | |||
| d283999b1c | |||
| 2786c4d0e2 | |||
| 2939617c8b | |||
| f5e3c68c93 | |||
| c96b03797a | |||
| 0f06c54f40 | |||
| 32fcacdedb | |||
| 140e36a77e | |||
| 4945a51f73 | |||
| c881349e5e | |||
| ecdf1297a3 | |||
| db3daa7bdd | |||
| c0fc839e2b | |||
| 2e29eadb5c | |||
| ecda5c0a6d | |||
| 4872f86d00 | |||
| 6ca78fff63 | |||
| e9c51c3edc | |||
| b38c720f19 | |||
| 3bed793ba1 | |||
| ab7e7a7338 | |||
| 4fefcde5a6 | |||
| 63e3b7433f | |||
| 736c216d58 | |||
| 85f0e01833 | |||
| eb3ed436a4 | |||
| d77d7a0056 | |||
| a0aad0d4de | |||
| baf9ea5ce4 | |||
| a6fe70c696 | |||
| eee959a9b9 | |||
| d000ba05fd | |||
| d8eff999e0 | |||
| fb124bcad0 | |||
| 9827107dcf | |||
| e57d649057 | |||
| d6ff6e2c6f | |||
| cc2944c7af | |||
| 9cee8c4ed0 | |||
| 7c8fca2ddb | |||
| 376188dcd3 | |||
| dc610864aa | |||
| 97cd073598 | |||
| d5ebb60bdd | |||
| 83c5131acd | |||
| b6a9dc399b | |||
| 614a9690e7 | |||
| 545b440a80 | |||
| 3162024e28 | |||
| 769acfec29 | |||
| 47496519da | |||
| fdd21bf032 | |||
| d928dad8c8 | |||
| 82366ce059 | |||
| 6410c3c066 | |||
| 9231dc9cc0 | |||
| 6a3f37386b | |||
| d9a0c05208 | |||
| 24cb9df189 | |||
| c51cd3298c | |||
| 10affa8018 | |||
| ce27fa2716 | |||
| 8d83409e85 | |||
| 3a73b6a2bf | |||
| f99269882f | |||
| 568a9703ac | |||
| faaeb5162d | |||
| b5613baac2 | |||
| c956efcd52 | |||
| 5455864187 | |||
| ceb72fab34 | |||
| c6ea062a26 | |||
| 0e045ab50c | |||
| eeb05fc081 | |||
| e1381ae328 | |||
| 45ad064150 | |||
| 72e14a1566 | |||
| 7d7bec86c9 | |||
| a39d49d676 | |||
| 5bf15a013b | |||
| 9461967eec | |||
| 3be7d11cee | |||
| d9910b8fd8 | |||
| f115c32f6b | |||
| 57939badc3 | |||
| 51ee02d478 | |||
| c92860748c | |||
| f582f9717f | |||
| ebcb36a8d2 | |||
| e6e8f2d717 | |||
| 317a510261 | |||
| 5d3a080178 | |||
| 542c84c2d2 | |||
| f1df74d09d | |||
| 4ddbc7a62f | |||
| f72b2c02b8 | |||
| af9dab70f8 | |||
| 10425e83f2 | |||
| 9c528dca85 | |||
| cb2c25ad2d | |||
| 962dec81ec | |||
| 1eae925a3d | |||
| 3ce2cc8ee7 | |||
| 054acc4bee | |||
| 63cb03a55b | |||
| 49b6f23696 | |||
| 299ae6a2e6 | |||
| 97b521bf10 | |||
| 7f5937d46c | |||
| b6166b9496 | |||
| b596d025f5 | |||
| ca32446950 | |||
| 5328d6d620 | |||
| d0023242ab | |||
| 3ff002aa1a | |||
| ea9b357be2 | |||
| bf1829459f | |||
| 4f744ca781 | |||
| 7dab04383b | |||
| 8a003c8134 | |||
| 208178c799 | |||
| 03b36f3451 | |||
| 7758553239 | |||
| 10fb5ce6d0 | |||
| 0959aec1a9 | |||
| 773f74eb71 | |||
| 7070d9255a | |||
| 2cb4b7f65c | |||
| 2d80652d8a | |||
| 81b4691406 | |||
| 4bae33ef9f | |||
| a8a31eff10 | |||
| 68a8fd5c4e | |||
| 8387c5ab65 | |||
| 69498943c3 | |||
| 1aeb78c2ae | |||
| 331d37f35d | |||
| edf3bf7f33 | |||
| 9ee8dca246 | |||
| aa98bb6c13 | |||
| 2aba3c69f9 | |||
| 71a44c769e | |||
| d8381fad2b | |||
| be379580d0 | |||
| 7ea8513479 | |||
| 3b2fe01c78 | |||
| e8afc22b1f | |||
| 4e407e6b77 | |||
| 23f1c2ea9e | |||
| 788047cafb | |||
| 0c0e7b1b60 | |||
| 09d41529a0 | |||
| cb288fefee | |||
| f7636796c5 | |||
| cb5af83444 | |||
| 96feaf1920 | |||
| 1f03944950 | |||
| 6060eedf9c | |||
| d217da854a | |||
| d74d6d981b | |||
| 0205ac33d2 | |||
| dbb9d47bdc | |||
| ddd1d081d7 | |||
| d6ac36d929 | |||
| 715b94f664 | |||
| 16baca9277 | |||
| 627f8a6cd5 | |||
| a8a7df203a | |||
| 580cbcefd3 | |||
| d9c5ce6e97 | |||
| 0fe9b95415 | |||
| 41d0e4293e | |||
| 26fe640da1 | |||
| 198ca26969 | |||
| d5365f6fc4 | |||
| 5f7e814ee7 | |||
| a84aca0985 | |||
| 68ec22876a | |||
| 0dcc3559e9 | |||
| d4c731ecd6 | |||
| 2dba4e1386 | |||
| 06a7f471e0 | |||
| 4683d01691 | |||
| 6020a06399 | |||
| cc0bfccdf4 | |||
| 0c18ec62f3 | |||
| a9825c9bd8 | |||
| 908be50c44 | |||
| 2a82dba34d | |||
| 13fd2a908c | |||
| 464891aaf8 | |||
| 9974663ed7 | |||
| 76269e5c9d | |||
| 1121d7c4fd | |||
| 7e197575a2 | |||
| 3dc3059d94 | |||
| 7b6de74c9a | |||
| cad8adacb8 | |||
| 6226deeaf4 | |||
| 3ec19f56cf | |||
| 48c71787ed | |||
| 604731a930 | |||
| e8650e01f8 | |||
| 817d04d992 | |||
| cdff91a01c | |||
| 6f26bd0e16 | |||
| 3c090db4e9 | |||
| b7c3fdfd0d | |||
| aa682a845b | |||
| 218d0dcc9d | |||
| 510d8f8634 | |||
| b65600f6b6 | |||
| 79dcea718c | |||
| 072b09c45d | |||
| c2d9837745 | |||
| fa5dfbb18b | |||
| 6532a075f3 | |||
| 3b4a4bf809 | |||
| 4602909566 | |||
| 588f350b61 | |||
| 6e5ff509b2 | |||
| 61d341c2ca | |||
| b996d379a1 | |||
| b0935ea730 | |||
| 96fe13b49b | |||
| 12ccde442a | |||
| 4262cfe41f | |||
| ddc2e25546 | |||
| 6646cff646 | |||
| ac8fd856c0 | |||
| 48754d673c | |||
| 723684525a | |||
| 32aceadbe6 | |||
| c67d3e159c | |||
| a080e2add7 | |||
| 24d81b0ddb | |||
| 08f2fad40b | |||
| f91fbe39ce | |||
| 018ab080bb | |||
| fe94ecb2c1 | |||
| 7b2e67036f | |||
| e130faea1b | |||
| 38f09de334 | |||
| f9ba68ddfb | |||
| 16913455bd | |||
| 32f189cd08 | |||
| 526ca42282 | |||
| b98b42d843 | |||
| 7bf03dd132 | |||
| 034aa3b2c4 | |||
| 6da1e01e6c |
+1
-1
@@ -2,7 +2,6 @@
|
|||||||
# if you want to ignore files created by your editor/tools,
|
# if you want to ignore files created by your editor/tools,
|
||||||
# please consider a global .gitignore https://help.github.com/articles/ignoring-files
|
# please consider a global .gitignore https://help.github.com/articles/ignoring-files
|
||||||
*.exe
|
*.exe
|
||||||
*.exe~
|
|
||||||
*.orig
|
*.orig
|
||||||
*.rej
|
*.rej
|
||||||
*.test
|
*.test
|
||||||
@@ -14,6 +13,7 @@
|
|||||||
.git/
|
.git/
|
||||||
.gopath/
|
.gopath/
|
||||||
.hg/
|
.hg/
|
||||||
|
.idea
|
||||||
.vagrant*
|
.vagrant*
|
||||||
Vagrantfile
|
Vagrantfile
|
||||||
a.out
|
a.out
|
||||||
|
|||||||
@@ -142,30 +142,3 @@ Jessica Frazelle <jess@docker.com> Jessie Frazelle <jfrazelle@users.noreply.gith
|
|||||||
Thomas LEVEIL <thomasleveil@gmail.com> Thomas LÉVEIL <thomasleveil@users.noreply.github.com>
|
Thomas LEVEIL <thomasleveil@gmail.com> Thomas LÉVEIL <thomasleveil@users.noreply.github.com>
|
||||||
<oi@truffles.me.uk> <timruffles@googlemail.com>
|
<oi@truffles.me.uk> <timruffles@googlemail.com>
|
||||||
<Vincent.Bernat@exoscale.ch> <bernat@luffy.cx>
|
<Vincent.Bernat@exoscale.ch> <bernat@luffy.cx>
|
||||||
Antonio Murdaca <antonio.murdaca@gmail.com> <me@runcom.ninja>
|
|
||||||
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@linux.com>
|
|
||||||
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@users.noreply.github.com>
|
|
||||||
Darren Shepherd <darren.s.shepherd@gmail.com> <darren@rancher.com>
|
|
||||||
Deshi Xiao <dxiao@redhat.com> <dsxiao@dataman-inc.com>
|
|
||||||
Deshi Xiao <dxiao@redhat.com> <xiaods@gmail.com>
|
|
||||||
Doug Davis <dug@us.ibm.com> <duglin@users.noreply.github.com>
|
|
||||||
Jacob Atzen <jacob@jacobatzen.dk> <jatzen@gmail.com>
|
|
||||||
Jeff Nickoloff <jeff.nickoloff@gmail.com> <jeff@allingeek.com>
|
|
||||||
<jess@docker.com> <princess@docker.com>
|
|
||||||
John Howard (VM) <John.Howard@microsoft.com> John Howard <jhoward@microsoft.com>
|
|
||||||
Madhu Venugopal <madhu@socketplane.io> <madhu@docker.com>
|
|
||||||
Mary Anthony <mary.anthony@docker.com> <mary@docker.com>
|
|
||||||
Mary Anthony <mary.anthony@docker.com> moxiegirl <mary@docker.com>
|
|
||||||
Mary Anthony <mary.anthony@docker.com> <moxieandmore@gmail.com>
|
|
||||||
mattyw <mattyw@me.com> <gh@mattyw.net>
|
|
||||||
resouer <resouer@163.com> <resouer@gmail.com>
|
|
||||||
AJ Bowen <aj@gandi.net> soulshake <amy@gandi.net>
|
|
||||||
AJ Bowen <aj@gandi.net> soulshake <aj@gandi.net>
|
|
||||||
Tibor Vass <teabee89@gmail.com> <tibor@docker.com>
|
|
||||||
Tibor Vass <teabee89@gmail.com> <tiborvass@users.noreply.github.com>
|
|
||||||
Vincent Bernat <bernat@luffy.cx> <Vincent.Bernat@exoscale.ch>
|
|
||||||
Yestin Sun <sunyi0804@gmail.com> <yestin.sun@polyera.com>
|
|
||||||
bin liu <liubin0329@users.noreply.github.com> <liubin0329@gmail.com>
|
|
||||||
John Howard (VM) <John.Howard@microsoft.com> jhowardmsft <jhoward@microsoft.com>
|
|
||||||
Ankush Agarwal <ankushagarwal11@gmail.com> <ankushagarwal@users.noreply.github.com>
|
|
||||||
Tangi COLIN <tangicolin@gmail.com> tangicolin <tangicolin@gmail.com>
|
|
||||||
|
|||||||
@@ -2,18 +2,14 @@
|
|||||||
# For how it is generated, see `hack/generate-authors.sh`.
|
# For how it is generated, see `hack/generate-authors.sh`.
|
||||||
|
|
||||||
Aanand Prasad <aanand.prasad@gmail.com>
|
Aanand Prasad <aanand.prasad@gmail.com>
|
||||||
Aaron Davidson <aaron@databricks.com>
|
|
||||||
Aaron Feng <aaron.feng@gmail.com>
|
Aaron Feng <aaron.feng@gmail.com>
|
||||||
Aaron Huslage <huslage@gmail.com>
|
Aaron Huslage <huslage@gmail.com>
|
||||||
Aaron Welch <welch@packet.net>
|
|
||||||
Abel Muiño <amuino@gmail.com>
|
Abel Muiño <amuino@gmail.com>
|
||||||
Abhinav Ajgaonkar <abhinav316@gmail.com>
|
Abhinav Ajgaonkar <abhinav316@gmail.com>
|
||||||
Abhishek Chanda <abhishek.becs@gmail.com>
|
|
||||||
Abin Shahab <ashahab@altiscale.com>
|
Abin Shahab <ashahab@altiscale.com>
|
||||||
Adam Miller <admiller@redhat.com>
|
Adam Miller <admiller@redhat.com>
|
||||||
Adam Singer <financeCoding@gmail.com>
|
Adam Singer <financeCoding@gmail.com>
|
||||||
Aditya <aditya@netroy.in>
|
Aditya <aditya@netroy.in>
|
||||||
Adria Casas <adriacasas88@gmail.com>
|
|
||||||
Adrian Mouat <adrian.mouat@gmail.com>
|
Adrian Mouat <adrian.mouat@gmail.com>
|
||||||
Adrien Folie <folie.adrien@gmail.com>
|
Adrien Folie <folie.adrien@gmail.com>
|
||||||
Ahmed Kamal <email.ahmedkamal@googlemail.com>
|
Ahmed Kamal <email.ahmedkamal@googlemail.com>
|
||||||
@@ -27,9 +23,6 @@ Albert Callarisa <shark234@gmail.com>
|
|||||||
Albert Zhang <zhgwenming@gmail.com>
|
Albert Zhang <zhgwenming@gmail.com>
|
||||||
Aleksa Sarai <cyphar@cyphar.com>
|
Aleksa Sarai <cyphar@cyphar.com>
|
||||||
Aleksandrs Fadins <aleks@s-ko.net>
|
Aleksandrs Fadins <aleks@s-ko.net>
|
||||||
Alena Prokharchyk <alena@rancher.com>
|
|
||||||
Alessandro Boch <aboch@docker.com>
|
|
||||||
Alessio Biancalana <dottorblaster@gmail.com>
|
|
||||||
Alex Gaynor <alex.gaynor@gmail.com>
|
Alex Gaynor <alex.gaynor@gmail.com>
|
||||||
Alex Warhawk <ax.warhawk@gmail.com>
|
Alex Warhawk <ax.warhawk@gmail.com>
|
||||||
Alexander Boyd <alex@opengroove.org>
|
Alexander Boyd <alex@opengroove.org>
|
||||||
@@ -37,20 +30,14 @@ Alexander Larsson <alexl@redhat.com>
|
|||||||
Alexander Morozov <lk4d4@docker.com>
|
Alexander Morozov <lk4d4@docker.com>
|
||||||
Alexander Shopov <ash@kambanaria.org>
|
Alexander Shopov <ash@kambanaria.org>
|
||||||
Alexandr Morozov <lk4d4@docker.com>
|
Alexandr Morozov <lk4d4@docker.com>
|
||||||
Alexey Guskov <lexag@mail.ru>
|
|
||||||
Alexey Kotlyarov <alexey@infoxchange.net.au>
|
Alexey Kotlyarov <alexey@infoxchange.net.au>
|
||||||
Alexey Shamrin <shamrin@gmail.com>
|
Alexey Shamrin <shamrin@gmail.com>
|
||||||
Alexis THOMAS <fr.alexisthomas@gmail.com>
|
Alexis THOMAS <fr.alexisthomas@gmail.com>
|
||||||
Allen Madsen <blatyo@gmail.com>
|
|
||||||
almoehi <almoehi@users.noreply.github.com>
|
almoehi <almoehi@users.noreply.github.com>
|
||||||
Alvin Richards <alvin.richards@docker.com>
|
|
||||||
amangoel <amangoel@gmail.com>
|
amangoel <amangoel@gmail.com>
|
||||||
Amit Bakshi <ambakshi@gmail.com>
|
Amit Bakshi <ambakshi@gmail.com>
|
||||||
Amy Lindburg <amy.lindburg@docker.com>
|
|
||||||
Anand Patil <anand.prabhakar.patil@gmail.com>
|
Anand Patil <anand.prabhakar.patil@gmail.com>
|
||||||
AnandkumarPatel <anandkumarpatel@gmail.com>
|
AnandkumarPatel <anandkumarpatel@gmail.com>
|
||||||
Anchal Agrawal <aagrawa4@illinois.edu>
|
|
||||||
Anders Janmyr <anders@janmyr.com>
|
|
||||||
Andre Dublin <81dublin@gmail.com>
|
Andre Dublin <81dublin@gmail.com>
|
||||||
Andrea Luzzardi <aluzzardi@gmail.com>
|
Andrea Luzzardi <aluzzardi@gmail.com>
|
||||||
Andrea Turli <andrea.turli@gmail.com>
|
Andrea Turli <andrea.turli@gmail.com>
|
||||||
@@ -58,19 +45,15 @@ Andreas Köhler <andi5.py@gmx.net>
|
|||||||
Andreas Savvides <andreas@editd.com>
|
Andreas Savvides <andreas@editd.com>
|
||||||
Andreas Tiefenthaler <at@an-ti.eu>
|
Andreas Tiefenthaler <at@an-ti.eu>
|
||||||
Andrew C. Bodine <acbodine@us.ibm.com>
|
Andrew C. Bodine <acbodine@us.ibm.com>
|
||||||
Andrew Clay Shafer <andrewcshafer@gmail.com>
|
|
||||||
Andrew Duckworth <grillopress@gmail.com>
|
Andrew Duckworth <grillopress@gmail.com>
|
||||||
Andrew France <andrew@avito.co.uk>
|
Andrew France <andrew@avito.co.uk>
|
||||||
Andrew Kuklewicz <kookster@gmail.com>
|
|
||||||
Andrew Macgregor <andrew.macgregor@agworld.com.au>
|
Andrew Macgregor <andrew.macgregor@agworld.com.au>
|
||||||
Andrew Martin <sublimino@gmail.com>
|
|
||||||
Andrew Munsell <andrew@wizardapps.net>
|
Andrew Munsell <andrew@wizardapps.net>
|
||||||
Andrew Weiss <andrew.weiss@outlook.com>
|
Andrew Weiss <andrew.weiss@outlook.com>
|
||||||
Andrew Williams <williams.andrew@gmail.com>
|
Andrew Williams <williams.andrew@gmail.com>
|
||||||
Andrews Medina <andrewsmedina@gmail.com>
|
Andrews Medina <andrewsmedina@gmail.com>
|
||||||
Andrey Petrov <andrey.petrov@shazow.net>
|
Andrey Petrov <andrey.petrov@shazow.net>
|
||||||
Andrey Stolbovsky <andrey.stolbovsky@gmail.com>
|
Andrey Stolbovsky <andrey.stolbovsky@gmail.com>
|
||||||
André Martins <martins@noironetworks.com>
|
|
||||||
Andy Chambers <anchambers@paypal.com>
|
Andy Chambers <anchambers@paypal.com>
|
||||||
andy diller <dillera@gmail.com>
|
andy diller <dillera@gmail.com>
|
||||||
Andy Goldstein <agoldste@redhat.com>
|
Andy Goldstein <agoldste@redhat.com>
|
||||||
@@ -78,23 +61,18 @@ Andy Kipp <andy@rstudio.com>
|
|||||||
Andy Rothfusz <github@developersupport.net>
|
Andy Rothfusz <github@developersupport.net>
|
||||||
Andy Smith <github@anarkystic.com>
|
Andy Smith <github@anarkystic.com>
|
||||||
Andy Wilson <wilson.andrew.j+github@gmail.com>
|
Andy Wilson <wilson.andrew.j+github@gmail.com>
|
||||||
Anes Hasicic <anes.hasicic@gmail.com>
|
|
||||||
Ankush Agarwal <ankushagarwal11@gmail.com>
|
Ankush Agarwal <ankushagarwal11@gmail.com>
|
||||||
Anthony Baire <Anthony.Baire@irisa.fr>
|
Anthony Baire <Anthony.Baire@irisa.fr>
|
||||||
Anthony Bishopric <git@anthonybishopric.com>
|
Anthony Bishopric <git@anthonybishopric.com>
|
||||||
Anton Löfgren <anton.lofgren@gmail.com>
|
Anton Löfgren <anton.lofgren@gmail.com>
|
||||||
Anton Nikitin <anton.k.nikitin@gmail.com>
|
Anton Nikitin <anton.k.nikitin@gmail.com>
|
||||||
Anton Tiurin <noxiouz@yandex.ru>
|
|
||||||
Antonio Murdaca <antonio.murdaca@gmail.com>
|
|
||||||
Antony Messerli <amesserl@rackspace.com>
|
Antony Messerli <amesserl@rackspace.com>
|
||||||
apocas <petermdias@gmail.com>
|
apocas <petermdias@gmail.com>
|
||||||
ArikaChen <eaglesora@gmail.com>
|
ArikaChen <eaglesora@gmail.com>
|
||||||
Arnaud Porterie <arnaud.porterie@docker.com>
|
Arnaud Porterie <arnaud.porterie@docker.com>
|
||||||
Arthur Barr <arthur.barr@uk.ibm.com>
|
|
||||||
Arthur Gautier <baloo@gandi.net>
|
Arthur Gautier <baloo@gandi.net>
|
||||||
Asbjørn Enge <asbjorn@hanafjedle.net>
|
Asbjørn Enge <asbjorn@hanafjedle.net>
|
||||||
averagehuman <averagehuman@users.noreply.github.com>
|
averagehuman <averagehuman@users.noreply.github.com>
|
||||||
Avi Das <andas222@gmail.com>
|
|
||||||
Avi Miller <avi.miller@oracle.com>
|
Avi Miller <avi.miller@oracle.com>
|
||||||
Barnaby Gray <barnaby@pickle.me.uk>
|
Barnaby Gray <barnaby@pickle.me.uk>
|
||||||
Barry Allard <barry.allard@gmail.com>
|
Barry Allard <barry.allard@gmail.com>
|
||||||
@@ -102,31 +80,22 @@ Bartłomiej Piotrowski <b@bpiotrowski.pl>
|
|||||||
bdevloed <boris.de.vloed@gmail.com>
|
bdevloed <boris.de.vloed@gmail.com>
|
||||||
Ben Firshman <ben@firshman.co.uk>
|
Ben Firshman <ben@firshman.co.uk>
|
||||||
Ben Sargent <ben@brokendigits.com>
|
Ben Sargent <ben@brokendigits.com>
|
||||||
Ben Severson <BenSeverson@users.noreply.github.com>
|
|
||||||
Ben Toews <mastahyeti@gmail.com>
|
Ben Toews <mastahyeti@gmail.com>
|
||||||
Ben Wiklund <ben@daisyowl.com>
|
Ben Wiklund <ben@daisyowl.com>
|
||||||
Benjamin Atkin <ben@benatkin.com>
|
Benjamin Atkin <ben@benatkin.com>
|
||||||
Benoit Chesneau <bchesneau@gmail.com>
|
Benoit Chesneau <bchesneau@gmail.com>
|
||||||
Bernerd Schaefer <bj.schaefer@gmail.com>
|
Bernerd Schaefer <bj.schaefer@gmail.com>
|
||||||
Bert Goethals <bert@bertg.be>
|
Bert Goethals <bert@bertg.be>
|
||||||
Bharath Thiruveedula <bharath_ves@hotmail.com>
|
|
||||||
Bhiraj Butala <abhiraj.butala@gmail.com>
|
Bhiraj Butala <abhiraj.butala@gmail.com>
|
||||||
bin liu <liubin0329@users.noreply.github.com>
|
bin liu <liubin0329@users.noreply.github.com>
|
||||||
Blake Geno <blakegeno@gmail.com>
|
Blake Geno <blakegeno@gmail.com>
|
||||||
bobby abbott <ttobbaybbob@gmail.com>
|
|
||||||
boucher <rboucher@gmail.com>
|
|
||||||
Bouke Haarsma <bouke@webatoom.nl>
|
Bouke Haarsma <bouke@webatoom.nl>
|
||||||
Boyd Hemphill <boyd@feedmagnet.com>
|
Boyd Hemphill <boyd@feedmagnet.com>
|
||||||
Bradley Cicenas <bradley.cicenas@gmail.com>
|
|
||||||
Bradley Wright <brad@intranation.com>
|
|
||||||
Brandon Liu <bdon@bdon.org>
|
Brandon Liu <bdon@bdon.org>
|
||||||
Brandon Philips <brandon@ifup.org>
|
Brandon Philips <brandon@ifup.org>
|
||||||
Brandon Rhodes <brandon@rhodesmill.org>
|
Brandon Rhodes <brandon@rhodesmill.org>
|
||||||
Brendan Dixon <brendand@microsoft.com>
|
|
||||||
Brent Salisbury <brent.salisbury@docker.com>
|
|
||||||
Brett Kochendorfer <brett.kochendorfer@gmail.com>
|
Brett Kochendorfer <brett.kochendorfer@gmail.com>
|
||||||
Brian (bex) Exelbierd <bexelbie@redhat.com>
|
Brian (bex) Exelbierd <bexelbie@redhat.com>
|
||||||
Brian DeHamer <brian@dehamer.com>
|
|
||||||
Brian Dorsey <brian@dorseys.org>
|
Brian Dorsey <brian@dorseys.org>
|
||||||
Brian Flad <bflad417@gmail.com>
|
Brian Flad <bflad417@gmail.com>
|
||||||
Brian Goff <cpuguy83@gmail.com>
|
Brian Goff <cpuguy83@gmail.com>
|
||||||
@@ -137,67 +106,48 @@ Brice Jaglin <bjaglin@teads.tv>
|
|||||||
Briehan Lombaard <briehan.lombaard@gmail.com>
|
Briehan Lombaard <briehan.lombaard@gmail.com>
|
||||||
Bruno Bigras <bigras.bruno@gmail.com>
|
Bruno Bigras <bigras.bruno@gmail.com>
|
||||||
Bruno Binet <bruno.binet@gmail.com>
|
Bruno Binet <bruno.binet@gmail.com>
|
||||||
Bruno Gazzera <bgazzera@paginar.com>
|
|
||||||
Bruno Renié <brutasse@gmail.com>
|
Bruno Renié <brutasse@gmail.com>
|
||||||
Bryan Bess <squarejaw@bsbess.com>
|
Bryan Bess <squarejaw@bsbess.com>
|
||||||
Bryan Boreham <bjboreham@gmail.com>
|
|
||||||
Bryan Matsuo <bryan.matsuo@gmail.com>
|
Bryan Matsuo <bryan.matsuo@gmail.com>
|
||||||
Bryan Murphy <bmurphy1976@gmail.com>
|
Bryan Murphy <bmurphy1976@gmail.com>
|
||||||
buddhamagnet <buddhamagnet@gmail.com>
|
|
||||||
Burke Libbey <burke@libbey.me>
|
Burke Libbey <burke@libbey.me>
|
||||||
Byung Kang <byung.kang.ctr@amrdec.army.mil>
|
Byung Kang <byung.kang.ctr@amrdec.army.mil>
|
||||||
Caleb Spare <cespare@gmail.com>
|
Caleb Spare <cespare@gmail.com>
|
||||||
Calen Pennington <cale@edx.org>
|
Calen Pennington <cale@edx.org>
|
||||||
Cameron Boehmer <cameron.boehmer@gmail.com>
|
Cameron Boehmer <cameron.boehmer@gmail.com>
|
||||||
Carl X. Su <bcbcarl@gmail.com>
|
Carl X. Su <bcbcarl@gmail.com>
|
||||||
Cary <caryhartline@users.noreply.github.com>
|
|
||||||
Casey Bisson <casey.bisson@joyent.com>
|
|
||||||
Charles Hooper <charles.hooper@dotcloud.com>
|
Charles Hooper <charles.hooper@dotcloud.com>
|
||||||
Charles Lindsay <chaz@chazomatic.us>
|
Charles Lindsay <chaz@chazomatic.us>
|
||||||
Charles Merriam <charles.merriam@gmail.com>
|
Charles Merriam <charles.merriam@gmail.com>
|
||||||
Charlie Lewis <charliel@lab41.org>
|
Charlie Lewis <charliel@lab41.org>
|
||||||
Chen Chao <cc272309126@gmail.com>
|
Chen Chao <cc272309126@gmail.com>
|
||||||
Chen Hanxiao <chenhanxiao@cn.fujitsu.com>
|
|
||||||
cheney90 <cheney-90@hotmail.com>
|
|
||||||
Chewey <prosto-chewey@users.noreply.github.com>
|
Chewey <prosto-chewey@users.noreply.github.com>
|
||||||
Chia-liang Kao <clkao@clkao.org>
|
Chia-liang Kao <clkao@clkao.org>
|
||||||
chli <chli@freewheel.tv>
|
|
||||||
Chris Alfonso <calfonso@redhat.com>
|
Chris Alfonso <calfonso@redhat.com>
|
||||||
Chris Armstrong <chris@opdemand.com>
|
Chris Armstrong <chris@opdemand.com>
|
||||||
Chris Khoo <chris.khoo@gmail.com>
|
|
||||||
Chris Snow <chsnow123@gmail.com>
|
Chris Snow <chsnow123@gmail.com>
|
||||||
Chris St. Pierre <chris.a.st.pierre@gmail.com>
|
Chris St. Pierre <chris.a.st.pierre@gmail.com>
|
||||||
Chris Stivers <chris@stivers.us>
|
|
||||||
Chris Wahl <github@wahlnetwork.com>
|
|
||||||
chrismckinnel <chris.mckinnel@tangentlabs.co.uk>
|
chrismckinnel <chris.mckinnel@tangentlabs.co.uk>
|
||||||
Christian Berendt <berendt@b1-systems.de>
|
Christian Berendt <berendt@b1-systems.de>
|
||||||
Christian Simon <simon@swine.de>
|
|
||||||
Christian Stefanescu <st.chris@gmail.com>
|
Christian Stefanescu <st.chris@gmail.com>
|
||||||
ChristoperBiscardi <biscarch@sketcht.com>
|
ChristoperBiscardi <biscarch@sketcht.com>
|
||||||
Christophe Troestler <christophe.Troestler@umons.ac.be>
|
Christophe Troestler <christophe.Troestler@umons.ac.be>
|
||||||
Christopher Currie <codemonkey+github@gmail.com>
|
Christopher Currie <codemonkey+github@gmail.com>
|
||||||
Christopher Latham <sudosurootdev@gmail.com>
|
Christopher Latham <sudosurootdev@gmail.com>
|
||||||
Christopher Rigor <crigor@gmail.com>
|
Christopher Rigor <crigor@gmail.com>
|
||||||
Christy Perez <christy@linux.vnet.ibm.com>
|
|
||||||
Chun Chen <chenchun.feed@gmail.com>
|
Chun Chen <chenchun.feed@gmail.com>
|
||||||
Ciro S. Costa <ciro.costa@usp.br>
|
Ciro S. Costa <ciro.costa@usp.br>
|
||||||
Clayton Coleman <ccoleman@redhat.com>
|
Clayton Coleman <ccoleman@redhat.com>
|
||||||
Coenraad Loubser <coenraad@wish.org.za>
|
|
||||||
Colin Dunklau <colin.dunklau@gmail.com>
|
Colin Dunklau <colin.dunklau@gmail.com>
|
||||||
Colin Rice <colin@daedrum.net>
|
Colin Rice <colin@daedrum.net>
|
||||||
Colin Walters <walters@verbum.org>
|
Colin Walters <walters@verbum.org>
|
||||||
Colm Hally <colmhally@gmail.com>
|
|
||||||
Cory Forsyth <cory.forsyth@gmail.com>
|
Cory Forsyth <cory.forsyth@gmail.com>
|
||||||
cressie176 <github@stephen-cresswell.net>
|
cressie176 <github@stephen-cresswell.net>
|
||||||
Cristian Staretu <cristian.staretu@gmail.com>
|
Cristian Staretu <cristian.staretu@gmail.com>
|
||||||
Cruceru Calin-Cristian <crucerucalincristian@gmail.com>
|
Cruceru Calin-Cristian <crucerucalincristian@gmail.com>
|
||||||
Cyril F <cyrilf7x@gmail.com>
|
|
||||||
Daan van Berkel <daan.v.berkel.1980@gmail.com>
|
Daan van Berkel <daan.v.berkel.1980@gmail.com>
|
||||||
Daehyeok Mun <daehyeok@gmail.com>
|
Daehyeok Mun <daehyeok@gmail.com>
|
||||||
Dafydd Crosby <dtcrsby@gmail.com>
|
Dafydd Crosby <dtcrsby@gmail.com>
|
||||||
dalanlan <dalanlan925@gmail.com>
|
|
||||||
Damjan Georgievski <gdamjan@gmail.com>
|
|
||||||
Dan Anolik <dan@anolik.net>
|
|
||||||
Dan Buch <d.buch@modcloth.com>
|
Dan Buch <d.buch@modcloth.com>
|
||||||
Dan Cotora <dan@bluevision.ro>
|
Dan Cotora <dan@bluevision.ro>
|
||||||
Dan Griffin <dgriffin@peer1.com>
|
Dan Griffin <dgriffin@peer1.com>
|
||||||
@@ -207,46 +157,35 @@ Dan McPherson <dmcphers@redhat.com>
|
|||||||
Dan Stine <sw@stinemail.com>
|
Dan Stine <sw@stinemail.com>
|
||||||
Dan Walsh <dwalsh@redhat.com>
|
Dan Walsh <dwalsh@redhat.com>
|
||||||
Dan Williams <me@deedubs.com>
|
Dan Williams <me@deedubs.com>
|
||||||
Daniel Antlinger <d.antlinger@gmx.at>
|
|
||||||
Daniel Exner <dex@dragonslave.de>
|
Daniel Exner <dex@dragonslave.de>
|
||||||
Daniel Farrell <dfarrell@redhat.com>
|
Daniel Farrell <dfarrell@redhat.com>
|
||||||
Daniel Garcia <daniel@danielgarcia.info>
|
Daniel Garcia <daniel@danielgarcia.info>
|
||||||
Daniel Gasienica <daniel@gasienica.ch>
|
Daniel Gasienica <daniel@gasienica.ch>
|
||||||
Daniel Menet <membership@sontags.ch>
|
Daniel Menet <membership@sontags.ch>
|
||||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
|
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
|
||||||
Daniel Nephin <dnephin@gmail.com>
|
|
||||||
Daniel Norberg <dano@spotify.com>
|
Daniel Norberg <dano@spotify.com>
|
||||||
Daniel Nordberg <dnordberg@gmail.com>
|
Daniel Nordberg <dnordberg@gmail.com>
|
||||||
Daniel Robinson <gottagetmac@gmail.com>
|
Daniel Robinson <gottagetmac@gmail.com>
|
||||||
Daniel S <dan.streby@gmail.com>
|
|
||||||
Daniel Von Fange <daniel@leancoder.com>
|
Daniel Von Fange <daniel@leancoder.com>
|
||||||
Daniel YC Lin <dlin.tw@gmail.com>
|
Daniel YC Lin <dlin.tw@gmail.com>
|
||||||
Daniel Zhang <jmzwcn@gmail.com>
|
|
||||||
Daniel, Dao Quang Minh <dqminh89@gmail.com>
|
Daniel, Dao Quang Minh <dqminh89@gmail.com>
|
||||||
Danny Berger <dpb587@gmail.com>
|
Danny Berger <dpb587@gmail.com>
|
||||||
Danny Yates <danny@codeaholics.org>
|
Danny Yates <danny@codeaholics.org>
|
||||||
Darren Coxall <darren@darrencoxall.com>
|
Darren Coxall <darren@darrencoxall.com>
|
||||||
Darren Shepherd <darren.s.shepherd@gmail.com>
|
Darren Shepherd <darren.s.shepherd@gmail.com>
|
||||||
Dave Henderson <Dave.Henderson@ca.ibm.com>
|
|
||||||
David Anderson <dave@natulte.net>
|
David Anderson <dave@natulte.net>
|
||||||
David Calavera <david.calavera@gmail.com>
|
David Calavera <david.calavera@gmail.com>
|
||||||
David Corking <dmc-source@dcorking.com>
|
David Corking <dmc-source@dcorking.com>
|
||||||
David Davis <daviddavis@redhat.com>
|
|
||||||
David Gageot <david@gageot.net>
|
David Gageot <david@gageot.net>
|
||||||
David Gebler <davidgebler@gmail.com>
|
David Gebler <davidgebler@gmail.com>
|
||||||
David Mackey <tdmackey@booleanhaiku.com>
|
|
||||||
David Mat <david@davidmat.com>
|
David Mat <david@davidmat.com>
|
||||||
David Mcanulty <github@hellspark.com>
|
David Mcanulty <github@hellspark.com>
|
||||||
David Pelaez <pelaez89@gmail.com>
|
David Pelaez <pelaez89@gmail.com>
|
||||||
David R. Jenni <david.r.jenni@gmail.com>
|
|
||||||
David Röthlisberger <david@rothlis.net>
|
David Röthlisberger <david@rothlis.net>
|
||||||
David Sissitka <me@dsissitka.com>
|
David Sissitka <me@dsissitka.com>
|
||||||
David Xia <dxia@spotify.com>
|
|
||||||
David Young <yangboh@cn.ibm.com>
|
|
||||||
Davide Ceretti <davide.ceretti@hogarthww.com>
|
Davide Ceretti <davide.ceretti@hogarthww.com>
|
||||||
Dawn Chen <dawnchen@google.com>
|
Dawn Chen <dawnchen@google.com>
|
||||||
decadent <decadent@users.noreply.github.com>
|
decadent <decadent@users.noreply.github.com>
|
||||||
Deng Guangxing <dengguangxing@huawei.com>
|
|
||||||
Deni Bertovic <deni@kset.org>
|
Deni Bertovic <deni@kset.org>
|
||||||
Derek <crq@kernel.org>
|
Derek <crq@kernel.org>
|
||||||
Derek <crquan@gmail.com>
|
Derek <crquan@gmail.com>
|
||||||
@@ -254,53 +193,39 @@ Derek McGowan <derek@mcgstyle.net>
|
|||||||
Deric Crago <deric.crago@gmail.com>
|
Deric Crago <deric.crago@gmail.com>
|
||||||
Deshi Xiao <dxiao@redhat.com>
|
Deshi Xiao <dxiao@redhat.com>
|
||||||
Dinesh Subhraveti <dineshs@altiscale.com>
|
Dinesh Subhraveti <dineshs@altiscale.com>
|
||||||
DiuDiugirl <sophia.wang@pku.edu.cn>
|
|
||||||
Djibril Koné <kone.djibril@gmail.com>
|
Djibril Koné <kone.djibril@gmail.com>
|
||||||
dkumor <daniel@dkumor.com>
|
dkumor <daniel@dkumor.com>
|
||||||
Dmitry Demeshchuk <demeshchuk@gmail.com>
|
Dmitry Demeshchuk <demeshchuk@gmail.com>
|
||||||
Dmitry Gusev <dmitry.gusev@gmail.com>
|
|
||||||
Dmitry V. Krivenok <krivenok.dmitry@gmail.com>
|
Dmitry V. Krivenok <krivenok.dmitry@gmail.com>
|
||||||
Dolph Mathews <dolph.mathews@gmail.com>
|
Dolph Mathews <dolph.mathews@gmail.com>
|
||||||
Dominik Finkbeiner <finkes93@gmail.com>
|
|
||||||
Dominik Honnef <dominik@honnef.co>
|
Dominik Honnef <dominik@honnef.co>
|
||||||
Don Kirkby <donkirkby@users.noreply.github.com>
|
|
||||||
Don Kjer <don.kjer@gmail.com>
|
Don Kjer <don.kjer@gmail.com>
|
||||||
Don Spaulding <donspauldingii@gmail.com>
|
Don Spaulding <donspauldingii@gmail.com>
|
||||||
Doug Davis <dug@us.ibm.com>
|
Doug Davis <dug@us.ibm.com>
|
||||||
Doug MacEachern <dougm@vmware.com>
|
|
||||||
doug tangren <d.tangren@gmail.com>
|
doug tangren <d.tangren@gmail.com>
|
||||||
Dr Nic Williams <drnicwilliams@gmail.com>
|
Dr Nic Williams <drnicwilliams@gmail.com>
|
||||||
dragon788 <dragon788@users.noreply.github.com>
|
dragon788 <dragon788@users.noreply.github.com>
|
||||||
Dražen Lučanin <kermit666@gmail.com>
|
Dražen Lučanin <kermit666@gmail.com>
|
||||||
Dustin Sallings <dustin@spy.net>
|
Dustin Sallings <dustin@spy.net>
|
||||||
Ed Costello <epc@epcostello.com>
|
|
||||||
Edmund Wagner <edmund-wagner@web.de>
|
Edmund Wagner <edmund-wagner@web.de>
|
||||||
Eiichi Tsukata <devel@etsukata.com>
|
Eiichi Tsukata <devel@etsukata.com>
|
||||||
Eike Herzbach <eike@herzbach.net>
|
Eike Herzbach <eike@herzbach.net>
|
||||||
Eivind Uggedal <eivind@uggedal.com>
|
Eivind Uggedal <eivind@uggedal.com>
|
||||||
Elias Probst <mail@eliasprobst.eu>
|
Elias Probst <mail@eliasprobst.eu>
|
||||||
Elijah Zupancic <elijah@zupancic.name>
|
|
||||||
eluck <mail@eluck.me>
|
|
||||||
Emil Hernvall <emil@quench.at>
|
Emil Hernvall <emil@quench.at>
|
||||||
Emily Maier <emily@emilymaier.net>
|
Emily Maier <emily@emilymaier.net>
|
||||||
Emily Rose <emily@contactvibe.com>
|
Emily Rose <emily@contactvibe.com>
|
||||||
Emir Ozer <emirozer@yandex.com>
|
|
||||||
Enguerran <engcolson@gmail.com>
|
|
||||||
Eohyung Lee <liquidnuker@gmail.com>
|
|
||||||
Eric Hanchrow <ehanchrow@ine.com>
|
Eric Hanchrow <ehanchrow@ine.com>
|
||||||
Eric Lee <thenorthsecedes@gmail.com>
|
Eric Lee <thenorthsecedes@gmail.com>
|
||||||
Eric Myhre <hash@exultant.us>
|
Eric Myhre <hash@exultant.us>
|
||||||
Eric Paris <eparis@redhat.com>
|
Eric Paris <eparis@redhat.com>
|
||||||
Eric Rafaloff <erafaloff@gmail.com>
|
|
||||||
Eric Windisch <ewindisch@docker.com>
|
Eric Windisch <ewindisch@docker.com>
|
||||||
Eric-Olivier Lamey <eo@lamey.me>
|
|
||||||
Erik Dubbelboer <erik@dubbelboer.com>
|
Erik Dubbelboer <erik@dubbelboer.com>
|
||||||
Erik Hollensbe <github@hollensbe.org>
|
Erik Hollensbe <github@hollensbe.org>
|
||||||
Erik Inge Bolsø <knan@redpill-linpro.com>
|
Erik Inge Bolsø <knan@redpill-linpro.com>
|
||||||
Erik Kristensen <erik@erikkristensen.com>
|
Erik Kristensen <erik@erikkristensen.com>
|
||||||
Erno Hopearuoho <erno.hopearuoho@gmail.com>
|
Erno Hopearuoho <erno.hopearuoho@gmail.com>
|
||||||
Erwin van der Koogh <info@erronis.nl>
|
Erwin van der Koogh <info@erronis.nl>
|
||||||
Euan <euank@amazon.com>
|
|
||||||
Eugene Yakubovich <eugene.yakubovich@coreos.com>
|
Eugene Yakubovich <eugene.yakubovich@coreos.com>
|
||||||
eugenkrizo <eugen.krizo@gmail.com>
|
eugenkrizo <eugen.krizo@gmail.com>
|
||||||
Evan Carmi <carmi@users.noreply.github.com>
|
Evan Carmi <carmi@users.noreply.github.com>
|
||||||
@@ -308,78 +233,58 @@ Evan Hazlett <ejhazlett@gmail.com>
|
|||||||
Evan Krall <krall@yelp.com>
|
Evan Krall <krall@yelp.com>
|
||||||
Evan Phoenix <evan@fallingsnow.net>
|
Evan Phoenix <evan@fallingsnow.net>
|
||||||
Evan Wies <evan@neomantra.net>
|
Evan Wies <evan@neomantra.net>
|
||||||
Evgeny Vereshchagin <evvers@ya.ru>
|
|
||||||
Eystein Måløy Stenberg <eystein.maloy.stenberg@cfengine.com>
|
Eystein Måløy Stenberg <eystein.maloy.stenberg@cfengine.com>
|
||||||
ezbercih <cem.ezberci@gmail.com>
|
ezbercih <cem.ezberci@gmail.com>
|
||||||
Fabiano Rosas <farosas@br.ibm.com>
|
|
||||||
Fabio Falci <fabiofalci@gmail.com>
|
Fabio Falci <fabiofalci@gmail.com>
|
||||||
Fabio Rehm <fgrehm@gmail.com>
|
Fabio Rehm <fgrehm@gmail.com>
|
||||||
Fabrizio Regini <freegenie@gmail.com>
|
Fabrizio Regini <freegenie@gmail.com>
|
||||||
Faiz Khan <faizkhan00@gmail.com>
|
Faiz Khan <faizkhan00@gmail.com>
|
||||||
falmp <chico.lopes@gmail.com>
|
|
||||||
Fareed Dudhia <fareeddudhia@googlemail.com>
|
Fareed Dudhia <fareeddudhia@googlemail.com>
|
||||||
Felix Rabe <felix@rabe.io>
|
Felix Rabe <felix@rabe.io>
|
||||||
Felix Schindler <fschindler@weluse.de>
|
|
||||||
Ferenc Szabo <pragmaticfrank@gmail.com>
|
|
||||||
Fernando <fermayo@gmail.com>
|
Fernando <fermayo@gmail.com>
|
||||||
Filipe Brandenburger <filbranden@google.com>
|
Filipe Brandenburger <filbranden@google.com>
|
||||||
Flavio Castelli <fcastelli@suse.com>
|
Flavio Castelli <fcastelli@suse.com>
|
||||||
FLGMwt <ryan.stelly@live.com>
|
FLGMwt <ryan.stelly@live.com>
|
||||||
Florian Weingarten <flo@hackvalue.de>
|
|
||||||
Francisco Carriedo <fcarriedo@gmail.com>
|
Francisco Carriedo <fcarriedo@gmail.com>
|
||||||
Francisco Souza <f@souza.cc>
|
Francisco Souza <f@souza.cc>
|
||||||
Frank Herrmann <fgh@4gh.tv>
|
|
||||||
Frank Macreery <frank@macreery.com>
|
Frank Macreery <frank@macreery.com>
|
||||||
Frank Rosquin <frank.rosquin+github@gmail.com>
|
Frank Rosquin <frank.rosquin+github@gmail.com>
|
||||||
Fred Lifton <fred.lifton@docker.com>
|
Fred Lifton <fred.lifton@docker.com>
|
||||||
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
|
||||||
Frederik Loeffert <frederik@zitrusmedia.de>
|
Frederik Loeffert <frederik@zitrusmedia.de>
|
||||||
Freek Kalter <freek@kalteronline.org>
|
Freek Kalter <freek@kalteronline.org>
|
||||||
Félix Baylac-Jacqué <baylac.felix@gmail.com>
|
|
||||||
Gabe Rosenhouse <gabe@missionst.com>
|
Gabe Rosenhouse <gabe@missionst.com>
|
||||||
Gabor Nagy <mail@aigeruth.hu>
|
Gabor Nagy <mail@aigeruth.hu>
|
||||||
Gabriel Monroy <gabriel@opdemand.com>
|
Gabriel Monroy <gabriel@opdemand.com>
|
||||||
Galen Sampson <galen.sampson@gmail.com>
|
Galen Sampson <galen.sampson@gmail.com>
|
||||||
Gareth Rushgrove <gareth@morethanseven.net>
|
Gareth Rushgrove <gareth@morethanseven.net>
|
||||||
Gaurav <gaurav.gosec@gmail.com>
|
|
||||||
gautam, prasanna <prasannagautam@gmail.com>
|
gautam, prasanna <prasannagautam@gmail.com>
|
||||||
GennadySpb <lipenkov@gmail.com>
|
|
||||||
Geoffrey Bachelet <grosfrais@gmail.com>
|
Geoffrey Bachelet <grosfrais@gmail.com>
|
||||||
George MacRorie <gmacr31@gmail.com>
|
|
||||||
George Xie <georgexsh@gmail.com>
|
George Xie <georgexsh@gmail.com>
|
||||||
Gereon Frey <gereon.frey@dynport.de>
|
Gereon Frey <gereon.frey@dynport.de>
|
||||||
German DZ <germ@ndz.com.ar>
|
German DZ <germ@ndz.com.ar>
|
||||||
Gert van Valkenhoef <g.h.m.van.valkenhoef@rug.nl>
|
Gert van Valkenhoef <g.h.m.van.valkenhoef@rug.nl>
|
||||||
Gianluca Borello <g.borello@gmail.com>
|
|
||||||
Giuseppe Mazzotta <gdm85@users.noreply.github.com>
|
Giuseppe Mazzotta <gdm85@users.noreply.github.com>
|
||||||
Gleb Fotengauer-Malinovskiy <glebfm@altlinux.org>
|
Gleb Fotengauer-Malinovskiy <glebfm@altlinux.org>
|
||||||
Gleb M Borisov <borisov.gleb@gmail.com>
|
Gleb M Borisov <borisov.gleb@gmail.com>
|
||||||
Glyn Normington <gnormington@gopivotal.com>
|
Glyn Normington <gnormington@gopivotal.com>
|
||||||
Goffert van Gool <goffert@phusion.nl>
|
Goffert van Gool <goffert@phusion.nl>
|
||||||
golubbe <ben.golub@dotcloud.com>
|
golubbe <ben.golub@dotcloud.com>
|
||||||
Gosuke Miyashita <gosukenator@gmail.com>
|
|
||||||
Graydon Hoare <graydon@pobox.com>
|
Graydon Hoare <graydon@pobox.com>
|
||||||
Greg Fausak <greg@tacodata.com>
|
|
||||||
Greg Thornton <xdissent@me.com>
|
Greg Thornton <xdissent@me.com>
|
||||||
grossws <grossws@gmail.com>
|
|
||||||
grunny <mwgrunny@gmail.com>
|
grunny <mwgrunny@gmail.com>
|
||||||
Guilherme Salgado <gsalgado@gmail.com>
|
Guilherme Salgado <gsalgado@gmail.com>
|
||||||
Guillaume Dufour <gdufour.prestataire@voyages-sncf.com>
|
Guillaume Dufour <gdufour.prestataire@voyages-sncf.com>
|
||||||
Guillaume J. Charmes <guillaume.charmes@docker.com>
|
Guillaume J. Charmes <guillaume.charmes@docker.com>
|
||||||
guoxiuyan <guoxiuyan@huawei.com>
|
|
||||||
Gurjeet Singh <gurjeet@singh.im>
|
Gurjeet Singh <gurjeet@singh.im>
|
||||||
Guruprasad <lgp171188@gmail.com>
|
Guruprasad <lgp171188@gmail.com>
|
||||||
Günter Zöchbauer <guenter@gzoechbauer.com>
|
|
||||||
Hans Rødtang <hansrodtang@gmail.com>
|
Hans Rødtang <hansrodtang@gmail.com>
|
||||||
Harald Albers <github@albersweb.de>
|
Harald Albers <github@albersweb.de>
|
||||||
Harley Laue <losinggeneration@gmail.com>
|
Harley Laue <losinggeneration@gmail.com>
|
||||||
Harry Zhang <harryzhang@zju.edu.cn>
|
|
||||||
He Simei <hesimei@zju.edu.cn>
|
|
||||||
Hector Castro <hectcastro@gmail.com>
|
Hector Castro <hectcastro@gmail.com>
|
||||||
Henning Sprang <henning.sprang@gmail.com>
|
Henning Sprang <henning.sprang@gmail.com>
|
||||||
Hobofan <goisser94@gmail.com>
|
Hobofan <goisser94@gmail.com>
|
||||||
Hollie Teal <hollie@docker.com>
|
Hollie Teal <hollie@docker.com>
|
||||||
Hong Xu <hong@topbug.net>
|
|
||||||
Hu Keping <hukeping@huawei.com>
|
Hu Keping <hukeping@huawei.com>
|
||||||
Hu Tao <hutao@cn.fujitsu.com>
|
Hu Tao <hutao@cn.fujitsu.com>
|
||||||
Huayi Zhang <irachex@gmail.com>
|
Huayi Zhang <irachex@gmail.com>
|
||||||
@@ -387,28 +292,21 @@ Hugo Duncan <hugo@hugoduncan.org>
|
|||||||
Hunter Blanks <hunter@twilio.com>
|
Hunter Blanks <hunter@twilio.com>
|
||||||
Huu Nguyen <huu@prismskylabs.com>
|
Huu Nguyen <huu@prismskylabs.com>
|
||||||
hyeongkyu.lee <hyeongkyu.lee@navercorp.com>
|
hyeongkyu.lee <hyeongkyu.lee@navercorp.com>
|
||||||
hyp3rdino <markus.kortlang@lhsystems.com>
|
|
||||||
Ian Babrou <ibobrik@gmail.com>
|
Ian Babrou <ibobrik@gmail.com>
|
||||||
Ian Bishop <ianbishop@pace7.com>
|
Ian Bishop <ianbishop@pace7.com>
|
||||||
Ian Bull <irbull@gmail.com>
|
Ian Bull <irbull@gmail.com>
|
||||||
Ian Calvert <ianjcalvert@gmail.com>
|
|
||||||
Ian Main <imain@redhat.com>
|
Ian Main <imain@redhat.com>
|
||||||
Ian Truslove <ian.truslove@gmail.com>
|
Ian Truslove <ian.truslove@gmail.com>
|
||||||
Iavael <iavaelooeyt@gmail.com>
|
|
||||||
Igor Dolzhikov <bluesriverz@gmail.com>
|
Igor Dolzhikov <bluesriverz@gmail.com>
|
||||||
ILYA Khlopotov <ilya.khlopotov@gmail.com>
|
ILYA Khlopotov <ilya.khlopotov@gmail.com>
|
||||||
imre Fitos <imre.fitos+github@gmail.com>
|
|
||||||
inglesp <peter.inglesby@gmail.com>
|
inglesp <peter.inglesby@gmail.com>
|
||||||
Isaac Dupree <antispam@idupree.com>
|
Isaac Dupree <antispam@idupree.com>
|
||||||
Isabel Jimenez <contact.isabeljimenez@gmail.com>
|
Isabel Jimenez <contact.isabeljimenez@gmail.com>
|
||||||
Isao Jonas <isao.jonas@gmail.com>
|
Isao Jonas <isao.jonas@gmail.com>
|
||||||
Ivan Fraixedes <ifcdev@gmail.com>
|
Ivan Fraixedes <ifcdev@gmail.com>
|
||||||
J Bruni <joaohbruni@yahoo.com.br>
|
|
||||||
J. Nunn <jbnunn@gmail.com>
|
|
||||||
Jack Danger Canty <jackdanger@squareup.com>
|
Jack Danger Canty <jackdanger@squareup.com>
|
||||||
Jacob Atzen <jacob@jacobatzen.dk>
|
Jacob Atzen <jacob@jacobatzen.dk>
|
||||||
Jacob Edelman <edelman.jd@gmail.com>
|
Jacob Edelman <edelman.jd@gmail.com>
|
||||||
Jake Champlin <jake.champlin.27@gmail.com>
|
|
||||||
Jake Moshenko <jake@devtable.com>
|
Jake Moshenko <jake@devtable.com>
|
||||||
jakedt <jake@devtable.com>
|
jakedt <jake@devtable.com>
|
||||||
James Allen <jamesallen0108@gmail.com>
|
James Allen <jamesallen0108@gmail.com>
|
||||||
@@ -416,67 +314,41 @@ James Carr <james.r.carr@gmail.com>
|
|||||||
James DeFelice <james.defelice@ishisystems.com>
|
James DeFelice <james.defelice@ishisystems.com>
|
||||||
James Harrison Fisher <jameshfisher@gmail.com>
|
James Harrison Fisher <jameshfisher@gmail.com>
|
||||||
James Kyle <james@jameskyle.org>
|
James Kyle <james@jameskyle.org>
|
||||||
James Lal <james@lightsofapollo.com>
|
|
||||||
James Mills <prologic@shortcircuit.net.au>
|
James Mills <prologic@shortcircuit.net.au>
|
||||||
James Turnbull <james@lovedthanlost.net>
|
James Turnbull <james@lovedthanlost.net>
|
||||||
Jamie Hannaford <jamie.hannaford@rackspace.com>
|
|
||||||
Jamshid Afshar <jafshar@yahoo.com>
|
|
||||||
Jan Keromnes <janx@linux.com>
|
Jan Keromnes <janx@linux.com>
|
||||||
Jan Koprowski <jan.koprowski@gmail.com>
|
|
||||||
Jan Pazdziora <jpazdziora@redhat.com>
|
Jan Pazdziora <jpazdziora@redhat.com>
|
||||||
Jan Toebes <jan@toebes.info>
|
Jan Toebes <jan@toebes.info>
|
||||||
Jan-Jaap Driessen <janjaapdriessen@gmail.com>
|
|
||||||
Jana Radhakrishnan <mrjana@docker.com>
|
|
||||||
Jared Biel <jared.biel@bolderthinking.com>
|
|
||||||
Jaroslaw Zabiello <hipertracker@gmail.com>
|
Jaroslaw Zabiello <hipertracker@gmail.com>
|
||||||
jaseg <jaseg@jaseg.net>
|
jaseg <jaseg@jaseg.net>
|
||||||
Jason Divock <jdivock@gmail.com>
|
|
||||||
Jason Giedymin <jasong@apache.org>
|
Jason Giedymin <jasong@apache.org>
|
||||||
Jason Hall <imjasonh@gmail.com>
|
Jason Hall <imjasonh@gmail.com>
|
||||||
Jason Livesay <ithkuil@gmail.com>
|
Jason Livesay <ithkuil@gmail.com>
|
||||||
Jason McVetta <jason.mcvetta@gmail.com>
|
Jason McVetta <jason.mcvetta@gmail.com>
|
||||||
Jason Plum <jplum@devonit.com>
|
Jason Plum <jplum@devonit.com>
|
||||||
Jason Shepherd <jason@jasonshepherd.net>
|
|
||||||
Jason Smith <jasonrichardsmith@gmail.com>
|
|
||||||
Jason Sommer <jsdirv@gmail.com>
|
|
||||||
Jason Stangroome <jason@codeassassin.com>
|
|
||||||
Jay <teguhwpurwanto@gmail.com>
|
|
||||||
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
||||||
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||||
Jean-Paul Calderone <exarkun@twistedmatrix.com>
|
Jean-Paul Calderone <exarkun@twistedmatrix.com>
|
||||||
Jean-Tiare Le Bigot <jt@yadutaf.fr>
|
Jean-Tiare Le Bigot <jt@yadutaf.fr>
|
||||||
Jeff Anderson <jeff@docker.com>
|
Jeff Anderson <jeff@docker.com>
|
||||||
Jeff Lindsay <progrium@gmail.com>
|
Jeff Lindsay <progrium@gmail.com>
|
||||||
Jeff Nickoloff <jeff.nickoloff@gmail.com>
|
|
||||||
Jeff Welch <whatthejeff@gmail.com>
|
Jeff Welch <whatthejeff@gmail.com>
|
||||||
Jeffrey Bolle <jeffreybolle@gmail.com>
|
Jeffrey Bolle <jeffreybolle@gmail.com>
|
||||||
Jeffrey Morgan <jmorganca@gmail.com>
|
|
||||||
Jeffrey van Gogh <jvg@google.com>
|
|
||||||
Jeremy Grosser <jeremy@synack.me>
|
Jeremy Grosser <jeremy@synack.me>
|
||||||
Jesse Dearing <jesse.dearing@gmail.com>
|
|
||||||
Jesse Dubay <jesse@thefortytwo.net>
|
Jesse Dubay <jesse@thefortytwo.net>
|
||||||
Jessica Frazelle <jess@docker.com>
|
Jessica Frazelle <jess@docker.com>
|
||||||
Jezeniel Zapanta <jpzapanta22@gmail.com>
|
Jezeniel Zapanta <jpzapanta22@gmail.com>
|
||||||
jianbosun <wonderflow.sun@gmail.com>
|
|
||||||
Jilles Oldenbeuving <ojilles@gmail.com>
|
Jilles Oldenbeuving <ojilles@gmail.com>
|
||||||
Jim Alateras <jima@comware.com.au>
|
Jim Alateras <jima@comware.com.au>
|
||||||
Jim Perrin <jperrin@centos.org>
|
Jim Perrin <jperrin@centos.org>
|
||||||
Jimmy Cuadra <jimmy@jimmycuadra.com>
|
Jimmy Cuadra <jimmy@jimmycuadra.com>
|
||||||
Jimmy Puckett <jimmy.puckett@spinen.com>
|
|
||||||
jimmyxian <jimmyxian2004@yahoo.com.cn>
|
|
||||||
Jinsoo Park <cellpjs@gmail.com>
|
|
||||||
Jiri Popelka <jpopelka@redhat.com>
|
|
||||||
Jiří Župka <jzupka@redhat.com>
|
Jiří Župka <jzupka@redhat.com>
|
||||||
jjy <jiangjinyang@outlook.com>
|
|
||||||
jmzwcn <jmzwcn@gmail.com>
|
|
||||||
Joe Beda <joe.github@bedafamily.com>
|
Joe Beda <joe.github@bedafamily.com>
|
||||||
Joe Ferguson <joe@infosiftr.com>
|
Joe Ferguson <joe@infosiftr.com>
|
||||||
Joe Gordon <joe.gordon0@gmail.com>
|
|
||||||
Joe Shaw <joe@joeshaw.org>
|
Joe Shaw <joe@joeshaw.org>
|
||||||
Joe Van Dyk <joe@tanga.com>
|
Joe Van Dyk <joe@tanga.com>
|
||||||
Joel Friedly <joelfriedly@gmail.com>
|
Joel Friedly <joelfriedly@gmail.com>
|
||||||
Joel Handwell <joelhandwell@gmail.com>
|
Joel Handwell <joelhandwell@gmail.com>
|
||||||
Joey Gibson <joey@joeygibson.com>
|
|
||||||
Joffrey F <joffrey@docker.com>
|
Joffrey F <joffrey@docker.com>
|
||||||
Johan Euphrosine <proppy@google.com>
|
Johan Euphrosine <proppy@google.com>
|
||||||
Johan Rydberg <johan.rydberg@gmail.com>
|
Johan Rydberg <johan.rydberg@gmail.com>
|
||||||
@@ -485,17 +357,13 @@ John Costa <john.costa@gmail.com>
|
|||||||
John Feminella <jxf@jxf.me>
|
John Feminella <jxf@jxf.me>
|
||||||
John Gardiner Myers <jgmyers@proofpoint.com>
|
John Gardiner Myers <jgmyers@proofpoint.com>
|
||||||
John Gossman <johngos@microsoft.com>
|
John Gossman <johngos@microsoft.com>
|
||||||
John Howard (VM) <John.Howard@microsoft.com>
|
|
||||||
John OBrien III <jobrieniii@yahoo.com>
|
John OBrien III <jobrieniii@yahoo.com>
|
||||||
John Tims <john.k.tims@gmail.com>
|
|
||||||
John Warwick <jwarwick@gmail.com>
|
John Warwick <jwarwick@gmail.com>
|
||||||
John Willis <john.willis@docker.com>
|
|
||||||
Jon Wedaman <jweede@gmail.com>
|
Jon Wedaman <jweede@gmail.com>
|
||||||
Jonas Pfenniger <jonas@pfenniger.name>
|
Jonas Pfenniger <jonas@pfenniger.name>
|
||||||
Jonathan A. Sternberg <jonathansternberg@gmail.com>
|
Jonathan A. Sternberg <jonathansternberg@gmail.com>
|
||||||
Jonathan Boulle <jonathanboulle@gmail.com>
|
Jonathan Boulle <jonathanboulle@gmail.com>
|
||||||
Jonathan Camp <jonathan@irondojo.com>
|
Jonathan Camp <jonathan@irondojo.com>
|
||||||
Jonathan Dowland <jon+github@alcopop.org>
|
|
||||||
Jonathan McCrohan <jmccrohan@gmail.com>
|
Jonathan McCrohan <jmccrohan@gmail.com>
|
||||||
Jonathan Mueller <j.mueller@apoveda.ch>
|
Jonathan Mueller <j.mueller@apoveda.ch>
|
||||||
Jonathan Pares <jonathanpa@users.noreply.github.com>
|
Jonathan Pares <jonathanpa@users.noreply.github.com>
|
||||||
@@ -505,18 +373,15 @@ Jordan Arentsen <blissdev@gmail.com>
|
|||||||
Jordan Sissel <jls@semicomplete.com>
|
Jordan Sissel <jls@semicomplete.com>
|
||||||
Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
|
Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
|
||||||
Joseph Hager <ajhager@gmail.com>
|
Joseph Hager <ajhager@gmail.com>
|
||||||
Joseph Kern <jkern@semafour.net>
|
|
||||||
Josh <jokajak@gmail.com>
|
Josh <jokajak@gmail.com>
|
||||||
Josh Hawn <josh.hawn@docker.com>
|
Josh Hawn <josh.hawn@docker.com>
|
||||||
Josh Poimboeuf <jpoimboe@redhat.com>
|
Josh Poimboeuf <jpoimboe@redhat.com>
|
||||||
Josiah Kiehl <jkiehl@riotgames.com>
|
Josiah Kiehl <jkiehl@riotgames.com>
|
||||||
José Tomás Albornoz <jojo@eljojo.net>
|
|
||||||
JP <jpellerin@leapfrogonline.com>
|
JP <jpellerin@leapfrogonline.com>
|
||||||
Julian Taylor <jtaylor.debian@googlemail.com>
|
Julian Taylor <jtaylor.debian@googlemail.com>
|
||||||
Julien Barbier <write0@gmail.com>
|
Julien Barbier <write0@gmail.com>
|
||||||
Julien Bordellier <julienbordellier@gmail.com>
|
Julien Bordellier <julienbordellier@gmail.com>
|
||||||
Julien Dubois <julien.dubois@gmail.com>
|
Julien Dubois <julien.dubois@gmail.com>
|
||||||
Jun-Ru Chang <jrjang@gmail.com>
|
|
||||||
Justin Force <justin.force@gmail.com>
|
Justin Force <justin.force@gmail.com>
|
||||||
Justin Plock <jplock@users.noreply.github.com>
|
Justin Plock <jplock@users.noreply.github.com>
|
||||||
Justin Simonelis <justin.p.simonelis@gmail.com>
|
Justin Simonelis <justin.p.simonelis@gmail.com>
|
||||||
@@ -525,28 +390,22 @@ Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
|
|||||||
Jörg Thalheim <joerg@higgsboson.tk>
|
Jörg Thalheim <joerg@higgsboson.tk>
|
||||||
Kamil Domanski <kamil@domanski.co>
|
Kamil Domanski <kamil@domanski.co>
|
||||||
Karan Lyons <karan@karanlyons.com>
|
Karan Lyons <karan@karanlyons.com>
|
||||||
kargakis <kargakis@users.noreply.github.com>
|
|
||||||
Karl Grzeszczak <karlgrz@gmail.com>
|
Karl Grzeszczak <karlgrz@gmail.com>
|
||||||
Katie McLaughlin <katie@glasnt.com>
|
|
||||||
Kato Kazuyoshi <kato.kazuyoshi@gmail.com>
|
Kato Kazuyoshi <kato.kazuyoshi@gmail.com>
|
||||||
Katrina Owen <katrina.owen@gmail.com>
|
|
||||||
Kawsar Saiyeed <kawsar.saiyeed@projiris.com>
|
Kawsar Saiyeed <kawsar.saiyeed@projiris.com>
|
||||||
Keli Hu <dev@keli.hu>
|
Keli Hu <dev@keli.hu>
|
||||||
Ken Cochrane <kencochrane@gmail.com>
|
Ken Cochrane <kencochrane@gmail.com>
|
||||||
Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com>
|
Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com>
|
||||||
Kent Johnson <kentoj@gmail.com>
|
|
||||||
Kevin "qwazerty" Houdebert <kevin.houdebert@gmail.com>
|
Kevin "qwazerty" Houdebert <kevin.houdebert@gmail.com>
|
||||||
Kevin Clark <kevin.clark@gmail.com>
|
Kevin Clark <kevin.clark@gmail.com>
|
||||||
Kevin J. Lynagh <kevin@keminglabs.com>
|
Kevin J. Lynagh <kevin@keminglabs.com>
|
||||||
Kevin Menard <kevin@nirvdrum.com>
|
Kevin Menard <kevin@nirvdrum.com>
|
||||||
Kevin Wallace <kevin@pentabarf.net>
|
Kevin Wallace <kevin@pentabarf.net>
|
||||||
Kevin Yap <me@kevinyap.ca>
|
|
||||||
Keyvan Fatehi <keyvanfatehi@gmail.com>
|
Keyvan Fatehi <keyvanfatehi@gmail.com>
|
||||||
kies <lleelm@gmail.com>
|
kies <lleelm@gmail.com>
|
||||||
Kim BKC Carlbacker <kim.carlbacker@gmail.com>
|
Kim BKC Carlbacker <kim.carlbacker@gmail.com>
|
||||||
Kimbro Staken <kstaken@kstaken.com>
|
Kimbro Staken <kstaken@kstaken.com>
|
||||||
Kiran Gangadharan <kiran.daredevil@gmail.com>
|
Kiran Gangadharan <kiran.daredevil@gmail.com>
|
||||||
Kirill SIbirev <l0kix2@gmail.com>
|
|
||||||
knappe <tyler.knappe@gmail.com>
|
knappe <tyler.knappe@gmail.com>
|
||||||
Kohei Tsuruta <coheyxyz@gmail.com>
|
Kohei Tsuruta <coheyxyz@gmail.com>
|
||||||
Konrad Kleine <konrad.wilhelm.kleine@gmail.com>
|
Konrad Kleine <konrad.wilhelm.kleine@gmail.com>
|
||||||
@@ -560,8 +419,6 @@ Lajos Papp <lajos.papp@sequenceiq.com>
|
|||||||
Lakshan Perera <lakshan@laktek.com>
|
Lakshan Perera <lakshan@laktek.com>
|
||||||
lalyos <lalyos@yahoo.com>
|
lalyos <lalyos@yahoo.com>
|
||||||
Lance Chen <cyen0312@gmail.com>
|
Lance Chen <cyen0312@gmail.com>
|
||||||
Lance Kinley <lkinley@loyaltymethods.com>
|
|
||||||
Lars Kellogg-Stedman <lars@redhat.com>
|
|
||||||
Lars R. Damerow <lars@pixar.com>
|
Lars R. Damerow <lars@pixar.com>
|
||||||
Laurie Voss <github@seldo.com>
|
Laurie Voss <github@seldo.com>
|
||||||
leeplay <hyeongkyu.lee@navercorp.com>
|
leeplay <hyeongkyu.lee@navercorp.com>
|
||||||
@@ -571,26 +428,17 @@ Leszek Kowalski <github@leszekkowalski.pl>
|
|||||||
Levi Gross <levi@levigross.com>
|
Levi Gross <levi@levigross.com>
|
||||||
Lewis Marshall <lewis@lmars.net>
|
Lewis Marshall <lewis@lmars.net>
|
||||||
Lewis Peckover <lew+github@lew.io>
|
Lewis Peckover <lew+github@lew.io>
|
||||||
Liana Lo <liana.lixia@gmail.com>
|
|
||||||
Liang-Chi Hsieh <viirya@gmail.com>
|
Liang-Chi Hsieh <viirya@gmail.com>
|
||||||
limsy <seongyeol37@gmail.com>
|
limsy <seongyeol37@gmail.com>
|
||||||
Liu Hua <sdu.liu@huawei.com>
|
|
||||||
Lloyd Dewolf <foolswisdom@gmail.com>
|
|
||||||
Lokesh Mandvekar <lsm5@fedoraproject.org>
|
Lokesh Mandvekar <lsm5@fedoraproject.org>
|
||||||
Lorenz Leutgeb <lorenz.leutgeb@gmail.com>
|
Lorenz Leutgeb <lorenz.leutgeb@gmail.com>
|
||||||
Lorenzo Fontana <fontanalorenzo@me.com>
|
|
||||||
Louis Opter <kalessin@kalessin.fr>
|
Louis Opter <kalessin@kalessin.fr>
|
||||||
Luis Martínez de Bartolomé Izquierdo <lmartinez@biicode.com>
|
|
||||||
lukaspustina <lukas.pustina@centerdevice.com>
|
lukaspustina <lukas.pustina@centerdevice.com>
|
||||||
lukemarsden <luke@digital-crocus.com>
|
lukemarsden <luke@digital-crocus.com>
|
||||||
Lénaïc Huard <lhuard@amadeus.com>
|
Lénaïc Huard <lhuard@amadeus.com>
|
||||||
Ma Shimiao <mashimiao.fnst@cn.fujitsu.com>
|
|
||||||
Mabin <bin.ma@huawei.com>
|
|
||||||
Madhu Venugopal <madhu@socketplane.io>
|
Madhu Venugopal <madhu@socketplane.io>
|
||||||
Mahesh Tiyyagura <tmahesh@gmail.com>
|
Mahesh Tiyyagura <tmahesh@gmail.com>
|
||||||
malnick <malnick@gmail..com>
|
|
||||||
Malte Janduda <mail@janduda.net>
|
Malte Janduda <mail@janduda.net>
|
||||||
Manfred Touron <m@42.am>
|
|
||||||
Manfred Zabarauskas <manfredas@zabarauskas.com>
|
Manfred Zabarauskas <manfredas@zabarauskas.com>
|
||||||
Manuel Meurer <manuel@krautcomputing.com>
|
Manuel Meurer <manuel@krautcomputing.com>
|
||||||
Manuel Woelker <github@manuel.woelker.org>
|
Manuel Woelker <github@manuel.woelker.org>
|
||||||
@@ -602,52 +450,38 @@ Marcus Farkas <toothlessgear@finitebox.com>
|
|||||||
Marcus Linke <marcus.linke@gmx.de>
|
Marcus Linke <marcus.linke@gmx.de>
|
||||||
Marcus Ramberg <marcus@nordaaker.com>
|
Marcus Ramberg <marcus@nordaaker.com>
|
||||||
Marek Goldmann <marek.goldmann@gmail.com>
|
Marek Goldmann <marek.goldmann@gmail.com>
|
||||||
Marian Marinov <mm@yuhu.biz>
|
|
||||||
Marianna <mtesselh@gmail.com>
|
Marianna <mtesselh@gmail.com>
|
||||||
Marius Voila <marius.voila@gmail.com>
|
Marius Voila <marius.voila@gmail.com>
|
||||||
Mark Allen <mrallen1@yahoo.com>
|
Mark Allen <mrallen1@yahoo.com>
|
||||||
Mark McGranaghan <mmcgrana@gmail.com>
|
Mark McGranaghan <mmcgrana@gmail.com>
|
||||||
Mark West <markewest@gmail.com>
|
|
||||||
Marko Mikulicic <mmikulicic@gmail.com>
|
Marko Mikulicic <mmikulicic@gmail.com>
|
||||||
Marko Tibold <marko@tibold.nl>
|
Marko Tibold <marko@tibold.nl>
|
||||||
Markus Fix <lispmeister@gmail.com>
|
Markus Fix <lispmeister@gmail.com>
|
||||||
Martijn Dwars <ikben@martijndwars.nl>
|
|
||||||
Martijn van Oosterhout <kleptog@svana.org>
|
Martijn van Oosterhout <kleptog@svana.org>
|
||||||
Martin Honermeyer <maze@strahlungsfrei.de>
|
Martin Honermeyer <maze@strahlungsfrei.de>
|
||||||
Martin Redmond <martin@tinychat.com>
|
Martin Redmond <martin@tinychat.com>
|
||||||
Mary Anthony <mary.anthony@docker.com>
|
Mary Anthony <moxieandmore@gmail.com>
|
||||||
Masahito Zembutsu <zembutsu@users.noreply.github.com>
|
|
||||||
Mason Malone <mason.malone@gmail.com>
|
Mason Malone <mason.malone@gmail.com>
|
||||||
Mateusz Sulima <sulima.mateusz@gmail.com>
|
Mateusz Sulima <sulima.mateusz@gmail.com>
|
||||||
Mathias Monnerville <mathias@monnerville.com>
|
Mathias Monnerville <mathias@monnerville.com>
|
||||||
Mathieu Le Marec - Pasquet <kiorky@cryptelium.net>
|
Mathieu Le Marec - Pasquet <kiorky@cryptelium.net>
|
||||||
Matt Apperson <me@mattapperson.com>
|
Matt Apperson <me@mattapperson.com>
|
||||||
Matt Bachmann <bachmann.matt@gmail.com>
|
Matt Bachmann <bachmann.matt@gmail.com>
|
||||||
Matt Bentley <mbentley@mbentley.net>
|
|
||||||
Matt Haggard <haggardii@gmail.com>
|
Matt Haggard <haggardii@gmail.com>
|
||||||
Matt McCormick <matt.mccormick@kitware.com>
|
|
||||||
Matthew Heon <mheon@redhat.com>
|
Matthew Heon <mheon@redhat.com>
|
||||||
Matthew Mayer <matthewkmayer@gmail.com>
|
|
||||||
Matthew Mueller <mattmuelle@gmail.com>
|
Matthew Mueller <mattmuelle@gmail.com>
|
||||||
Matthew Riley <mattdr@google.com>
|
Matthew Riley <mattdr@google.com>
|
||||||
Matthias Klumpp <matthias@tenstral.net>
|
Matthias Klumpp <matthias@tenstral.net>
|
||||||
Matthias Kühnle <git.nivoc@neverbox.com>
|
Matthias Kühnle <git.nivoc@neverbox.com>
|
||||||
mattymo <raytrac3r@gmail.com>
|
mattymo <raytrac3r@gmail.com>
|
||||||
mattyw <mattyw@me.com>
|
mattyw <mattyw@me.com>
|
||||||
mauriyouth <mauriyouth@gmail.com>
|
|
||||||
Max Shytikov <mshytikov@gmail.com>
|
Max Shytikov <mshytikov@gmail.com>
|
||||||
Maxim Kulkin <mkulkin@mirantis.com>
|
|
||||||
Maxim Treskin <zerthurd@gmail.com>
|
Maxim Treskin <zerthurd@gmail.com>
|
||||||
Maxime Petazzoni <max@signalfuse.com>
|
Maxime Petazzoni <max@signalfuse.com>
|
||||||
Meaglith Ma <genedna@gmail.com>
|
|
||||||
meejah <meejah@meejah.ca>
|
meejah <meejah@meejah.ca>
|
||||||
Megan Kostick <mkostick@us.ibm.com>
|
|
||||||
Mehul Kar <mehul.kar@gmail.com>
|
|
||||||
Mengdi Gao <usrgdd@gmail.com>
|
Mengdi Gao <usrgdd@gmail.com>
|
||||||
Mert Yazıcıoğlu <merty@users.noreply.github.com>
|
Mert Yazıcıoğlu <merty@users.noreply.github.com>
|
||||||
Michael A. Smith <michael@smith-li.com>
|
|
||||||
Michael Brown <michael@netdirect.ca>
|
Michael Brown <michael@netdirect.ca>
|
||||||
Michael Chiang <mchiang@docker.com>
|
|
||||||
Michael Crosby <michael@docker.com>
|
Michael Crosby <michael@docker.com>
|
||||||
Michael Gorsuch <gorsuch@github.com>
|
Michael Gorsuch <gorsuch@github.com>
|
||||||
Michael Hudson-Doyle <michael.hudson@linaro.org>
|
Michael Hudson-Doyle <michael.hudson@linaro.org>
|
||||||
@@ -657,35 +491,26 @@ Michael Scharf <github@scharf.gr>
|
|||||||
Michael Stapelberg <michael+gh@stapelberg.de>
|
Michael Stapelberg <michael+gh@stapelberg.de>
|
||||||
Michael Steinert <mike.steinert@gmail.com>
|
Michael Steinert <mike.steinert@gmail.com>
|
||||||
Michael Thies <michaelthies78@gmail.com>
|
Michael Thies <michaelthies78@gmail.com>
|
||||||
Michael West <mwest@mdsol.com>
|
|
||||||
Michal Fojtik <mfojtik@redhat.com>
|
|
||||||
Michal Jemala <michal.jemala@gmail.com>
|
Michal Jemala <michal.jemala@gmail.com>
|
||||||
Michal Minar <miminar@redhat.com>
|
Michal Minar <miminar@redhat.com>
|
||||||
Michaël Pailloncy <mpapo.dev@gmail.com>
|
Michaël Pailloncy <mpapo.dev@gmail.com>
|
||||||
Michiel@unhosted <michiel@unhosted.org>
|
Michiel@unhosted <michiel@unhosted.org>
|
||||||
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
||||||
Mihai Borobocea <MihaiBorobocea@gmail.com>
|
|
||||||
Mike Chelen <michael.chelen@gmail.com>
|
Mike Chelen <michael.chelen@gmail.com>
|
||||||
Mike Dillon <mike@embody.org>
|
|
||||||
Mike Gaffney <mike@uberu.com>
|
Mike Gaffney <mike@uberu.com>
|
||||||
Mike Leone <mleone896@gmail.com>
|
|
||||||
Mike MacCana <mike.maccana@gmail.com>
|
Mike MacCana <mike.maccana@gmail.com>
|
||||||
Mike Naberezny <mike@naberezny.com>
|
Mike Naberezny <mike@naberezny.com>
|
||||||
Mike Snitzer <snitzer@redhat.com>
|
Mike Snitzer <snitzer@redhat.com>
|
||||||
Mikhail Sobolev <mss@mawhrin.net>
|
Mikhail Sobolev <mss@mawhrin.net>
|
||||||
Mingzhen Feng <fmzhen@zju.edu.cn>
|
|
||||||
Mitch Capper <mitch.capper@gmail.com>
|
|
||||||
Mohit Soni <mosoni@ebay.com>
|
Mohit Soni <mosoni@ebay.com>
|
||||||
Morgante Pell <morgante.pell@morgante.net>
|
Morgante Pell <morgante.pell@morgante.net>
|
||||||
Morten Siebuhr <sbhr@sbhr.dk>
|
Morten Siebuhr <sbhr@sbhr.dk>
|
||||||
Moysés Borges <moyses.furtado@wplex.com.br>
|
|
||||||
Mrunal Patel <mrunalp@gmail.com>
|
Mrunal Patel <mrunalp@gmail.com>
|
||||||
mschurenko <matt.schurenko@gmail.com>
|
mschurenko <matt.schurenko@gmail.com>
|
||||||
Mustafa Akın <mustafa91@gmail.com>
|
Mustafa Akın <mustafa91@gmail.com>
|
||||||
Médi-Rémi Hashim <medimatrix@users.noreply.github.com>
|
Médi-Rémi Hashim <medimatrix@users.noreply.github.com>
|
||||||
Nan Monnand Deng <monnand@gmail.com>
|
Nan Monnand Deng <monnand@gmail.com>
|
||||||
Naoki Orii <norii@cs.cmu.edu>
|
Naoki Orii <norii@cs.cmu.edu>
|
||||||
Natalie Parker <nparker@omnifone.com>
|
|
||||||
Nate Eagleson <nate@nateeag.com>
|
Nate Eagleson <nate@nateeag.com>
|
||||||
Nate Jones <nate@endot.org>
|
Nate Jones <nate@endot.org>
|
||||||
Nathan Hsieh <hsieh.nathan@gmail.com>
|
Nathan Hsieh <hsieh.nathan@gmail.com>
|
||||||
@@ -693,11 +518,8 @@ Nathan Kleyn <nathan@nathankleyn.com>
|
|||||||
Nathan LeClaire <nathan.leclaire@docker.com>
|
Nathan LeClaire <nathan.leclaire@docker.com>
|
||||||
Neal McBurnett <neal@mcburnett.org>
|
Neal McBurnett <neal@mcburnett.org>
|
||||||
Nelson Chen <crazysim@gmail.com>
|
Nelson Chen <crazysim@gmail.com>
|
||||||
Nghia Tran <nghia@google.com>
|
|
||||||
Niall O'Higgins <niallo@unworkable.org>
|
Niall O'Higgins <niallo@unworkable.org>
|
||||||
Nicholas E. Rabenau <nerab@gmx.at>
|
Nicholas E. Rabenau <nerab@gmx.at>
|
||||||
Nick Irvine <nfirvine@nfirvine.com>
|
|
||||||
Nick Parker <nikaios@gmail.com>
|
|
||||||
Nick Payne <nick@kurai.co.uk>
|
Nick Payne <nick@kurai.co.uk>
|
||||||
Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
|
Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
|
||||||
Nick Stinemates <nick@stinemates.org>
|
Nick Stinemates <nick@stinemates.org>
|
||||||
@@ -706,11 +528,8 @@ Nicolas Dudebout <nicolas.dudebout@gatech.edu>
|
|||||||
Nicolas Goy <kuon@goyman.com>
|
Nicolas Goy <kuon@goyman.com>
|
||||||
Nicolas Kaiser <nikai@nikai.net>
|
Nicolas Kaiser <nikai@nikai.net>
|
||||||
NikolaMandic <mn080202@gmail.com>
|
NikolaMandic <mn080202@gmail.com>
|
||||||
nikolas <nnyby@columbia.edu>
|
|
||||||
noducks <onemannoducks@gmail.com>
|
noducks <onemannoducks@gmail.com>
|
||||||
Nolan Darilek <nolan@thewordnerd.info>
|
Nolan Darilek <nolan@thewordnerd.info>
|
||||||
nponeccop <andy.melnikov@gmail.com>
|
|
||||||
Nuutti Kotivuori <naked@iki.fi>
|
|
||||||
nzwsch <hi@nzwsch.com>
|
nzwsch <hi@nzwsch.com>
|
||||||
O.S. Tezer <ostezer@gmail.com>
|
O.S. Tezer <ostezer@gmail.com>
|
||||||
OddBloke <daniel@daniel-watkins.co.uk>
|
OddBloke <daniel@daniel-watkins.co.uk>
|
||||||
@@ -723,14 +542,11 @@ pandrew <letters@paulnotcom.se>
|
|||||||
panticz <mail@konczalski.de>
|
panticz <mail@konczalski.de>
|
||||||
Pascal Borreli <pascal@borreli.com>
|
Pascal Borreli <pascal@borreli.com>
|
||||||
Pascal Hartig <phartig@rdrei.net>
|
Pascal Hartig <phartig@rdrei.net>
|
||||||
Patrick Devine <patrick.devine@docker.com>
|
|
||||||
Patrick Hemmer <patrick.hemmer@gmail.com>
|
Patrick Hemmer <patrick.hemmer@gmail.com>
|
||||||
Patrick Stapleton <github@gdi2290.com>
|
Patrick Stapleton <github@gdi2290.com>
|
||||||
pattichen <craftsbear@gmail.com>
|
pattichen <craftsbear@gmail.com>
|
||||||
Paul <paul9869@gmail.com>
|
Paul <paul9869@gmail.com>
|
||||||
paul <paul@inkling.com>
|
|
||||||
Paul Annesley <paul@annesley.cc>
|
Paul Annesley <paul@annesley.cc>
|
||||||
Paul Bellamy <paul.a.bellamy@gmail.com>
|
|
||||||
Paul Bowsher <pbowsher@globalpersonals.co.uk>
|
Paul Bowsher <pbowsher@globalpersonals.co.uk>
|
||||||
Paul Hammond <paul@paulhammond.org>
|
Paul Hammond <paul@paulhammond.org>
|
||||||
Paul Jimenez <pj@place.org>
|
Paul Jimenez <pj@place.org>
|
||||||
@@ -738,18 +554,11 @@ Paul Lietar <paul@lietar.net>
|
|||||||
Paul Morie <pmorie@gmail.com>
|
Paul Morie <pmorie@gmail.com>
|
||||||
Paul Nasrat <pnasrat@gmail.com>
|
Paul Nasrat <pnasrat@gmail.com>
|
||||||
Paul Weaver <pauweave@cisco.com>
|
Paul Weaver <pauweave@cisco.com>
|
||||||
Pavel Lobashov <ShockwaveNN@gmail.com>
|
|
||||||
Pavel Tikhomirov <ptikhomirov@parallels.com>
|
|
||||||
Pavlos Ratis <dastergon@gentoo.org>
|
Pavlos Ratis <dastergon@gentoo.org>
|
||||||
Peggy Li <peggyli.224@gmail.com>
|
|
||||||
Peter Bourgon <peter@bourgon.org>
|
Peter Bourgon <peter@bourgon.org>
|
||||||
Peter Braden <peterbraden@peterbraden.co.uk>
|
Peter Braden <peterbraden@peterbraden.co.uk>
|
||||||
Peter Choi <reikani@Peters-MacBook-Pro.local>
|
|
||||||
Peter Dave Hello <PeterDaveHello@users.noreply.github.com>
|
|
||||||
Peter Ericson <pdericson@gmail.com>
|
Peter Ericson <pdericson@gmail.com>
|
||||||
Peter Esbensen <pkesbensen@gmail.com>
|
|
||||||
Peter Salvatore <peter@psftw.com>
|
Peter Salvatore <peter@psftw.com>
|
||||||
Peter Volpe <petervo@redhat.com>
|
|
||||||
Peter Waller <p@pwaller.net>
|
Peter Waller <p@pwaller.net>
|
||||||
Phil <underscorephil@gmail.com>
|
Phil <underscorephil@gmail.com>
|
||||||
Phil Estes <estesp@linux.vnet.ibm.com>
|
Phil Estes <estesp@linux.vnet.ibm.com>
|
||||||
@@ -763,7 +572,6 @@ Pierre-Alain RIVIERE <pariviere@ippon.fr>
|
|||||||
Piotr Bogdan <ppbogdan@gmail.com>
|
Piotr Bogdan <ppbogdan@gmail.com>
|
||||||
pixelistik <pixelistik@users.noreply.github.com>
|
pixelistik <pixelistik@users.noreply.github.com>
|
||||||
Porjo <porjo38@yahoo.com.au>
|
Porjo <porjo38@yahoo.com.au>
|
||||||
Pradeep Chhetri <pradeep@indix.com>
|
|
||||||
Prasanna Gautam <prasannagautam@gmail.com>
|
Prasanna Gautam <prasannagautam@gmail.com>
|
||||||
Przemek Hejman <przemyslaw.hejman@gmail.com>
|
Przemek Hejman <przemyslaw.hejman@gmail.com>
|
||||||
pysqz <randomq@126.com>
|
pysqz <randomq@126.com>
|
||||||
@@ -772,7 +580,6 @@ Quentin Brossard <qbrossard@gmail.com>
|
|||||||
r0n22 <cameron.regan@gmail.com>
|
r0n22 <cameron.regan@gmail.com>
|
||||||
Rafal Jeczalik <rjeczalik@gmail.com>
|
Rafal Jeczalik <rjeczalik@gmail.com>
|
||||||
Rafe Colton <rafael.colton@gmail.com>
|
Rafe Colton <rafael.colton@gmail.com>
|
||||||
Raghuram Devarakonda <draghuram@gmail.com>
|
|
||||||
Rajat Pandit <rp@rajatpandit.com>
|
Rajat Pandit <rp@rajatpandit.com>
|
||||||
Rajdeep Dua <dua_rajdeep@yahoo.com>
|
Rajdeep Dua <dua_rajdeep@yahoo.com>
|
||||||
Ralph Bean <rbean@redhat.com>
|
Ralph Bean <rbean@redhat.com>
|
||||||
@@ -781,19 +588,13 @@ Ramon van Alteren <ramon@vanalteren.nl>
|
|||||||
Recursive Madman <recursive.madman@gmx.de>
|
Recursive Madman <recursive.madman@gmx.de>
|
||||||
Remi Rampin <remirampin@gmail.com>
|
Remi Rampin <remirampin@gmail.com>
|
||||||
Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
|
Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
|
||||||
resouer <resouer@163.com>
|
|
||||||
rgstephens <greg@udon.org>
|
rgstephens <greg@udon.org>
|
||||||
Rhys Hiltner <rhys@twitch.tv>
|
Rhys Hiltner <rhys@twitch.tv>
|
||||||
Rich Seymour <rseymour@gmail.com>
|
|
||||||
Richard <richard.scothern@gmail.com>
|
|
||||||
Richard Burnison <rburnison@ebay.com>
|
|
||||||
Richard Harvey <richard@squarecows.com>
|
Richard Harvey <richard@squarecows.com>
|
||||||
Richard Metzler <richard@paadee.com>
|
Richard Metzler <richard@paadee.com>
|
||||||
Richo Healey <richo@psych0tik.net>
|
Richo Healey <richo@psych0tik.net>
|
||||||
Rick Bradley <rick@users.noreply.github.com>
|
Rick Bradley <rick@users.noreply.github.com>
|
||||||
Rick van de Loo <rickvandeloo@gmail.com>
|
Rick van de Loo <rickvandeloo@gmail.com>
|
||||||
Rick Wieman <git@rickw.nl>
|
|
||||||
Rik Nijessen <rik@keefo.nl>
|
|
||||||
Robert Bachmann <rb@robertbachmann.at>
|
Robert Bachmann <rb@robertbachmann.at>
|
||||||
Robert Bittle <guywithnose@gmail.com>
|
Robert Bittle <guywithnose@gmail.com>
|
||||||
Robert Obryk <robryk@gmail.com>
|
Robert Obryk <robryk@gmail.com>
|
||||||
@@ -807,7 +608,6 @@ Rohit Jnagal <jnagal@google.com>
|
|||||||
Roland Huß <roland@jolokia.org>
|
Roland Huß <roland@jolokia.org>
|
||||||
Roland Moriz <rmoriz@users.noreply.github.com>
|
Roland Moriz <rmoriz@users.noreply.github.com>
|
||||||
Ron Smits <ron.smits@gmail.com>
|
Ron Smits <ron.smits@gmail.com>
|
||||||
root <docker-dummy@example.com>
|
|
||||||
Rovanion Luckey <rovanion.luckey@gmail.com>
|
Rovanion Luckey <rovanion.luckey@gmail.com>
|
||||||
Rudolph Gottesheim <r.gottesheim@loot.at>
|
Rudolph Gottesheim <r.gottesheim@loot.at>
|
||||||
Ryan Anderson <anderson.ryanc@gmail.com>
|
Ryan Anderson <anderson.ryanc@gmail.com>
|
||||||
@@ -818,11 +618,6 @@ Ryan O'Donnell <odonnellryanc@gmail.com>
|
|||||||
Ryan Seto <ryanseto@yak.net>
|
Ryan Seto <ryanseto@yak.net>
|
||||||
Ryan Thomas <rthomas@atlassian.com>
|
Ryan Thomas <rthomas@atlassian.com>
|
||||||
Rémy Greinhofer <remy.greinhofer@livelovely.com>
|
Rémy Greinhofer <remy.greinhofer@livelovely.com>
|
||||||
s. rannou <mxs@sbrk.org>
|
|
||||||
s00318865 <sunyuan3@huawei.com>
|
|
||||||
Sabin Basyal <sabin.basyal@gmail.com>
|
|
||||||
Sachin Joshi <sachin_jayant_joshi@hotmail.com>
|
|
||||||
Sam Abed <sam.abed@gmail.com>
|
|
||||||
Sam Alba <sam.alba@gmail.com>
|
Sam Alba <sam.alba@gmail.com>
|
||||||
Sam Bailey <cyprix@cyprix.com.au>
|
Sam Bailey <cyprix@cyprix.com.au>
|
||||||
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
|
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
|
||||||
@@ -831,9 +626,6 @@ Sam Rijs <srijs@airpost.net>
|
|||||||
Sami Wagiaalla <swagiaal@redhat.com>
|
Sami Wagiaalla <swagiaal@redhat.com>
|
||||||
Samuel Andaya <samuel@andaya.net>
|
Samuel Andaya <samuel@andaya.net>
|
||||||
Samuel PHAN <samuel-phan@users.noreply.github.com>
|
Samuel PHAN <samuel-phan@users.noreply.github.com>
|
||||||
Sankar சங்கர் <sankar.curiosity@gmail.com>
|
|
||||||
Sanket Saurav <sanketsaurav@gmail.com>
|
|
||||||
sapphiredev <se.imas.kr@gmail.com>
|
|
||||||
Satnam Singh <satnam@raintown.org>
|
Satnam Singh <satnam@raintown.org>
|
||||||
satoru <satorulogic@gmail.com>
|
satoru <satorulogic@gmail.com>
|
||||||
Satoshi Amemiya <satoshi_amemiya@voyagegroup.com>
|
Satoshi Amemiya <satoshi_amemiya@voyagegroup.com>
|
||||||
@@ -842,35 +634,26 @@ Scott Collier <emailscottcollier@gmail.com>
|
|||||||
Scott Johnston <scott@docker.com>
|
Scott Johnston <scott@docker.com>
|
||||||
Scott Stamp <scottstamp851@gmail.com>
|
Scott Stamp <scottstamp851@gmail.com>
|
||||||
Scott Walls <sawalls@umich.edu>
|
Scott Walls <sawalls@umich.edu>
|
||||||
sdreyesg <sdreyesg@gmail.com>
|
|
||||||
Sean Cronin <seancron@gmail.com>
|
Sean Cronin <seancron@gmail.com>
|
||||||
Sean P. Kane <skane@newrelic.com>
|
Sean P. Kane <skane@newrelic.com>
|
||||||
Sebastiaan van Steenis <mail@superseb.nl>
|
|
||||||
Sebastiaan van Stijn <github@gone.nl>
|
Sebastiaan van Stijn <github@gone.nl>
|
||||||
Senthil Kumar Selvaraj <senthil.thecoder@gmail.com>
|
Senthil Kumar Selvaraj <senthil.thecoder@gmail.com>
|
||||||
SeongJae Park <sj38.park@gmail.com>
|
SeongJae Park <sj38.park@gmail.com>
|
||||||
Seongyeol Lim <seongyeol37@gmail.com>
|
|
||||||
Sergey Alekseev <sergey.alekseev.minsk@gmail.com>
|
|
||||||
Sergey Evstifeev <sergey.evstifeev@gmail.com>
|
|
||||||
Shane Canon <scanon@lbl.gov>
|
Shane Canon <scanon@lbl.gov>
|
||||||
shaunol <shaunol@gmail.com>
|
shaunol <shaunol@gmail.com>
|
||||||
Shawn Landden <shawn@churchofgit.com>
|
Shawn Landden <shawn@churchofgit.com>
|
||||||
Shawn Siefkas <shawn.siefkas@meredith.com>
|
Shawn Siefkas <shawn.siefkas@meredith.com>
|
||||||
Shih-Yuan Lee <fourdollars@gmail.com>
|
Shih-Yuan Lee <fourdollars@gmail.com>
|
||||||
Shijiang Wei <mountkin@gmail.com>
|
|
||||||
Shishir Mahajan <shishir.mahajan@redhat.com>
|
Shishir Mahajan <shishir.mahajan@redhat.com>
|
||||||
shuai-z <zs.broccoli@gmail.com>
|
shuai-z <zs.broccoli@gmail.com>
|
||||||
sidharthamani <sid@rancher.com>
|
|
||||||
Silas Sewell <silas@sewell.org>
|
Silas Sewell <silas@sewell.org>
|
||||||
Simei He <hesimei@zju.edu.cn>
|
|
||||||
Simon Eskildsen <sirup@sirupsen.com>
|
|
||||||
Simon Leinen <simon.leinen@gmail.com>
|
|
||||||
Simon Taranto <simon.taranto@gmail.com>
|
Simon Taranto <simon.taranto@gmail.com>
|
||||||
Sindhu S <sindhus@live.in>
|
Sindhu S <sindhus@live.in>
|
||||||
Sjoerd Langkemper <sjoerd-github@linuxonly.nl>
|
Sjoerd Langkemper <sjoerd-github@linuxonly.nl>
|
||||||
Solomon Hykes <solomon@docker.com>
|
Solomon Hykes <solomon@docker.com>
|
||||||
Song Gao <song@gao.io>
|
Song Gao <song@gao.io>
|
||||||
Soulou <leo@unbekandt.eu>
|
Soulou <leo@unbekandt.eu>
|
||||||
|
soulshake <amy@gandi.net>
|
||||||
Sridatta Thatipamala <sthatipamala@gmail.com>
|
Sridatta Thatipamala <sthatipamala@gmail.com>
|
||||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||||
Srini Brahmaroutu <sbrahma@us.ibm.com>
|
Srini Brahmaroutu <sbrahma@us.ibm.com>
|
||||||
@@ -878,30 +661,20 @@ Srini Brahmaroutu <srbrahma@us.ibm.com>
|
|||||||
Steeve Morin <steeve.morin@gmail.com>
|
Steeve Morin <steeve.morin@gmail.com>
|
||||||
Stefan Praszalowicz <stefan@greplin.com>
|
Stefan Praszalowicz <stefan@greplin.com>
|
||||||
Stephen Crosby <stevecrozz@gmail.com>
|
Stephen Crosby <stevecrozz@gmail.com>
|
||||||
Stephen J Day <stephen.day@docker.com>
|
|
||||||
Steve Francia <steve.francia@gmail.com>
|
|
||||||
Steve Koch <stevekochscience@gmail.com>
|
|
||||||
Steven Burgess <steven.a.burgess@hotmail.com>
|
Steven Burgess <steven.a.burgess@hotmail.com>
|
||||||
Steven Merrill <steven.merrill@gmail.com>
|
Steven Merrill <steven.merrill@gmail.com>
|
||||||
Steven Richards <steven@axiomzen.co>
|
|
||||||
Steven Taylor <steven.taylor@me.com>
|
|
||||||
Sven Dowideit <SvenDowideit@home.org.au>
|
Sven Dowideit <SvenDowideit@home.org.au>
|
||||||
Swapnil Daingade <swapnil.daingade@gmail.com>
|
|
||||||
Sylvain Baubeau <sbaubeau@redhat.com>
|
|
||||||
Sylvain Bellemare <sylvain.bellemare@ezeep.com>
|
Sylvain Bellemare <sylvain.bellemare@ezeep.com>
|
||||||
Sébastien <sebastien@yoozio.com>
|
Sébastien <sebastien@yoozio.com>
|
||||||
Sébastien Luttringer <seblu@seblu.net>
|
Sébastien Luttringer <seblu@seblu.net>
|
||||||
Sébastien Stormacq <sebsto@users.noreply.github.com>
|
Sébastien Stormacq <sebsto@users.noreply.github.com>
|
||||||
tang0th <tang0th@gmx.com>
|
tang0th <tang0th@gmx.com>
|
||||||
Tangi COLIN <tangicolin@gmail.com>
|
|
||||||
Tatsuki Sugiura <sugi@nemui.org>
|
Tatsuki Sugiura <sugi@nemui.org>
|
||||||
Tatsushi Inagaki <e29253@jp.ibm.com>
|
Tatsushi Inagaki <e29253@jp.ibm.com>
|
||||||
Ted M. Young <tedyoung@gmail.com>
|
Ted M. Young <tedyoung@gmail.com>
|
||||||
Tehmasp Chaudhri <tehmasp@gmail.com>
|
Tehmasp Chaudhri <tehmasp@gmail.com>
|
||||||
Tejesh Mehta <tejesh.mehta@gmail.com>
|
Tejesh Mehta <tejesh.mehta@gmail.com>
|
||||||
Thatcher Peskens <thatcher@docker.com>
|
Thatcher Peskens <thatcher@docker.com>
|
||||||
theadactyl <thea.lamkin@gmail.com>
|
|
||||||
Thell 'Bo' Fowler <thell@tbfowler.name>
|
|
||||||
Thermionix <bond711@gmail.com>
|
Thermionix <bond711@gmail.com>
|
||||||
Thijs Terlouw <thijsterlouw@gmail.com>
|
Thijs Terlouw <thijsterlouw@gmail.com>
|
||||||
Thomas Bikeev <thomas.bikeev@mac.com>
|
Thomas Bikeev <thomas.bikeev@mac.com>
|
||||||
@@ -910,11 +683,8 @@ Thomas Hansen <thomas.hansen@gmail.com>
|
|||||||
Thomas LEVEIL <thomasleveil@gmail.com>
|
Thomas LEVEIL <thomasleveil@gmail.com>
|
||||||
Thomas Orozco <thomas@orozco.fr>
|
Thomas Orozco <thomas@orozco.fr>
|
||||||
Thomas Schroeter <thomas@cliqz.com>
|
Thomas Schroeter <thomas@cliqz.com>
|
||||||
Thomas Sjögren <konstruktoid@users.noreply.github.com>
|
|
||||||
Thomas Texier <sharkone@en-mousse.org>
|
|
||||||
Tianon Gravi <admwiggin@gmail.com>
|
Tianon Gravi <admwiggin@gmail.com>
|
||||||
Tibor Vass <teabee89@gmail.com>
|
Tibor Vass <teabee89@gmail.com>
|
||||||
Tiffany Low <tiffany@box.com>
|
|
||||||
Tim Bosse <taim@bosboot.org>
|
Tim Bosse <taim@bosboot.org>
|
||||||
Tim Hockin <thockin@google.com>
|
Tim Hockin <thockin@google.com>
|
||||||
Tim Ruffles <oi@truffles.me.uk>
|
Tim Ruffles <oi@truffles.me.uk>
|
||||||
@@ -928,7 +698,6 @@ Tobias Gesellchen <tobias@gesellix.de>
|
|||||||
Tobias Schmidt <ts@soundcloud.com>
|
Tobias Schmidt <ts@soundcloud.com>
|
||||||
Tobias Schwab <tobias.schwab@dynport.de>
|
Tobias Schwab <tobias.schwab@dynport.de>
|
||||||
Todd Lunter <tlunter@gmail.com>
|
Todd Lunter <tlunter@gmail.com>
|
||||||
Todd Whiteman <todd.whiteman@joyent.com>
|
|
||||||
Tom Fotherby <tom+github@peopleperhour.com>
|
Tom Fotherby <tom+github@peopleperhour.com>
|
||||||
Tom Hulihan <hulihan.tom159@gmail.com>
|
Tom Hulihan <hulihan.tom159@gmail.com>
|
||||||
Tom Maaswinkel <tom.maaswinkel@12wiki.eu>
|
Tom Maaswinkel <tom.maaswinkel@12wiki.eu>
|
||||||
@@ -936,22 +705,16 @@ Tomas Tomecek <ttomecek@redhat.com>
|
|||||||
Tomasz Lipinski <tlipinski@users.noreply.github.com>
|
Tomasz Lipinski <tlipinski@users.noreply.github.com>
|
||||||
Tomasz Nurkiewicz <nurkiewicz@gmail.com>
|
Tomasz Nurkiewicz <nurkiewicz@gmail.com>
|
||||||
Tommaso Visconti <tommaso.visconti@gmail.com>
|
Tommaso Visconti <tommaso.visconti@gmail.com>
|
||||||
Tomáš Hrčka <thrcka@redhat.com>
|
|
||||||
Tonis Tiigi <tonistiigi@gmail.com>
|
Tonis Tiigi <tonistiigi@gmail.com>
|
||||||
Tonny Xu <tonny.xu@gmail.com>
|
|
||||||
Tony Daws <tony@daws.ca>
|
Tony Daws <tony@daws.ca>
|
||||||
Tony Miller <mcfiredrill@gmail.com>
|
|
||||||
Torstein Husebø <torstein@huseboe.net>
|
Torstein Husebø <torstein@huseboe.net>
|
||||||
tpng <benny.tpng@gmail.com>
|
tpng <benny.tpng@gmail.com>
|
||||||
Travis Cline <travis.cline@gmail.com>
|
Travis Cline <travis.cline@gmail.com>
|
||||||
Travis Thieman <travis.thieman@gmail.com>
|
|
||||||
Trent Ogren <tedwardo2@gmail.com>
|
Trent Ogren <tedwardo2@gmail.com>
|
||||||
Tristan Carel <tristan.carel@gmail.com>
|
|
||||||
Tyler Brock <tyler.brock@gmail.com>
|
Tyler Brock <tyler.brock@gmail.com>
|
||||||
Tzu-Jung Lee <roylee17@gmail.com>
|
Tzu-Jung Lee <roylee17@gmail.com>
|
||||||
Ulysse Carion <ulyssecarion@gmail.com>
|
Ulysse Carion <ulyssecarion@gmail.com>
|
||||||
unknown <sebastiaan@ws-key-sebas3.dpi1.dpi>
|
unknown <sebastiaan@ws-key-sebas3.dpi1.dpi>
|
||||||
vagrant <vagrant@ubuntu-14.04-amd64-vbox>
|
|
||||||
Vaidas Jablonskis <jablonskis@gmail.com>
|
Vaidas Jablonskis <jablonskis@gmail.com>
|
||||||
vgeta <gopikannan.venugopalsamy@gmail.com>
|
vgeta <gopikannan.venugopalsamy@gmail.com>
|
||||||
Victor Coisne <victor.coisne@dotcloud.com>
|
Victor Coisne <victor.coisne@dotcloud.com>
|
||||||
@@ -962,7 +725,6 @@ Viktor Vojnovski <viktor.vojnovski@amadeus.com>
|
|||||||
Vincent Batts <vbatts@redhat.com>
|
Vincent Batts <vbatts@redhat.com>
|
||||||
Vincent Bernat <bernat@luffy.cx>
|
Vincent Bernat <bernat@luffy.cx>
|
||||||
Vincent Bernat <Vincent.Bernat@exoscale.ch>
|
Vincent Bernat <Vincent.Bernat@exoscale.ch>
|
||||||
Vincent Demeester <vincent@sbr.pm>
|
|
||||||
Vincent Giersch <vincent.giersch@ovh.net>
|
Vincent Giersch <vincent.giersch@ovh.net>
|
||||||
Vincent Mayers <vincent.mayers@inbloom.org>
|
Vincent Mayers <vincent.mayers@inbloom.org>
|
||||||
Vincent Woo <me@vincentwoo.com>
|
Vincent Woo <me@vincentwoo.com>
|
||||||
@@ -976,7 +738,6 @@ Vivek Goyal <vgoyal@redhat.com>
|
|||||||
Vladimir Bulyga <xx@ccxx.cc>
|
Vladimir Bulyga <xx@ccxx.cc>
|
||||||
Vladimir Kirillov <proger@wilab.org.ua>
|
Vladimir Kirillov <proger@wilab.org.ua>
|
||||||
Vladimir Rutsky <altsysrq@gmail.com>
|
Vladimir Rutsky <altsysrq@gmail.com>
|
||||||
VladimirAus <v_roudakov@yahoo.com>
|
|
||||||
Vojtech Vitek (V-Teq) <vvitek@redhat.com>
|
Vojtech Vitek (V-Teq) <vvitek@redhat.com>
|
||||||
waitingkuo <waitingkuo0527@gmail.com>
|
waitingkuo <waitingkuo0527@gmail.com>
|
||||||
Walter Leibbrandt <github@wrl.co.za>
|
Walter Leibbrandt <github@wrl.co.za>
|
||||||
@@ -984,46 +745,25 @@ Walter Stanish <walter@pratyeka.org>
|
|||||||
Ward Vandewege <ward@jhvc.com>
|
Ward Vandewege <ward@jhvc.com>
|
||||||
WarheadsSE <max@warheads.net>
|
WarheadsSE <max@warheads.net>
|
||||||
Wayne Chang <wayne@neverfear.org>
|
Wayne Chang <wayne@neverfear.org>
|
||||||
Wei-Ting Kuo <waitingkuo0527@gmail.com>
|
|
||||||
Wes Morgan <cap10morgan@gmail.com>
|
Wes Morgan <cap10morgan@gmail.com>
|
||||||
Will Dietz <w@wdtz.org>
|
Will Dietz <w@wdtz.org>
|
||||||
Will Rouesnel <w.rouesnel@gmail.com>
|
Will Rouesnel <w.rouesnel@gmail.com>
|
||||||
Will Weaver <monkey@buildingbananas.com>
|
Will Weaver <monkey@buildingbananas.com>
|
||||||
willhf <willhf@gmail.com>
|
|
||||||
William Delanoue <william.delanoue@gmail.com>
|
William Delanoue <william.delanoue@gmail.com>
|
||||||
William Henry <whenry@redhat.com>
|
William Henry <whenry@redhat.com>
|
||||||
William Riancho <wr.wllm@gmail.com>
|
William Riancho <wr.wllm@gmail.com>
|
||||||
William Thurston <thurstw@amazon.com>
|
William Thurston <thurstw@amazon.com>
|
||||||
WiseTrem <shepelyov.g@gmail.com>
|
|
||||||
wlan0 <sidharthamn@gmail.com>
|
|
||||||
Wolfgang Powisch <powo@powo.priv.at>
|
|
||||||
wonderflow <wonderflow.sun@gmail.com>
|
|
||||||
xamyzhao <x.amy.zhao@gmail.com>
|
|
||||||
XiaoBing Jiang <s7v7nislands@gmail.com>
|
|
||||||
Xinzi Zhou <imdreamrunner@gmail.com>
|
|
||||||
Xiuming Chen <cc@cxm.cc>
|
Xiuming Chen <cc@cxm.cc>
|
||||||
xuzhaokui <cynicholas@gmail.com>
|
xuzhaokui <cynicholas@gmail.com>
|
||||||
y00277921 <yuchangchun1@huawei.com>
|
|
||||||
Yahya <ya7yaz@gmail.com>
|
|
||||||
YAMADA Tsuyoshi <tyamada@minimum2scp.org>
|
|
||||||
Yan Feng <yanfeng2@huawei.com>
|
|
||||||
Yang Bai <hamo.by@gmail.com>
|
Yang Bai <hamo.by@gmail.com>
|
||||||
Yasunori Mahata <nori@mahata.net>
|
Yasunori Mahata <nori@mahata.net>
|
||||||
Yestin Sun <sunyi0804@gmail.com>
|
|
||||||
Yihang Ho <hoyihang5@gmail.com>
|
|
||||||
Yohei Ueda <yohei@jp.ibm.com>
|
Yohei Ueda <yohei@jp.ibm.com>
|
||||||
Yongzhi Pan <panyongzhi@gmail.com>
|
|
||||||
Yuan Sun <sunyuan3@huawei.com>
|
|
||||||
Yurii Rashkovskii <yrashk@gmail.com>
|
Yurii Rashkovskii <yrashk@gmail.com>
|
||||||
Zac Dover <zdover@redhat.com>
|
Zac Dover <zdover@redhat.com>
|
||||||
Zach Borboa <zachborboa@gmail.com>
|
Zach Borboa <zachborboa@gmail.com>
|
||||||
Zain Memon <zain@inzain.net>
|
Zain Memon <zain@inzain.net>
|
||||||
Zaiste! <oh@zaiste.net>
|
Zaiste! <oh@zaiste.net>
|
||||||
Zane DeGraffenried <zane.deg@gmail.com>
|
Zane DeGraffenried <zane.deg@gmail.com>
|
||||||
Zefan Li <lizefan@huawei.com>
|
|
||||||
Zen Lin(Zhinan Lin) <linzhinan@huawei.com>
|
|
||||||
Zhang Wei <zhangwei555@huawei.com>
|
|
||||||
Zhang Wentao <zhangwentao234@huawei.com>
|
|
||||||
Zilin Du <zilin.du@gmail.com>
|
Zilin Du <zilin.du@gmail.com>
|
||||||
zimbatm <zimbatm@zimbatm.com>
|
zimbatm <zimbatm@zimbatm.com>
|
||||||
Zoltan Tombol <zoltan.tombol@gmail.com>
|
Zoltan Tombol <zoltan.tombol@gmail.com>
|
||||||
|
|||||||
+7
-130
@@ -1,128 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 1.8.1 (2015-08-12)
|
|
||||||
|
|
||||||
### Distribution
|
|
||||||
|
|
||||||
* Fix a bug where pushing multiple tags would result in invalid images
|
|
||||||
|
|
||||||
## 1.8.0 (2015-08-11)
|
|
||||||
|
|
||||||
### Distribution
|
|
||||||
|
|
||||||
+ Trusted pull, push and build, disabled by default
|
|
||||||
* Make tar layers deterministic between registries
|
|
||||||
* Don't allow deleting the image of running containers
|
|
||||||
* Check if a tag name to load is a valid digest
|
|
||||||
* Allow one character repository names
|
|
||||||
* Add a more accurate error description for invalid tag name
|
|
||||||
* Make build cache ignore mtime
|
|
||||||
|
|
||||||
### Cli
|
|
||||||
|
|
||||||
+ Add support for DOCKER_CONFIG/--config to specify config file dir
|
|
||||||
+ Add --type flag for docker inspect command
|
|
||||||
+ Add formatting options to `docker ps` with `--format`
|
|
||||||
+ Replace `docker -d` with new subcommand `docker daemon`
|
|
||||||
* Zsh completion updates and improvements
|
|
||||||
* Add some missing events to bash completion
|
|
||||||
* Support daemon urls with base paths in `docker -H`
|
|
||||||
* Validate status= filter to docker ps
|
|
||||||
* Display when a container is in --net=host in docker ps
|
|
||||||
* Extend docker inspect to export image metadata related to graph driver
|
|
||||||
* Restore --default-gateway{,-v6} daemon options
|
|
||||||
* Add missing unpublished ports in docker ps
|
|
||||||
* Allow duration strings in `docker events` as --since/--until
|
|
||||||
* Expose more mounts information in `docker inspect`
|
|
||||||
|
|
||||||
### Runtime
|
|
||||||
|
|
||||||
+ Add new Fluentd logging driver
|
|
||||||
+ Allow `docker import` to load from local files
|
|
||||||
+ Add logging driver for GELF via UDP
|
|
||||||
+ Allow to copy files from host to containers with `docker cp`
|
|
||||||
+ Promote volume drivers from experimental to master
|
|
||||||
+ Add rollover options to json-file log driver, and --log-driver-opts flag
|
|
||||||
+ Add memory swappiness tuning options
|
|
||||||
* Remove cgroup read-only flag when privileged
|
|
||||||
* Make /proc, /sys, & /dev readonly for readonly containers
|
|
||||||
* Add cgroup bind mount by default
|
|
||||||
* Overlay: Export metadata for container and image in `docker inspect`
|
|
||||||
* Devicemapper: external device activation
|
|
||||||
* Devicemapper: Compare uuid of base device on startup
|
|
||||||
* Remove RC4 from the list of registry cipher suites
|
|
||||||
* Add syslog-facility option
|
|
||||||
* LXC execdriver compatibility with recent LXC versions
|
|
||||||
* Mark LXC execriver as deprecated (to be removed with the migration to runc)
|
|
||||||
|
|
||||||
### Plugins
|
|
||||||
|
|
||||||
* Separate plugin sockets and specs locations
|
|
||||||
* Allow TLS connections to plugins
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
- Add missing 'Names' field to /containers/json API output
|
|
||||||
- Make `docker rmi` of dangling images safe while pulling
|
|
||||||
- Devicemapper: Change default basesize to 100G
|
|
||||||
- Go Scheduler issue with sync.Mutex and gcc
|
|
||||||
- Fix issue where Search API endpoint would panic due to empty AuthConfig
|
|
||||||
- Set image canonical names correctly
|
|
||||||
- Check dockerinit only if lxc driver is used
|
|
||||||
- Fix ulimit usage of nproc
|
|
||||||
- Always attach STDIN if -i,--interactive is specified
|
|
||||||
- Show error messages when saving container state fails
|
|
||||||
- Fixed incorrect assumption on --bridge=none treated as disable network
|
|
||||||
- Check for invalid port specifications in host configuration
|
|
||||||
- Fix endpoint leave failure for --net=host mode
|
|
||||||
- Fix goroutine leak in the stats API if the container is not running
|
|
||||||
- Check for apparmor file before reading it
|
|
||||||
- Fix DOCKER_TLS_VERIFY being ignored
|
|
||||||
- Set umask to the default on startup
|
|
||||||
- Correct the message of pause and unpause a non-running container
|
|
||||||
- Adjust disallowed CpuShares in container creation
|
|
||||||
- ZFS: correctly apply selinux context
|
|
||||||
- Display empty string instead of <nil> when IP opt is nil
|
|
||||||
- `docker kill` returns error when container is not running
|
|
||||||
- Fix COPY/ADD quoted/json form
|
|
||||||
- Fix goroutine leak on logs -f with no output
|
|
||||||
- Remove panic in nat package on invalid hostport
|
|
||||||
- Fix container linking in Fedora 22
|
|
||||||
- Fix error caused using default gateways outside of the allocated range
|
|
||||||
- Format times in inspect command with a template as RFC3339Nano
|
|
||||||
- Make registry client to accept 2xx and 3xx http status responses as successful
|
|
||||||
- Fix race issue that caused the daemon to crash with certain layer downloads failed in a specific order.
|
|
||||||
- Fix error when the docker ps format was not valid.
|
|
||||||
- Remove redundant ip forward check.
|
|
||||||
- Fix issue trying to push images to repository mirrors.
|
|
||||||
- Fix error cleaning up network entrypoints when there is an initialization issue.
|
|
||||||
|
|
||||||
## 1.7.1 (2015-07-14)
|
|
||||||
|
|
||||||
#### Runtime
|
|
||||||
|
|
||||||
- Fix default user spawning exec process with `docker exec`
|
|
||||||
- Make `--bridge=none` not to configure the network bridge
|
|
||||||
- Publish networking stats properly
|
|
||||||
- Fix implicit devicemapper selection with static binaries
|
|
||||||
- Fix socket connections that hung intermittently
|
|
||||||
- Fix bridge interface creation on CentOS/RHEL 6.6
|
|
||||||
- Fix local dns lookups added to resolv.conf
|
|
||||||
- Fix copy command mounting volumes
|
|
||||||
- Fix read/write privileges in volumes mounted with --volumes-from
|
|
||||||
|
|
||||||
#### Remote API
|
|
||||||
|
|
||||||
- Fix unmarshalling of Command and Entrypoint
|
|
||||||
- Set limit for minimum client version supported
|
|
||||||
- Validate port specification
|
|
||||||
- Return proper errors when attach/reattach fail
|
|
||||||
|
|
||||||
#### Distribution
|
|
||||||
|
|
||||||
- Fix pulling private images
|
|
||||||
- Fix fallback between registry V2 and V1
|
|
||||||
|
|
||||||
## 1.7.0 (2015-06-16)
|
## 1.7.0 (2015-06-16)
|
||||||
|
|
||||||
#### Runtime
|
#### Runtime
|
||||||
@@ -170,7 +47,7 @@
|
|||||||
- Prohibit mount of /sys
|
- Prohibit mount of /sys
|
||||||
|
|
||||||
#### Runtime
|
#### Runtime
|
||||||
- Update AppArmor policy to not allow mounts
|
- Update Apparmor policy to not allow mounts
|
||||||
|
|
||||||
## 1.6.0 (2015-04-07)
|
## 1.6.0 (2015-04-07)
|
||||||
|
|
||||||
@@ -464,7 +341,7 @@
|
|||||||
|
|
||||||
#### Hack
|
#### Hack
|
||||||
* Clean up "go test" output from "make test" to be much more readable/scannable.
|
* Clean up "go test" output from "make test" to be much more readable/scannable.
|
||||||
* Exclude more "definitely not unit tested Go source code" directories from hack/make/test.
|
* 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.
|
+ Generate md5 and sha256 hashes when building, and upload them via hack/release.sh.
|
||||||
- Include contributed completions in Ubuntu PPA.
|
- Include contributed completions in Ubuntu PPA.
|
||||||
+ Add cli integration tests.
|
+ Add cli integration tests.
|
||||||
@@ -744,7 +621,7 @@ With the ongoing changes to the networking and execution subsystems of docker te
|
|||||||
* The ADD instruction now supports caching, which avoids unnecessarily re-uploading the same source content again and again when it hasn’t changed
|
* The ADD instruction now supports caching, which avoids unnecessarily re-uploading the same source content again and again when it hasn’t changed
|
||||||
* The new ONBUILD instruction adds to your image a “trigger” instruction to be executed at a later time, when the image is used as the base for another build
|
* The new ONBUILD instruction adds to your image a “trigger” instruction to be executed at a later time, when the image is used as the base for another build
|
||||||
* Docker now ships with an experimental storage driver which uses the BTRFS filesystem for copy-on-write
|
* Docker now ships with an experimental storage driver which uses the BTRFS filesystem for copy-on-write
|
||||||
* Docker is officially supported on Mac OS X
|
* Docker is officially supported on Mac OSX
|
||||||
* The Docker daemon supports systemd socket activation
|
* The Docker daemon supports systemd socket activation
|
||||||
|
|
||||||
## 0.7.6 (2014-01-14)
|
## 0.7.6 (2014-01-14)
|
||||||
@@ -798,12 +675,12 @@ With the ongoing changes to the networking and execution subsystems of docker te
|
|||||||
- Fix ADD caching issue with . prefixed path
|
- Fix ADD caching issue with . prefixed path
|
||||||
- Fix docker build on devicemapper by reverting sparse file tar option
|
- Fix docker build on devicemapper by reverting sparse file tar option
|
||||||
- Fix issue with file caching and prevent wrong cache hit
|
- Fix issue with file caching and prevent wrong cache hit
|
||||||
* Use same error handling while unmarshalling CMD and ENTRYPOINT
|
* Use same error handling while unmarshalling CMD and ENTRYPOINT
|
||||||
|
|
||||||
#### Documentation
|
#### Documentation
|
||||||
|
|
||||||
* Simplify and streamline Amazon Quickstart
|
* Simplify and streamline Amazon Quickstart
|
||||||
* Install instructions use unprefixed Fedora image
|
* Install instructions use unprefixed fedora image
|
||||||
* Update instructions for mtu flag for Docker on GCE
|
* Update instructions for mtu flag for Docker on GCE
|
||||||
+ Add Ubuntu Saucy to installation
|
+ Add Ubuntu Saucy to installation
|
||||||
- Fix for wrong version warning on master instead of latest
|
- Fix for wrong version warning on master instead of latest
|
||||||
@@ -978,7 +855,7 @@ With the ongoing changes to the networking and execution subsystems of docker te
|
|||||||
* Improve unit tests
|
* Improve unit tests
|
||||||
* The test suite now runs all tests even if one fails
|
* The test suite now runs all tests even if one fails
|
||||||
* Refactor C in Go (Devmapper)
|
* Refactor C in Go (Devmapper)
|
||||||
- Fix OS X compilation
|
- Fix OSX compilation
|
||||||
|
|
||||||
## 0.7.0 (2013-11-25)
|
## 0.7.0 (2013-11-25)
|
||||||
|
|
||||||
@@ -996,7 +873,7 @@ With the ongoing changes to the networking and execution subsystems of docker te
|
|||||||
|
|
||||||
#### Runtime
|
#### Runtime
|
||||||
|
|
||||||
* Improve stability, fixes some race conditions
|
* Improve stability, fixes some race conditons
|
||||||
* Skip the volumes mounted when deleting the volumes of container.
|
* Skip the volumes mounted when deleting the volumes of container.
|
||||||
* Fix layer size computation: handle hard links correctly
|
* Fix layer size computation: handle hard links correctly
|
||||||
* Use the work Path for docker cp CONTAINER:PATH
|
* Use the work Path for docker cp CONTAINER:PATH
|
||||||
|
|||||||
+4
-52
@@ -28,8 +28,8 @@ Please **DO NOT** file a public issue, instead send your report privately to
|
|||||||
[security@docker.com](mailto:security@docker.com),
|
[security@docker.com](mailto:security@docker.com),
|
||||||
|
|
||||||
Security reports are greatly appreciated and we will publicly thank you for it.
|
Security reports are greatly appreciated and we will publicly thank you for it.
|
||||||
We also like to send gifts—if you're into Docker schwag, make sure to let
|
We also like to send gifts—if you're into Docker schwag make sure to let
|
||||||
us know. We currently do not offer a paid security bounty program, but are not
|
us know We currently do not offer a paid security bounty program, but are not
|
||||||
ruling it out in the future.
|
ruling it out in the future.
|
||||||
|
|
||||||
|
|
||||||
@@ -220,7 +220,7 @@ set of patches that should be reviewed together: for example, upgrading the
|
|||||||
version of a vendored dependency and taking advantage of its now available new
|
version of a vendored dependency and taking advantage of its now available new
|
||||||
feature constitute two separate units of work. Implementing a new function and
|
feature constitute two separate units of work. Implementing a new function and
|
||||||
calling it in another file constitute a single logical unit of work. The very
|
calling it in another file constitute a single logical unit of work. The very
|
||||||
high majority of submissions should have a single commit, so if in doubt: squash
|
high majory of submissions should have a single commit, so if in doubt: squash
|
||||||
down to one.
|
down to one.
|
||||||
|
|
||||||
After every commit, [make sure the test suite passes]
|
After every commit, [make sure the test suite passes]
|
||||||
@@ -234,8 +234,6 @@ close an issue. Including references automatically closes the issue on a merge.
|
|||||||
Please do not add yourself to the `AUTHORS` file, as it is regenerated regularly
|
Please do not add yourself to the `AUTHORS` file, as it is regenerated regularly
|
||||||
from the Git history.
|
from the Git history.
|
||||||
|
|
||||||
Please see the [Coding Style](#coding-style) for further guidelines.
|
|
||||||
|
|
||||||
### Merge approval
|
### Merge approval
|
||||||
|
|
||||||
Docker maintainers use LGTM (Looks Good To Me) in comments on the code review to
|
Docker maintainers use LGTM (Looks Good To Me) in comments on the code review to
|
||||||
@@ -319,7 +317,7 @@ maintainer to make a difference on the project!
|
|||||||
|
|
||||||
### IRC meetings
|
### IRC meetings
|
||||||
|
|
||||||
There are two monthly meetings taking place on #docker-dev IRC to accommodate all
|
There are two monthly meetings taking place on #docker-dev IRC to accomodate all
|
||||||
timezones. Anybody can propose a topic for discussion prior to the meeting.
|
timezones. Anybody can propose a topic for discussion prior to the meeting.
|
||||||
|
|
||||||
If you feel the conversation is going off-topic, feel free to point it out.
|
If you feel the conversation is going off-topic, feel free to point it out.
|
||||||
@@ -387,49 +385,3 @@ do need a fair way to deal with people who are making our community suck.
|
|||||||
appeals, we know that mistakes happen, and we'll work with you to come up with a
|
appeals, we know that mistakes happen, and we'll work with you to come up with a
|
||||||
fair solution if there has been a misunderstanding.
|
fair solution if there has been a misunderstanding.
|
||||||
|
|
||||||
## Coding Style
|
|
||||||
|
|
||||||
Unless explicitly stated, we follow all coding guidelines from the Go
|
|
||||||
community. While some of these standards may seem arbitrary, they somehow seem
|
|
||||||
to result in a solid, consistent codebase.
|
|
||||||
|
|
||||||
It is possible that the code base does not currently comply with these
|
|
||||||
guidelines. We are not looking for a massive PR that fixes this, since that
|
|
||||||
goes against the spirit of the guidelines. All new contributions should make a
|
|
||||||
best effort to clean up and make the code base better than they left it.
|
|
||||||
Obviously, apply your best judgement. Remember, the goal here is to make the
|
|
||||||
code base easier for humans to navigate and understand. Always keep that in
|
|
||||||
mind when nudging others to comply.
|
|
||||||
|
|
||||||
The rules:
|
|
||||||
|
|
||||||
1. All code should be formatted with `gofmt -s`.
|
|
||||||
2. All code should pass the default levels of
|
|
||||||
[`golint`](https://github.com/golang/lint).
|
|
||||||
3. All code should follow the guidelines covered in [Effective
|
|
||||||
Go](http://golang.org/doc/effective_go.html) and [Go Code Review
|
|
||||||
Comments](https://github.com/golang/go/wiki/CodeReviewComments).
|
|
||||||
4. Comment the code. Tell us the why, the history and the context.
|
|
||||||
5. Document _all_ declarations and methods, even private ones. Declare
|
|
||||||
expectations, caveats and anything else that may be important. If a type
|
|
||||||
gets exported, having the comments already there will ensure it's ready.
|
|
||||||
6. Variable name length should be proportional to it's context and no longer.
|
|
||||||
`noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
|
|
||||||
In practice, short methods will have short variable names and globals will
|
|
||||||
have longer names.
|
|
||||||
7. No underscores in package names. If you need a compound name, step back,
|
|
||||||
and re-examine why you need a compound name. If you still think you need a
|
|
||||||
compound name, lose the underscore.
|
|
||||||
8. No utils or helpers packages. If a function is not general enough to
|
|
||||||
warrant it's own package, it has not been written generally enough to be a
|
|
||||||
part of a util package. Just leave it unexported and well-documented.
|
|
||||||
9. All tests should run with `go test` and outside tooling should not be
|
|
||||||
required. No, we don't need another unit testing framework. Assertion
|
|
||||||
packages are acceptable if they provide _real_ incremental value.
|
|
||||||
10. Even though we call these "rules" above, they are actually just
|
|
||||||
guidelines. Since you've read all the rules, you now know that.
|
|
||||||
|
|
||||||
If you are having trouble getting into the mood of idiomatic Go, we recommend
|
|
||||||
reading through [Effective Go](http://golang.org/doc/effective_go.html). The
|
|
||||||
[Go Blog](http://blog.golang.org/) is also a great resource. Drinking the
|
|
||||||
kool-aid is a lot easier than going thirsty.
|
|
||||||
|
|||||||
+20
-48
@@ -19,14 +19,14 @@
|
|||||||
# -e GPG_PASSPHRASE=gloubiboulga \
|
# -e GPG_PASSPHRASE=gloubiboulga \
|
||||||
# docker hack/release.sh
|
# docker hack/release.sh
|
||||||
#
|
#
|
||||||
# Note: AppArmor used to mess with privileged mode, but this is no longer
|
# Note: Apparmor used to mess with privileged mode, but this is no longer
|
||||||
# the case. Therefore, you don't have to disable it anymore.
|
# the case. Therefore, you don't have to disable it anymore.
|
||||||
#
|
#
|
||||||
|
|
||||||
FROM ubuntu:14.04
|
FROM ubuntu:14.04
|
||||||
MAINTAINER Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
MAINTAINER Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||||
|
|
||||||
RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys E871F18B51E0147C77796AC81196BA81F6B0FC61
|
RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net --recv-keys E871F18B51E0147C77796AC81196BA81F6B0FC61
|
||||||
RUN echo deb http://ppa.launchpad.net/zfs-native/stable/ubuntu trusty main > /etc/apt/sources.list.d/zfs.list
|
RUN echo deb http://ppa.launchpad.net/zfs-native/stable/ubuntu trusty main > /etc/apt/sources.list.d/zfs.list
|
||||||
|
|
||||||
# Packaged dependencies
|
# Packaged dependencies
|
||||||
@@ -37,10 +37,8 @@ RUN apt-get update && apt-get install -y \
|
|||||||
bash-completion \
|
bash-completion \
|
||||||
btrfs-tools \
|
btrfs-tools \
|
||||||
build-essential \
|
build-essential \
|
||||||
createrepo \
|
|
||||||
curl \
|
curl \
|
||||||
dpkg-sig \
|
dpkg-sig \
|
||||||
gcc-mingw-w64 \
|
|
||||||
git \
|
git \
|
||||||
iptables \
|
iptables \
|
||||||
libapparmor-dev \
|
libapparmor-dev \
|
||||||
@@ -71,7 +69,7 @@ RUN cd /usr/local/lvm2 \
|
|||||||
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
|
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
|
||||||
|
|
||||||
# Install lxc
|
# Install lxc
|
||||||
ENV LXC_VERSION 1.1.2
|
ENV LXC_VERSION 1.0.7
|
||||||
RUN mkdir -p /usr/src/lxc \
|
RUN mkdir -p /usr/src/lxc \
|
||||||
&& curl -sSL https://linuxcontainers.org/downloads/lxc/lxc-${LXC_VERSION}.tar.gz | tar -v -C /usr/src/lxc/ -xz --strip-components=1
|
&& curl -sSL https://linuxcontainers.org/downloads/lxc/lxc-${LXC_VERSION}.tar.gz | tar -v -C /usr/src/lxc/ -xz --strip-components=1
|
||||||
RUN cd /usr/src/lxc \
|
RUN cd /usr/src/lxc \
|
||||||
@@ -118,37 +116,21 @@ RUN git clone https://github.com/golang/tools.git /go/src/golang.org/x/tools \
|
|||||||
&& (cd /go/src/golang.org/x/tools && git checkout -q $GO_TOOLS_COMMIT) \
|
&& (cd /go/src/golang.org/x/tools && git checkout -q $GO_TOOLS_COMMIT) \
|
||||||
&& go install -v golang.org/x/tools/cmd/cover \
|
&& go install -v golang.org/x/tools/cmd/cover \
|
||||||
&& go install -v golang.org/x/tools/cmd/vet
|
&& go install -v golang.org/x/tools/cmd/vet
|
||||||
# Grab Go's lint tool
|
|
||||||
ENV GO_LINT_COMMIT f42f5c1c440621302702cb0741e9d2ca547ae80f
|
|
||||||
RUN git clone https://github.com/golang/lint.git /go/src/github.com/golang/lint \
|
|
||||||
&& (cd /go/src/github.com/golang/lint && git checkout -q $GO_LINT_COMMIT) \
|
|
||||||
&& go install -v github.com/golang/lint/golint
|
|
||||||
|
|
||||||
# TODO replace FPM with some very minimal debhelper stuff
|
# TODO replace FPM with some very minimal debhelper stuff
|
||||||
RUN gem install --no-rdoc --no-ri fpm --version 1.3.2
|
RUN gem install --no-rdoc --no-ri fpm --version 1.3.2
|
||||||
|
|
||||||
# Install registry
|
# Install registry
|
||||||
ENV REGISTRY_COMMIT ec87e9b6971d831f0eff752ddb54fb64693e51cd
|
ENV REGISTRY_COMMIT d957768537c5af40e4f4cd96871f7b2bde9e2923
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
&& export GOPATH="$(mktemp -d)" \
|
&& git clone https://github.com/docker/distribution.git /go/src/github.com/docker/distribution \
|
||||||
&& git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution" \
|
&& (cd /go/src/github.com/docker/distribution && git checkout -q $REGISTRY_COMMIT) \
|
||||||
&& (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT") \
|
&& GOPATH=/go/src/github.com/docker/distribution/Godeps/_workspace:/go \
|
||||||
&& GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
|
go build -o /go/bin/registry-v2 github.com/docker/distribution/cmd/registry \
|
||||||
go build -o /usr/local/bin/registry-v2 github.com/docker/distribution/cmd/registry \
|
&& rm -rf /go/src/github.com/docker/distribution/
|
||||||
&& rm -rf "$GOPATH"
|
|
||||||
|
|
||||||
# Install notary server
|
|
||||||
ENV NOTARY_COMMIT 8e8122eb5528f621afcd4e2854c47302f17392f7
|
|
||||||
RUN set -x \
|
|
||||||
&& export GOPATH="$(mktemp -d)" \
|
|
||||||
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
|
|
||||||
&& (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_COMMIT") \
|
|
||||||
&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
|
|
||||||
go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
|
|
||||||
&& rm -rf "$GOPATH"
|
|
||||||
|
|
||||||
# Get the "docker-py" source so we can run their integration tests
|
# Get the "docker-py" source so we can run their integration tests
|
||||||
ENV DOCKER_PY_COMMIT 139850f3f3b17357bab5ba3edfb745fb14043764
|
ENV DOCKER_PY_COMMIT 91985b239764fe54714fa0a93d52aa362357d251
|
||||||
RUN git clone https://github.com/docker/docker-py.git /docker-py \
|
RUN git clone https://github.com/docker/docker-py.git /docker-py \
|
||||||
&& cd /docker-py \
|
&& cd /docker-py \
|
||||||
&& git checkout -q $DOCKER_PY_COMMIT
|
&& git checkout -q $DOCKER_PY_COMMIT
|
||||||
@@ -180,36 +162,26 @@ RUN ln -sv $PWD/contrib/completion/bash/docker /etc/bash_completion.d/docker
|
|||||||
# Get useful and necessary Hub images so we can "docker load" locally instead of pulling
|
# Get useful and necessary Hub images so we can "docker load" locally instead of pulling
|
||||||
COPY contrib/download-frozen-image.sh /go/src/github.com/docker/docker/contrib/
|
COPY contrib/download-frozen-image.sh /go/src/github.com/docker/docker/contrib/
|
||||||
RUN ./contrib/download-frozen-image.sh /docker-frozen-images \
|
RUN ./contrib/download-frozen-image.sh /docker-frozen-images \
|
||||||
busybox:latest@8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55 \
|
busybox:latest@4986bf8c15363d1c5d15512d5266f8777bfba4974ac56e3270e7760f6f0a8125 \
|
||||||
hello-world:frozen@91c95931e552b11604fea91c2f537284149ec32fff0f700a4769cfd31d7696ae \
|
hello-world:frozen@e45a5af57b00862e5ef5782a9925979a02ba2b12dff832fd0991335f4a11e5c5 \
|
||||||
jess/unshare@5c9f6ea50341a2a8eb6677527f2bdedbf331ae894a41714fda770fb130f3314d
|
jess/unshare@5c9f6ea50341a2a8eb6677527f2bdedbf331ae894a41714fda770fb130f3314d
|
||||||
# see also "hack/make/.ensure-frozen-images" (which needs to be updated any time this list is)
|
# see also "hack/make/.ensure-frozen-images" (which needs to be updated any time this list is)
|
||||||
|
|
||||||
# Download man page generator
|
# Download man page generator
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
&& export GOPATH="$(mktemp -d)" \
|
&& git clone -b v1.0.1 https://github.com/cpuguy83/go-md2man.git /go/src/github.com/cpuguy83/go-md2man \
|
||||||
&& git clone -b v1.0.3 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
|
&& git clone -b v1.2 https://github.com/russross/blackfriday.git /go/src/github.com/russross/blackfriday
|
||||||
&& git clone -b v1.2 https://github.com/russross/blackfriday.git "$GOPATH/src/github.com/russross/blackfriday" \
|
|
||||||
&& go get -v -d github.com/cpuguy83/go-md2man \
|
|
||||||
&& go build -v -o /usr/local/bin/go-md2man github.com/cpuguy83/go-md2man \
|
|
||||||
&& rm -rf "$GOPATH"
|
|
||||||
|
|
||||||
# Download toml validator
|
# Download toml validator
|
||||||
ENV TOMLV_COMMIT 9baf8a8a9f2ed20a8e54160840c492f937eeaf9a
|
ENV TOMLV_COMMIT 9baf8a8a9f2ed20a8e54160840c492f937eeaf9a
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
&& export GOPATH="$(mktemp -d)" \
|
&& git clone https://github.com/BurntSushi/toml.git /go/src/github.com/BurntSushi/toml \
|
||||||
&& git clone https://github.com/BurntSushi/toml.git "$GOPATH/src/github.com/BurntSushi/toml" \
|
&& (cd /go/src/github.com/BurntSushi/toml && git checkout -q $TOMLV_COMMIT)
|
||||||
&& (cd "$GOPATH/src/github.com/BurntSushi/toml" && git checkout -q "$TOMLV_COMMIT") \
|
|
||||||
&& go build -v -o /usr/local/bin/tomlv github.com/BurntSushi/toml/cmd/tomlv \
|
|
||||||
&& rm -rf "$GOPATH"
|
|
||||||
|
|
||||||
# Build/install the tool for embedding resources in Windows binaries
|
# copy vendor/ because go-md2man needs golang.org/x/net
|
||||||
ENV RSRC_COMMIT e48dbf1b7fc464a9e85fcec450dddf80816b76e0
|
COPY vendor /go/src/github.com/docker/docker/vendor
|
||||||
RUN set -x \
|
RUN go install -v github.com/cpuguy83/go-md2man \
|
||||||
&& git clone https://github.com/akavel/rsrc.git /go/src/github.com/akavel/rsrc \
|
github.com/BurntSushi/toml/cmd/tomlv
|
||||||
&& cd /go/src/github.com/akavel/rsrc \
|
|
||||||
&& git checkout -q $RSRC_COMMIT \
|
|
||||||
&& go install -v
|
|
||||||
|
|
||||||
# Wrap all commands in the "docker-in-docker" script to allow nested containers
|
# Wrap all commands in the "docker-in-docker" script to allow nested containers
|
||||||
ENTRYPOINT ["hack/dind"]
|
ENTRYPOINT ["hack/dind"]
|
||||||
|
|||||||
+95
-11
@@ -124,8 +124,98 @@ the relevant operators.
|
|||||||
|
|
||||||
* If the change affects the governance, philosophy, goals or principles of the project,
|
* If the change affects the governance, philosophy, goals or principles of the project,
|
||||||
it must be approved by BDFL.
|
it must be approved by BDFL.
|
||||||
|
|
||||||
|
* A pull request can be in 1 of 5 distinct states, for each of which there is a corresponding label
|
||||||
|
that needs to be applied. `Rules.review.states` contains the list of states with possible targets
|
||||||
|
for each.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Triage
|
||||||
|
[Rules.review.states.0-needs-triage]
|
||||||
|
|
||||||
|
# Maintainers are expected to triage new incoming pull requests by removing
|
||||||
|
# the `0-triage` label and adding the correct labels (e.g. `1-design-review`)
|
||||||
|
# potentially skipping some steps depending on the kind of pull request.
|
||||||
|
# Use common sense for judging.
|
||||||
|
#
|
||||||
|
# Checking for DCO should be done at this stage.
|
||||||
|
#
|
||||||
|
# If an owner, responsible for closing or merging, can be assigned to the PR,
|
||||||
|
# the better.
|
||||||
|
|
||||||
|
close = "e.g. unresponsive contributor without DCO"
|
||||||
|
3-docs-review = "non-proposal documentation-only change"
|
||||||
|
2-code-review = "e.g. trivial bugfix"
|
||||||
|
1-design-review = "general case"
|
||||||
|
|
||||||
|
# Design review
|
||||||
|
[Rules.review.states.1-needs-design-review]
|
||||||
|
|
||||||
|
# Maintainers are expected to comment on the design of the pull request.
|
||||||
|
# Review of documentation is expected only in the context of design validation,
|
||||||
|
# not for stylistic changes.
|
||||||
|
#
|
||||||
|
# Ideally, documentation should reflect the expected behavior of the code.
|
||||||
|
# No code review should take place in this step.
|
||||||
|
#
|
||||||
|
# Once design is approved, a maintainer should make sure to remove this label
|
||||||
|
# and add the next one.
|
||||||
|
|
||||||
|
close = "design rejected"
|
||||||
|
3-docs-review = "proposals with only documentation changes"
|
||||||
|
2-code-review = "general case"
|
||||||
|
|
||||||
|
# Code review
|
||||||
|
[Rules.review.states.2-needs-code-review]
|
||||||
|
|
||||||
|
# Maintainers are expected to review the code and ensure that it is good
|
||||||
|
# quality and in accordance with the documentation in the PR.
|
||||||
|
#
|
||||||
|
# If documentation is absent but expected, maintainers should ask for documentation.
|
||||||
|
#
|
||||||
|
# All tests should pass.
|
||||||
|
#
|
||||||
|
# Once code is approved according to the rules of the subsystem, a maintainer
|
||||||
|
# should make sure to remove this label and add the next one.
|
||||||
|
|
||||||
|
close = ""
|
||||||
|
1-design-review = "raises design concerns"
|
||||||
|
4-merge = "trivial change not impacting documentation"
|
||||||
|
3-docs-review = "general case"
|
||||||
|
|
||||||
|
# Docs review
|
||||||
|
[Rules.review.states.3-needs-docs-review]
|
||||||
|
|
||||||
|
# Maintainers are expected to review the documentation in its bigger context,
|
||||||
|
# ensuring consistency, completeness, validity, and breadth of coverage across
|
||||||
|
# all extent and new documentation.
|
||||||
|
#
|
||||||
|
# They should ask for any editorial change that makes the documentation more
|
||||||
|
# consistent and easier to understand.
|
||||||
|
#
|
||||||
|
# Changes and additions to docs must be reviewed and approved (LGTM'd) by a minimum of
|
||||||
|
# two docs sub-project maintainers. If the docs change originates with a docs
|
||||||
|
# maintainer, only one additional LGTM is required (since we assume a docs maintainer
|
||||||
|
# approves of their own PR).
|
||||||
|
#
|
||||||
|
# Once documentation is approved (see below), a maintainer should make sure to remove this
|
||||||
|
# label and add the next one.
|
||||||
|
|
||||||
|
close = ""
|
||||||
|
2-code-review = "requires more code changes"
|
||||||
|
1-design-review = "raises design concerns"
|
||||||
|
4-merge = "general case"
|
||||||
|
|
||||||
|
# Merge
|
||||||
|
[Rules.review.states.4-needs-merge]
|
||||||
|
|
||||||
|
# Maintainers are expected to merge this pull request as soon as possible.
|
||||||
|
# They can ask for a rebase, or carry the pull request themselves.
|
||||||
|
# These should be the easy PRs to merge.
|
||||||
|
|
||||||
|
close = "carry PR"
|
||||||
|
merge = ""
|
||||||
|
|
||||||
[Rules.DCO]
|
[Rules.DCO]
|
||||||
|
|
||||||
title = "Helping contributors with the DCO"
|
title = "Helping contributors with the DCO"
|
||||||
@@ -224,7 +314,7 @@ made through a pull request.
|
|||||||
"jfrazelle",
|
"jfrazelle",
|
||||||
"crosbymichael"
|
"crosbymichael"
|
||||||
]
|
]
|
||||||
|
|
||||||
[Org.Operators.community]
|
[Org.Operators.community]
|
||||||
people = [
|
people = [
|
||||||
"theadactyl"
|
"theadactyl"
|
||||||
@@ -236,7 +326,7 @@ made through a pull request.
|
|||||||
# day of a new maintainer, the best advice should be "follow the C.M.'s example and you'll
|
# day of a new maintainer, the best advice should be "follow the C.M.'s example and you'll
|
||||||
# be fine".
|
# be fine".
|
||||||
"Chief Maintainer" = "crosbymichael"
|
"Chief Maintainer" = "crosbymichael"
|
||||||
|
|
||||||
# The community manager is responsible for serving the project community, including users,
|
# The community manager is responsible for serving the project community, including users,
|
||||||
# contributors and partners. This involves:
|
# contributors and partners. This involves:
|
||||||
# - facilitating communication between maintainers, contributors and users
|
# - facilitating communication between maintainers, contributors and users
|
||||||
@@ -245,7 +335,7 @@ made through a pull request.
|
|||||||
# - anything the project community needs to be successful
|
# - anything the project community needs to be successful
|
||||||
#
|
#
|
||||||
# The community manager is a point of contact for any contributor who has questions, concerns
|
# The community manager is a point of contact for any contributor who has questions, concerns
|
||||||
# or feedback about project operations.
|
# or feedback about project operations.
|
||||||
"Community Manager" = "theadactyl"
|
"Community Manager" = "theadactyl"
|
||||||
|
|
||||||
[Org."Core maintainers"]
|
[Org."Core maintainers"]
|
||||||
@@ -267,7 +357,6 @@ made through a pull request.
|
|||||||
|
|
||||||
|
|
||||||
people = [
|
people = [
|
||||||
"calavera",
|
|
||||||
"crosbymichael",
|
"crosbymichael",
|
||||||
"erikh",
|
"erikh",
|
||||||
"estesp",
|
"estesp",
|
||||||
@@ -309,7 +398,7 @@ made through a pull request.
|
|||||||
# - If the pull request has some small problems that need to be changed, make
|
# - If the pull request has some small problems that need to be changed, make
|
||||||
# a comment adressing the issues.
|
# a comment adressing the issues.
|
||||||
# - If the changes needed to a PR are small, you can add a "LGTM once the
|
# - If the changes needed to a PR are small, you can add a "LGTM once the
|
||||||
# following comments are addressed..." this will reduce needless back and
|
# following comments are adressed..." this will reduce needless back and
|
||||||
# forth.
|
# forth.
|
||||||
# - If the PR only needs a few changes before being merged, any MAINTAINER can
|
# - If the PR only needs a few changes before being merged, any MAINTAINER can
|
||||||
# make a replacement PR that incorporates the existing commits and fixes the
|
# make a replacement PR that incorporates the existing commits and fixes the
|
||||||
@@ -447,11 +536,6 @@ made through a pull request.
|
|||||||
Email = "ben@firshman.co.uk"
|
Email = "ben@firshman.co.uk"
|
||||||
GitHub = "bfirsh"
|
GitHub = "bfirsh"
|
||||||
|
|
||||||
[people.calavera]
|
|
||||||
Name = "David Calavera"
|
|
||||||
Email = "david.calavera@gmail.com"
|
|
||||||
GitHub = "calavera"
|
|
||||||
|
|
||||||
[people.cpuguy83]
|
[people.cpuguy83]
|
||||||
Name = "Brian Goff"
|
Name = "Brian Goff"
|
||||||
Email = "cpuguy83@gmail.com"
|
Email = "cpuguy83@gmail.com"
|
||||||
@@ -566,7 +650,7 @@ made through a pull request.
|
|||||||
Name = "Sebastiaan van Stijn"
|
Name = "Sebastiaan van Stijn"
|
||||||
Email = "github@gone.nl"
|
Email = "github@gone.nl"
|
||||||
GitHub = "thaJeztah"
|
GitHub = "thaJeztah"
|
||||||
|
|
||||||
[people.theadactyl]
|
[people.theadactyl]
|
||||||
Name = "Thea Lamkin"
|
Name = "Thea Lamkin"
|
||||||
Email = "thea@docker.com"
|
Email = "thea@docker.com"
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
DOCKER_ENVS := \
|
DOCKER_ENVS := \
|
||||||
-e BUILDFLAGS \
|
-e BUILDFLAGS \
|
||||||
-e DOCKER_CLIENTONLY \
|
-e DOCKER_CLIENTONLY \
|
||||||
-e DOCKER_DEBUG \
|
|
||||||
-e DOCKER_EXECDRIVER \
|
-e DOCKER_EXECDRIVER \
|
||||||
-e DOCKER_EXPERIMENTAL \
|
-e DOCKER_EXPERIMENTAL \
|
||||||
-e DOCKER_GRAPHDRIVER \
|
-e DOCKER_GRAPHDRIVER \
|
||||||
@@ -46,11 +45,6 @@ binary: build
|
|||||||
cross: build
|
cross: build
|
||||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross
|
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross
|
||||||
|
|
||||||
deb: build
|
|
||||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary build-deb
|
|
||||||
|
|
||||||
rpm: build
|
|
||||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary build-rpm
|
|
||||||
|
|
||||||
test: build
|
test: build
|
||||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross test-unit test-integration-cli test-docker-py
|
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross test-unit test-integration-cli test-docker-py
|
||||||
@@ -65,7 +59,7 @@ test-docker-py: build
|
|||||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary test-docker-py
|
$(DOCKER_RUN_DOCKER) hack/make.sh binary test-docker-py
|
||||||
|
|
||||||
validate: build
|
validate: build
|
||||||
$(DOCKER_RUN_DOCKER) hack/make.sh validate-dco validate-gofmt validate-pkg validate-lint validate-test validate-toml validate-vet
|
$(DOCKER_RUN_DOCKER) hack/make.sh validate-dco validate-gofmt validate-test validate-toml validate-vet
|
||||||
|
|
||||||
shell: build
|
shell: build
|
||||||
$(DOCKER_RUN_DOCKER) bash
|
$(DOCKER_RUN_DOCKER) bash
|
||||||
@@ -75,6 +69,3 @@ build: bundles
|
|||||||
|
|
||||||
bundles:
|
bundles:
|
||||||
mkdir bundles
|
mkdir bundles
|
||||||
|
|
||||||
docs:
|
|
||||||
$(MAKE) -C docs docs
|
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
Docker: the container engine [](https://github.com/docker/docker/releases/latest)
|
Docker: the Linux container engine
|
||||||
============================
|
==================================
|
||||||
|
|
||||||
Docker is an open source project to pack, ship and run any application
|
Docker is an open source project to pack, ship and run any application
|
||||||
as a lightweight container.
|
as a lightweight container.
|
||||||
|
|
||||||
Docker containers are both *hardware-agnostic* and *platform-agnostic*.
|
Docker containers are both *hardware-agnostic* and *platform-agnostic*.
|
||||||
This means they can run anywhere, from your laptop to the largest
|
This means they can run anywhere, from your laptop to the largest
|
||||||
cloud compute instance and everything in between - and they don't require
|
EC2 compute instance and everything in between - and they don't require
|
||||||
you to use a particular language, framework or packaging system. That
|
you to use a particular language, framework or packaging system. That
|
||||||
makes them great building blocks for deploying and scaling web apps,
|
makes them great building blocks for deploying and scaling web apps,
|
||||||
databases, and backend services without depending on a particular stack
|
databases, and backend services without depending on a particular stack
|
||||||
or provider.
|
or provider.
|
||||||
|
|
||||||
Docker began as an open-source implementation of the deployment engine which
|
Docker began as an open-source implementation of the deployment engine which
|
||||||
powers [dotCloud](https://www.dotcloud.com), a popular Platform-as-a-Service.
|
powers [dotCloud](https://dotcloud.com), a popular Platform-as-a-Service.
|
||||||
It benefits directly from the experience accumulated over several years
|
It benefits directly from the experience accumulated over several years
|
||||||
of large-scale operation and support of hundreds of thousands of
|
of large-scale operation and support of hundreds of thousands of
|
||||||
applications and databases.
|
applications and databases.
|
||||||
@@ -30,7 +30,7 @@ security@docker.com and not by creating a github issue.
|
|||||||
|
|
||||||
A common method for distributing applications and sandboxing their
|
A common method for distributing applications and sandboxing their
|
||||||
execution is to use virtual machines, or VMs. Typical VM formats are
|
execution is to use virtual machines, or VMs. Typical VM formats are
|
||||||
VMware's vmdk, Oracle VirtualBox's vdi, and Amazon EC2's ami. In theory
|
VMware's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In theory
|
||||||
these formats should allow every developer to automatically package
|
these formats should allow every developer to automatically package
|
||||||
their application into a "machine" for easy distribution and deployment.
|
their application into a "machine" for easy distribution and deployment.
|
||||||
In practice, that almost never happens, for a few reasons:
|
In practice, that almost never happens, for a few reasons:
|
||||||
@@ -58,7 +58,7 @@ takes place at the kernel level. Most modern operating system kernels
|
|||||||
now support the primitives necessary for containerization, including
|
now support the primitives necessary for containerization, including
|
||||||
Linux with [openvz](https://openvz.org),
|
Linux with [openvz](https://openvz.org),
|
||||||
[vserver](http://linux-vserver.org) and more recently
|
[vserver](http://linux-vserver.org) and more recently
|
||||||
[lxc](https://linuxcontainers.org/), Solaris with
|
[lxc](http://lxc.sourceforge.net), Solaris with
|
||||||
[zones](https://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc),
|
[zones](https://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc),
|
||||||
and FreeBSD with
|
and FreeBSD with
|
||||||
[Jails](https://www.freebsd.org/doc/handbook/jails.html).
|
[Jails](https://www.freebsd.org/doc/handbook/jails.html).
|
||||||
@@ -168,23 +168,22 @@ Under the hood
|
|||||||
Under the hood, Docker is built on the following components:
|
Under the hood, Docker is built on the following components:
|
||||||
|
|
||||||
* The
|
* The
|
||||||
[cgroups](https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt)
|
[cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c)
|
||||||
and
|
and
|
||||||
[namespaces](http://man7.org/linux/man-pages/man7/namespaces.7.html)
|
[namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part)
|
||||||
capabilities of the Linux kernel
|
capabilities of the Linux kernel
|
||||||
* The [Go](https://golang.org) programming language
|
* The [Go](https://golang.org) programming language
|
||||||
* The [Docker Image Specification](https://github.com/docker/docker/blob/master/image/spec/v1.md)
|
* The [Docker Image Specification](https://github.com/docker/docker/blob/master/image/spec/v1.md)
|
||||||
* The [Libcontainer Specification](https://github.com/opencontainers/runc/blob/master/libcontainer/SPEC.md)
|
* The [Libcontainer Specification](https://github.com/docker/libcontainer/blob/master/SPEC.md)
|
||||||
|
|
||||||
Contributing to Docker [](https://godoc.org/github.com/docker/docker)
|
Contributing to Docker
|
||||||
======================
|
======================
|
||||||
|
|
||||||
| **Master** (Linux) | **Experimental** (linux) | **Windows** | **FreeBSD** |
|
[](https://godoc.org/github.com/docker/docker)
|
||||||
|------------------|----------------------|---------|---------|
|
[](https://jenkins.dockerproject.org/job/Docker%20Master/)
|
||||||
| [](https://jenkins.dockerproject.org/view/Docker/job/Docker%20Master/) | [](https://jenkins.dockerproject.org/view/Docker/job/Docker%20Master%20%28experimental%29/) | [/badge/icon)](http://jenkins.dockerproject.org/job/Docker%20Master%20(windows)/) | [/badge/icon)](http://jenkins.dockerproject.org/job/Docker%20Master%20(freebsd)/) |
|
|
||||||
|
|
||||||
Want to hack on Docker? Awesome! We have [instructions to help you get
|
Want to hack on Docker? Awesome! We have [instructions to help you get
|
||||||
started contributing code or documentation](https://docs.docker.com/project/who-written-for/).
|
started contributing code or documentation.](https://docs.docker.com/project/who-written-for/).
|
||||||
|
|
||||||
These instructions are probably not perfect, please let us know if anything
|
These instructions are probably not perfect, please let us know if anything
|
||||||
feels wrong or incomplete. Better yet, submit a PR and improve them yourself.
|
feels wrong or incomplete. Better yet, submit a PR and improve them yourself.
|
||||||
@@ -248,7 +247,7 @@ We are always open to suggestions on process improvements, and are always lookin
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Stack Overflow</td>
|
<td>Stack Overflow</td>
|
||||||
<td>
|
<td>
|
||||||
Stack Overflow has over 7000 Docker questions listed. We regularly
|
Stack Overflow has over 7000K Docker questions listed. We regularly
|
||||||
monitor <a href="https://stackoverflow.com/search?tab=newest&q=docker" target="_blank">Docker questions</a>
|
monitor <a href="https://stackoverflow.com/search?tab=newest&q=docker" target="_blank">Docker questions</a>
|
||||||
and so do many other knowledgeable Docker users.
|
and so do many other knowledgeable Docker users.
|
||||||
</td>
|
</td>
|
||||||
@@ -290,11 +289,7 @@ system
|
|||||||
* [Docker Compose](https://github.com/docker/compose) (formerly Fig):
|
* [Docker Compose](https://github.com/docker/compose) (formerly Fig):
|
||||||
Define and run multi-container apps
|
Define and run multi-container apps
|
||||||
* [Kitematic](https://github.com/kitematic/kitematic): The easiest way to use
|
* [Kitematic](https://github.com/kitematic/kitematic): The easiest way to use
|
||||||
Docker on Mac and Windows
|
Docker on a Mac
|
||||||
|
|
||||||
If you know of another project underway that should be listed here, please help
|
If you know of another project underway that should be listed here, please help
|
||||||
us keep this list up-to-date by submitting a PR.
|
us keep this list up-to-date by submitting a PR.
|
||||||
|
|
||||||
Awesome-Docker
|
|
||||||
==============
|
|
||||||
You can find more projects, tools and articles related to Docker on the [awesome-docker list](https://github.com/veggiemonk/awesome-docker). Add your project there.
|
|
||||||
|
|||||||
-183
@@ -1,183 +0,0 @@
|
|||||||
Docker Engine Roadmap
|
|
||||||
=====================
|
|
||||||
|
|
||||||
### How should I use this document?
|
|
||||||
|
|
||||||
This document provides description of items that the project decided to prioritize. This should
|
|
||||||
serve as a reference point for Docker contributors to understand where the project is going, and
|
|
||||||
help determine if a contribution could be conflicting with some longer terms plans.
|
|
||||||
|
|
||||||
The fact that a feature isn't listed here doesn't mean that a patch for it will automatically be
|
|
||||||
refused (except for those mentioned as "frozen features" below)! We are always happy to receive
|
|
||||||
patches for new cool features we haven't thought about, or didn't judge priority. Please however
|
|
||||||
understand that such patches might take longer for us to review.
|
|
||||||
|
|
||||||
### How can I help?
|
|
||||||
|
|
||||||
Short term objectives are listed in the [wiki](https://github.com/docker/docker/wiki) and described
|
|
||||||
in [Issues](https://github.com/docker/docker/issues?q=is%3Aopen+is%3Aissue+label%3Aroadmap). Our
|
|
||||||
goal is to split down the workload in such way that anybody can jump in and help. Please comment on
|
|
||||||
issues if you want to take it to avoid duplicating effort! Similarly, if a maintainer is already
|
|
||||||
assigned on an issue you'd like to participate in, pinging him on IRC or GitHub to offer your help is
|
|
||||||
the best way to go.
|
|
||||||
|
|
||||||
### How can I add something to the roadmap?
|
|
||||||
|
|
||||||
The roadmap process is new to the Docker Engine: we are only beginning to structure and document the
|
|
||||||
project objectives. Our immediate goal is to be more transparent, and work with our community to
|
|
||||||
focus our efforts on fewer prioritized topics.
|
|
||||||
|
|
||||||
We hope to offer in the near future a process allowing anyone to propose a topic to the roadmap, but
|
|
||||||
we are not quite there yet. For the time being, the BDFL remains the keeper of the roadmap, and we
|
|
||||||
won't be accepting pull requests adding or removing items from this file.
|
|
||||||
|
|
||||||
# 1. Features and refactoring
|
|
||||||
|
|
||||||
## 1.1 Security
|
|
||||||
|
|
||||||
Security is a top objective for the Docker Engine. The most notable items we intend to provide in
|
|
||||||
the near future are:
|
|
||||||
|
|
||||||
- Trusted distribution of images: the effort is driven by the [distribution](https://github.com/docker/distribution)
|
|
||||||
group but will have significant impact on the Engine
|
|
||||||
- [User namespaces](https://github.com/docker/docker/pull/12648)
|
|
||||||
- [Seccomp support](https://github.com/docker/libcontainer/pull/613)
|
|
||||||
|
|
||||||
## 1.2 Plumbing project
|
|
||||||
|
|
||||||
We define a plumbing tool as a standalone piece of software usable and meaningful on its own. In
|
|
||||||
the current state of the Docker Engine, most subsystems provide independent functionalities (such
|
|
||||||
the builder, pushing and pulling images, running applications in a containerized environment, etc)
|
|
||||||
but all are coupled in a single binary. We want to offer the users to flexibility to use only the
|
|
||||||
pieces they need, and we will also gain in maintainability by splitting the project among multiple
|
|
||||||
repositories.
|
|
||||||
|
|
||||||
As it currently stands, the rough design outlines is to have:
|
|
||||||
- Low level plumbing tools, each dealing with one responsibility (e.g., [runC](https://runc.io))
|
|
||||||
- Docker subsystems services, each exposing an elementary concept over an API, and relying on one or
|
|
||||||
multiple lower level plumbing tools for their implementation (e.g., network management)
|
|
||||||
- Docker Engine to expose higher level actions (e.g., create a container with volume `V` and network
|
|
||||||
`N`), while still providing pass-through access to the individual subsystems.
|
|
||||||
|
|
||||||
The architectural details are still being worked on, but one thing we know for sure is that we need
|
|
||||||
to technically decouple the pieces.
|
|
||||||
|
|
||||||
### 1.2.1 Runtime
|
|
||||||
|
|
||||||
A Runtime tool already exists today in the form of [runC](https://github.com/opencontainers/runc).
|
|
||||||
We intend to modify the Engine to directly call out to a binary implementing the Open Containers
|
|
||||||
Specification such as runC rather than relying on libcontainer to set the container runtime up.
|
|
||||||
|
|
||||||
This plan will deprecate the existing [`execdriver`](https://github.com/docker/docker/tree/master/daemon/execdriver)
|
|
||||||
as different runtime backends will be implemented as separated binaries instead of being compiled
|
|
||||||
into the Engine.
|
|
||||||
|
|
||||||
### 1.2.2 Builder
|
|
||||||
|
|
||||||
The Builder (i.e., the ability to build an image from a Dockerfile) is already nicely decoupled,
|
|
||||||
but would benefit from being entirely separated from the Engine, and rely on the standard Engine
|
|
||||||
API for its operations.
|
|
||||||
|
|
||||||
### 1.2.3 Distribution
|
|
||||||
|
|
||||||
Distribution already has a [dedicated repository](https://github.com/docker/distribution) which
|
|
||||||
holds the implementation for Registry v2 and client libraries. We could imagine going further by
|
|
||||||
having the Engine call out to a binary providing image distribution related functionalities.
|
|
||||||
|
|
||||||
There are two short term goals related to image distribution. The first is stabilize and simplify
|
|
||||||
the push/pull code. Following that is the conversion to the more secure Registry V2 protocol.
|
|
||||||
|
|
||||||
### 1.2.4 Networking
|
|
||||||
|
|
||||||
Most of networking related code was already decoupled today in [libnetwork](https://github.com/docker/libnetwork).
|
|
||||||
As with other ingredients, we might want to take it a step further and make it a meaningful utility
|
|
||||||
that the Engine would call out to instead of a library.
|
|
||||||
|
|
||||||
## 1.3 Plugins
|
|
||||||
|
|
||||||
An initiative around plugins started with Docker 1.7.0, with the goal of allowing for out of
|
|
||||||
process extensibility of some Docker functionalities, starting with volumes and networking. The
|
|
||||||
approach is to provide specific extension points rather than generic hooking facilities. We also
|
|
||||||
deliberately keep the extensions API the simplest possible, expanding as we discover valid use
|
|
||||||
cases that cannot be implemented.
|
|
||||||
|
|
||||||
At the time of writing:
|
|
||||||
|
|
||||||
- Plugin support is merged as an experimental feature: real world use cases and user feedback will
|
|
||||||
help us refine the UX to make the feature more user friendly.
|
|
||||||
- There are no immediate plans to expand on the number of pluggable subsystems.
|
|
||||||
- Golang 1.5 might add language support for [plugins](https://docs.google.com/document/d/1nr-TQHw_er6GOQRsF6T43GGhFDelrAP0NqSS_00RgZQ)
|
|
||||||
which we consider supporting as an alternative to JSON/HTTP.
|
|
||||||
|
|
||||||
## 1.4 Volume management
|
|
||||||
|
|
||||||
Volumes are not a first class citizen in the Engine today: we would like better volume management,
|
|
||||||
similar to the way network are managed in the new [CNM](https://github.com/docker/docker/issues/9983).
|
|
||||||
|
|
||||||
## 1.5 Better API implementation
|
|
||||||
|
|
||||||
The current Engine API is insufficiently typed, versioned, and ultimately hard to maintain. We
|
|
||||||
also suffer from the lack of a common implementation with [Swarm](https://github.com/docker/swarm).
|
|
||||||
|
|
||||||
## 1.6 Checkpoint/restore
|
|
||||||
|
|
||||||
Support for checkpoint/restore was [merged](https://github.com/docker/libcontainer/pull/479) in
|
|
||||||
[libcontainer](https://github.com/docker/libcontainer) and made available through [runC](https://runc.io):
|
|
||||||
we intend to take advantage of it in the Engine.
|
|
||||||
|
|
||||||
# 2 Frozen features
|
|
||||||
|
|
||||||
## 2.1 Docker exec
|
|
||||||
|
|
||||||
We won't accept patches expanding the surface of `docker exec`, which we intend to keep as a
|
|
||||||
*debugging* feature, as well as being strongly dependent on the the Runtime ingredient effort.
|
|
||||||
|
|
||||||
## 2.2 Dockerfile syntax
|
|
||||||
|
|
||||||
The Dockerfile syntax as we know it is simple, and has proven successful in supporting all our
|
|
||||||
[official images](https://github.com/docker-library/official-images). Although this is *not* a
|
|
||||||
definitive move, we temporarily won't accept more patches to the Dockerfile syntax for several
|
|
||||||
reasons:
|
|
||||||
|
|
||||||
- Long term impact of syntax changes is a sensitive matter that require an amount of attention
|
|
||||||
the volume of Engine codebase and activity today doesn't allow us to provide.
|
|
||||||
- Allowing the Builder to be implemented as a separate utility consuming the Engine's API will
|
|
||||||
open the door for many possibilities, such as offering alternate syntaxes or DSL for existing
|
|
||||||
languages without cluttering the Engine's codebase.
|
|
||||||
- A standalone Builder will also offer the opportunity for a better dedicated group of maintainers
|
|
||||||
to own the Dockerfile syntax and decide collectively on the direction to give it.
|
|
||||||
- Our experience with official images tend to show that no new instruction or syntax expansion is
|
|
||||||
*strictly* necessary for the majority of use cases, and although we are aware many things are still
|
|
||||||
lacking for many, we cannot make it a priority yet for the above reasons.
|
|
||||||
|
|
||||||
Again, this is not about saying that the Dockerfile syntax is done, it's about making choices about
|
|
||||||
what we want to do first!
|
|
||||||
|
|
||||||
## 2.3 Remote Registry Operations
|
|
||||||
|
|
||||||
A large amount of work is ongoing in the area of image distribution and
|
|
||||||
provenance. This includes moving to the V2 Registry API and heavily
|
|
||||||
refactoring the code that powers these features. The desired result is more
|
|
||||||
secure, reliable and easier to use image distribution.
|
|
||||||
|
|
||||||
Part of the problem with this part of the code base is the lack of a stable
|
|
||||||
and flexible interface. If new features are added that access the registry
|
|
||||||
without solidifying these interfaces, achieving feature parity will continue
|
|
||||||
to be elusive. While we get a handle on this situation, we are imposing a
|
|
||||||
moratorium on new code that accesses the Registry API in commands that don't
|
|
||||||
already make remote calls.
|
|
||||||
|
|
||||||
Currently, only the following commands cause interaction with a remote
|
|
||||||
registry:
|
|
||||||
|
|
||||||
- push
|
|
||||||
- pull
|
|
||||||
- run
|
|
||||||
- build
|
|
||||||
- search
|
|
||||||
- login
|
|
||||||
|
|
||||||
In the interest of stabilizing the registry access model during this ongoing
|
|
||||||
work, we are not accepting additions to other commands that will cause remote
|
|
||||||
interaction with the Registry API. This moratorium will lift when the goals of
|
|
||||||
the distribution project have been met.
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJsonContentType(t *testing.T) {
|
||||||
|
if !MatchesContentType("application/json", "application/json") {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !MatchesContentType("application/json; charset=utf-8", "application/json") {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if MatchesContentType("dockerapplication/json", "application/json") {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-10
@@ -8,7 +8,6 @@ import (
|
|||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/signal"
|
"github.com/docker/docker/pkg/signal"
|
||||||
)
|
)
|
||||||
@@ -17,23 +16,23 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker attach [OPTIONS] CONTAINER
|
// Usage: docker attach [OPTIONS] CONTAINER
|
||||||
func (cli *DockerCli) CmdAttach(args ...string) error {
|
func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||||
cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, "Attach to a running container", true)
|
var (
|
||||||
noStdin := cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN")
|
cmd = cli.Subcmd("attach", "CONTAINER", "Attach to a running container", true)
|
||||||
proxy := cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process")
|
noStdin = cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN")
|
||||||
|
proxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process")
|
||||||
|
)
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
name := cmd.Arg(0)
|
||||||
|
|
||||||
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
var c types.ContainerJSON
|
var c types.ContainerJSON
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
if err := json.NewDecoder(stream).Decode(&c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +76,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
return Cli.StatusError{StatusCode: status}
|
return StatusError{StatusCode: status}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+142
-489
@@ -1,7 +1,6 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -14,24 +13,20 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/graph/tags"
|
"github.com/docker/docker/graph/tags"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/fileutils"
|
"github.com/docker/docker/pkg/fileutils"
|
||||||
"github.com/docker/docker/pkg/httputils"
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/pkg/progressreader"
|
"github.com/docker/docker/pkg/progressreader"
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
"github.com/docker/docker/pkg/ulimit"
|
"github.com/docker/docker/pkg/symlink"
|
||||||
"github.com/docker/docker/pkg/units"
|
"github.com/docker/docker/pkg/units"
|
||||||
"github.com/docker/docker/pkg/urlutil"
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
@@ -48,7 +43,7 @@ const (
|
|||||||
//
|
//
|
||||||
// Usage: docker build [OPTIONS] PATH | URL | -
|
// Usage: docker build [OPTIONS] PATH | URL | -
|
||||||
func (cli *DockerCli) CmdBuild(args ...string) error {
|
func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
cmd := Cli.Subcmd("build", []string{"PATH | URL | -"}, "Build a new image from the source code at PATH", true)
|
cmd := cli.Subcmd("build", "PATH | URL | -", "Build a new image from the source code at PATH", true)
|
||||||
tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) for the image")
|
tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) for the image")
|
||||||
suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
|
suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
|
||||||
noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
|
noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
|
||||||
@@ -59,123 +54,160 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||||||
flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
|
flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
|
||||||
flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap")
|
flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap")
|
||||||
flCPUShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
|
flCPUShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
|
||||||
flCPUPeriod := cmd.Int64([]string{"-cpu-period"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
|
flCpuPeriod := cmd.Int64([]string{"-cpu-period"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
|
||||||
flCPUQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
|
flCpuQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
|
||||||
flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
|
flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
|
||||||
flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
|
flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
|
||||||
flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
|
flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
|
||||||
|
|
||||||
ulimits := make(map[string]*ulimit.Ulimit)
|
|
||||||
flUlimits := opts.NewUlimitOpt(&ulimits)
|
|
||||||
cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
|
|
||||||
|
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
// For trusted pull on "FROM <image>" instruction.
|
|
||||||
addTrustedFlags(cmd, true)
|
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
context io.ReadCloser
|
context archive.Archive
|
||||||
isRemote bool
|
isRemote bool
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
_, err = exec.LookPath("git")
|
_, err = exec.LookPath("git")
|
||||||
hasGit := err == nil
|
hasGit := err == nil
|
||||||
|
if cmd.Arg(0) == "-" {
|
||||||
|
// As a special case, 'docker build -' will build from either an empty context with the
|
||||||
|
// contents of stdin as a Dockerfile, or a tar-ed context from stdin.
|
||||||
|
buf := bufio.NewReader(cli.in)
|
||||||
|
magic, err := buf.Peek(tarHeaderSize)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return fmt.Errorf("failed to peek context header from STDIN: %v", err)
|
||||||
|
}
|
||||||
|
if !archive.IsArchive(magic) {
|
||||||
|
dockerfile, err := ioutil.ReadAll(buf)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read Dockerfile from STDIN: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
specifiedContext := cmd.Arg(0)
|
// -f option has no meaning when we're reading it from stdin,
|
||||||
|
// so just use our default Dockerfile name
|
||||||
|
*dockerfileName = api.DefaultDockerfileName
|
||||||
|
context, err = archive.Generate(*dockerfileName, string(dockerfile))
|
||||||
|
} else {
|
||||||
|
context = ioutil.NopCloser(buf)
|
||||||
|
}
|
||||||
|
} else if urlutil.IsURL(cmd.Arg(0)) && (!urlutil.IsGitURL(cmd.Arg(0)) || !hasGit) {
|
||||||
|
isRemote = true
|
||||||
|
} else {
|
||||||
|
root := cmd.Arg(0)
|
||||||
|
if urlutil.IsGitURL(root) {
|
||||||
|
root, err = utils.GitClone(root)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(root)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(root); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
absRoot, err := filepath.Abs(root)
|
||||||
contextDir string
|
if err != nil {
|
||||||
tempDir string
|
return err
|
||||||
relDockerfile string
|
}
|
||||||
)
|
|
||||||
|
|
||||||
switch {
|
filename := *dockerfileName // path to Dockerfile
|
||||||
case specifiedContext == "-":
|
|
||||||
tempDir, relDockerfile, err = getContextFromReader(cli.in, *dockerfileName)
|
if *dockerfileName == "" {
|
||||||
case urlutil.IsGitURL(specifiedContext) && hasGit:
|
// No -f/--file was specified so use the default
|
||||||
tempDir, relDockerfile, err = getContextFromGitURL(specifiedContext, *dockerfileName)
|
*dockerfileName = api.DefaultDockerfileName
|
||||||
case urlutil.IsURL(specifiedContext):
|
filename = filepath.Join(absRoot, *dockerfileName)
|
||||||
tempDir, relDockerfile, err = getContextFromURL(cli.out, specifiedContext, *dockerfileName)
|
|
||||||
default:
|
// Just to be nice ;-) look for 'dockerfile' too but only
|
||||||
contextDir, relDockerfile, err = getContextFromLocalDir(specifiedContext, *dockerfileName)
|
// use it if we found it, otherwise ignore this check
|
||||||
|
if _, err = os.Lstat(filename); os.IsNotExist(err) {
|
||||||
|
tmpFN := path.Join(absRoot, strings.ToLower(*dockerfileName))
|
||||||
|
if _, err = os.Lstat(tmpFN); err == nil {
|
||||||
|
*dockerfileName = strings.ToLower(*dockerfileName)
|
||||||
|
filename = tmpFN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
origDockerfile := *dockerfileName // used for error msg
|
||||||
|
if filename, err = filepath.Abs(filename); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that 'filename' is within the build context
|
||||||
|
filename, err = symlink.FollowSymlinkInScope(filename, absRoot)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", origDockerfile, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now reset the dockerfileName to be relative to the build context
|
||||||
|
*dockerfileName, err = filepath.Rel(absRoot, filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// And canonicalize dockerfile name to a platform-independent one
|
||||||
|
*dockerfileName, err = archive.CanonicalTarNameForPath(*dockerfileName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Cannot canonicalize dockerfile path %s: %v", *dockerfileName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = os.Lstat(filename); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("Cannot locate Dockerfile: %s", origDockerfile)
|
||||||
|
}
|
||||||
|
var includes = []string{"."}
|
||||||
|
|
||||||
|
excludes, err := utils.ReadDockerIgnore(path.Join(root, ".dockerignore"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If .dockerignore mentions .dockerignore or the Dockerfile
|
||||||
|
// then make sure we send both files over to the daemon
|
||||||
|
// because Dockerfile is, obviously, needed no matter what, and
|
||||||
|
// .dockerignore is needed to know if either one needs to be
|
||||||
|
// removed. The deamon will remove them for us, if needed, after it
|
||||||
|
// parses the Dockerfile.
|
||||||
|
keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
|
||||||
|
keepThem2, _ := fileutils.Matches(*dockerfileName, excludes)
|
||||||
|
if keepThem1 || keepThem2 {
|
||||||
|
includes = append(includes, ".dockerignore", *dockerfileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := utils.ValidateContextDirectory(root, excludes); err != nil {
|
||||||
|
return fmt.Errorf("Error checking context is accessible: '%s'. Please check permissions and try again.", err)
|
||||||
|
}
|
||||||
|
options := &archive.TarOptions{
|
||||||
|
Compression: archive.Uncompressed,
|
||||||
|
ExcludePatterns: excludes,
|
||||||
|
IncludeFiles: includes,
|
||||||
|
}
|
||||||
|
context, err = archive.TarWithOptions(root, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
// windows: show error message about modified file permissions
|
||||||
return fmt.Errorf("unable to prepare context: %s", err)
|
// FIXME: this is not a valid warning when the daemon is running windows. should be removed once docker engine for windows can build.
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
fmt.Fprintln(cli.err, `SECURITY WARNING: You are building a Docker image from Windows against a Linux Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tempDir != "" {
|
var body io.Reader
|
||||||
defer os.RemoveAll(tempDir)
|
|
||||||
contextDir = tempDir
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve the FROM lines in the Dockerfile to trusted digest references
|
|
||||||
// using Notary. On a successful build, we must tag the resolved digests
|
|
||||||
// to the original name specified in the Dockerfile.
|
|
||||||
newDockerfile, resolvedTags, err := rewriteDockerfileFrom(filepath.Join(contextDir, relDockerfile), cli.trustedReference)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to process Dockerfile: %v", err)
|
|
||||||
}
|
|
||||||
defer newDockerfile.Close()
|
|
||||||
|
|
||||||
// And canonicalize dockerfile name to a platform-independent one
|
|
||||||
relDockerfile, err = archive.CanonicalTarNameForPath(relDockerfile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var includes = []string{"."}
|
|
||||||
|
|
||||||
excludes, err := utils.ReadDockerIgnore(path.Join(contextDir, ".dockerignore"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := utils.ValidateContextDirectory(contextDir, excludes); err != nil {
|
|
||||||
return fmt.Errorf("Error checking context: '%s'.", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If .dockerignore mentions .dockerignore or the Dockerfile
|
|
||||||
// then make sure we send both files over to the daemon
|
|
||||||
// because Dockerfile is, obviously, needed no matter what, and
|
|
||||||
// .dockerignore is needed to know if either one needs to be
|
|
||||||
// removed. The deamon will remove them for us, if needed, after it
|
|
||||||
// parses the Dockerfile. Ignore errors here, as they will have been
|
|
||||||
// caught by ValidateContextDirectory above.
|
|
||||||
keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
|
|
||||||
keepThem2, _ := fileutils.Matches(relDockerfile, excludes)
|
|
||||||
if keepThem1 || keepThem2 {
|
|
||||||
includes = append(includes, ".dockerignore", relDockerfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
context, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
|
|
||||||
Compression: archive.Uncompressed,
|
|
||||||
ExcludePatterns: excludes,
|
|
||||||
IncludeFiles: includes,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
|
|
||||||
// Dockerfile which uses trusted pulls.
|
|
||||||
context = replaceDockerfileTarWrapper(context, newDockerfile, relDockerfile)
|
|
||||||
|
|
||||||
// Setup an upload progress bar
|
// Setup an upload progress bar
|
||||||
// FIXME: ProgressReader shouldn't be this annoying to use
|
// FIXME: ProgressReader shouldn't be this annoying to use
|
||||||
sf := streamformatter.NewStreamFormatter()
|
if context != nil {
|
||||||
var body io.Reader = progressreader.New(progressreader.Config{
|
sf := streamformatter.NewStreamFormatter()
|
||||||
In: context,
|
body = progressreader.New(progressreader.Config{
|
||||||
Out: cli.out,
|
In: context,
|
||||||
Formatter: sf,
|
Out: cli.out,
|
||||||
NewLines: true,
|
Formatter: sf,
|
||||||
ID: "",
|
NewLines: true,
|
||||||
Action: "Sending build context to Docker daemon",
|
ID: "",
|
||||||
})
|
Action: "Sending build context to Docker daemon",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var memory int64
|
var memory int64
|
||||||
if *flMemoryString != "" {
|
if *flMemoryString != "" {
|
||||||
@@ -242,20 +274,13 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||||||
v.Set("cpusetcpus", *flCPUSetCpus)
|
v.Set("cpusetcpus", *flCPUSetCpus)
|
||||||
v.Set("cpusetmems", *flCPUSetMems)
|
v.Set("cpusetmems", *flCPUSetMems)
|
||||||
v.Set("cpushares", strconv.FormatInt(*flCPUShares, 10))
|
v.Set("cpushares", strconv.FormatInt(*flCPUShares, 10))
|
||||||
v.Set("cpuquota", strconv.FormatInt(*flCPUQuota, 10))
|
v.Set("cpuquota", strconv.FormatInt(*flCpuQuota, 10))
|
||||||
v.Set("cpuperiod", strconv.FormatInt(*flCPUPeriod, 10))
|
v.Set("cpuperiod", strconv.FormatInt(*flCpuPeriod, 10))
|
||||||
v.Set("memory", strconv.FormatInt(memory, 10))
|
v.Set("memory", strconv.FormatInt(memory, 10))
|
||||||
v.Set("memswap", strconv.FormatInt(memorySwap, 10))
|
v.Set("memswap", strconv.FormatInt(memorySwap, 10))
|
||||||
v.Set("cgroupparent", *flCgroupParent)
|
v.Set("cgroupparent", *flCgroupParent)
|
||||||
|
|
||||||
v.Set("dockerfile", relDockerfile)
|
v.Set("dockerfile", *dockerfileName)
|
||||||
|
|
||||||
ulimitsVar := flUlimits.GetList()
|
|
||||||
ulimitsJSON, err := json.Marshal(ulimitsVar)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
v.Set("ulimits", string(ulimitsJSON))
|
|
||||||
|
|
||||||
headers := http.Header(make(map[string][]string))
|
headers := http.Header(make(map[string][]string))
|
||||||
buf, err := json.Marshal(cli.configFile.AuthConfigs)
|
buf, err := json.Marshal(cli.configFile.AuthConfigs)
|
||||||
@@ -263,395 +288,23 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
|
headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
|
||||||
headers.Set("Content-Type", "application/tar")
|
|
||||||
|
|
||||||
|
if context != nil {
|
||||||
|
headers.Set("Content-Type", "application/tar")
|
||||||
|
}
|
||||||
sopts := &streamOpts{
|
sopts := &streamOpts{
|
||||||
rawTerminal: true,
|
rawTerminal: true,
|
||||||
in: body,
|
in: body,
|
||||||
out: cli.out,
|
out: cli.out,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
}
|
}
|
||||||
|
err = cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), sopts)
|
||||||
serverResp, err := cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), sopts)
|
|
||||||
|
|
||||||
// Windows: show error message about modified file permissions.
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
h, err := httputils.ParseServerHeader(serverResp.header.Get("Server"))
|
|
||||||
if err == nil {
|
|
||||||
if h.OS != "windows" {
|
|
||||||
fmt.Fprintln(cli.err, `SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
||||||
// If no error code is set, default to 1
|
// If no error code is set, default to 1
|
||||||
if jerr.Code == 0 {
|
if jerr.Code == 0 {
|
||||||
jerr.Code = 1
|
jerr.Code = 1
|
||||||
}
|
}
|
||||||
return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
|
return StatusError{Status: jerr.Message, StatusCode: jerr.Code}
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since the build was successful, now we must tag any of the resolved
|
|
||||||
// images from the above Dockerfile rewrite.
|
|
||||||
for _, resolved := range resolvedTags {
|
|
||||||
if err := cli.tagTrusted(resolved.repoInfo, resolved.digestRef, resolved.tagRef); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isUNC returns true if the path is UNC (one starting \\). It always returns
|
|
||||||
// false on Linux.
|
|
||||||
func isUNC(path string) bool {
|
|
||||||
return runtime.GOOS == "windows" && strings.HasPrefix(path, `\\`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDockerfileRelPath uses the given context directory for a `docker build`
|
|
||||||
// and returns the absolute path to the context directory, the relative path of
|
|
||||||
// the dockerfile in that context directory, and a non-nil error on success.
|
|
||||||
func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDir, relDockerfile string, err error) {
|
|
||||||
if absContextDir, err = filepath.Abs(givenContextDir); err != nil {
|
|
||||||
return "", "", fmt.Errorf("unable to get absolute context directory: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The context dir might be a symbolic link, so follow it to the actual
|
|
||||||
// target directory.
|
|
||||||
//
|
|
||||||
// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
|
|
||||||
// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
|
|
||||||
// paths (those starting with \\). This hack means that when using links
|
|
||||||
// on UNC paths, they will not be followed.
|
|
||||||
if !isUNC(absContextDir) {
|
|
||||||
absContextDir, err = filepath.EvalSymlinks(absContextDir)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("unable to evaluate symlinks in context path: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stat, err := os.Lstat(absContextDir)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("unable to stat context directory %q: %v", absContextDir, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !stat.IsDir() {
|
|
||||||
return "", "", fmt.Errorf("context must be a directory: %s", absContextDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
absDockerfile := givenDockerfile
|
|
||||||
if absDockerfile == "" {
|
|
||||||
// No -f/--file was specified so use the default relative to the
|
|
||||||
// context directory.
|
|
||||||
absDockerfile = filepath.Join(absContextDir, api.DefaultDockerfileName)
|
|
||||||
|
|
||||||
// Just to be nice ;-) look for 'dockerfile' too but only
|
|
||||||
// use it if we found it, otherwise ignore this check
|
|
||||||
if _, err = os.Lstat(absDockerfile); os.IsNotExist(err) {
|
|
||||||
altPath := filepath.Join(absContextDir, strings.ToLower(api.DefaultDockerfileName))
|
|
||||||
if _, err = os.Lstat(altPath); err == nil {
|
|
||||||
absDockerfile = altPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not already an absolute path, the Dockerfile path should be joined to
|
|
||||||
// the base directory.
|
|
||||||
if !filepath.IsAbs(absDockerfile) {
|
|
||||||
absDockerfile = filepath.Join(absContextDir, absDockerfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate symlinks in the path to the Dockerfile too.
|
|
||||||
//
|
|
||||||
// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
|
|
||||||
// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
|
|
||||||
// paths (those starting with \\). This hack means that when using links
|
|
||||||
// on UNC paths, they will not be followed.
|
|
||||||
if !isUNC(absDockerfile) {
|
|
||||||
absDockerfile, err = filepath.EvalSymlinks(absDockerfile)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("unable to evaluate symlinks in Dockerfile path: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Lstat(absDockerfile); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return "", "", fmt.Errorf("Cannot locate Dockerfile: %q", absDockerfile)
|
|
||||||
}
|
|
||||||
return "", "", fmt.Errorf("unable to stat Dockerfile: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if relDockerfile, err = filepath.Rel(absContextDir, absDockerfile); err != nil {
|
|
||||||
return "", "", fmt.Errorf("unable to get relative Dockerfile path: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
|
|
||||||
return "", "", fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", givenDockerfile, givenContextDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
return absContextDir, relDockerfile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeToFile copies from the given reader and writes it to a file with the
|
|
||||||
// given filename.
|
|
||||||
func writeToFile(r io.Reader, filename string) error {
|
|
||||||
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(0600))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to create file: %v", err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
if _, err := io.Copy(file, r); err != nil {
|
|
||||||
return fmt.Errorf("unable to write file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getContextFromReader will read the contents of the given reader as either a
|
|
||||||
// Dockerfile or tar archive to be extracted to a temporary directory used as
|
|
||||||
// the context directory. Returns the absolute path to the temporary context
|
|
||||||
// directory, the relative path of the dockerfile in that context directory,
|
|
||||||
// and a non-nil error on success.
|
|
||||||
func getContextFromReader(r io.Reader, dockerfileName string) (absContextDir, relDockerfile string, err error) {
|
|
||||||
buf := bufio.NewReader(r)
|
|
||||||
|
|
||||||
magic, err := buf.Peek(tarHeaderSize)
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
return "", "", fmt.Errorf("failed to peek context header from STDIN: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if absContextDir, err = ioutil.TempDir("", "docker-build-context-"); err != nil {
|
|
||||||
return "", "", fmt.Errorf("unbale to create temporary context directory: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func(d string) {
|
|
||||||
if err != nil {
|
|
||||||
os.RemoveAll(d)
|
|
||||||
}
|
|
||||||
}(absContextDir)
|
|
||||||
|
|
||||||
if !archive.IsArchive(magic) { // Input should be read as a Dockerfile.
|
|
||||||
// -f option has no meaning when we're reading it from stdin,
|
|
||||||
// so just use our default Dockerfile name
|
|
||||||
relDockerfile = api.DefaultDockerfileName
|
|
||||||
|
|
||||||
return absContextDir, relDockerfile, writeToFile(buf, filepath.Join(absContextDir, relDockerfile))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := archive.Untar(buf, absContextDir, nil); err != nil {
|
|
||||||
return "", "", fmt.Errorf("unable to extract stdin to temporary context directory: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return getDockerfileRelPath(absContextDir, dockerfileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getContextFromGitURL uses a Git URL as context for a `docker build`. The
|
|
||||||
// git repo is cloned into a temporary directory used as the context directory.
|
|
||||||
// Returns the absolute path to the temporary context directory, the relative
|
|
||||||
// path of the dockerfile in that context directory, and a non-nil error on
|
|
||||||
// success.
|
|
||||||
func getContextFromGitURL(gitURL, dockerfileName string) (absContextDir, relDockerfile string, err error) {
|
|
||||||
if absContextDir, err = utils.GitClone(gitURL); err != nil {
|
|
||||||
return "", "", fmt.Errorf("unable to 'git clone' to temporary context directory: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return getDockerfileRelPath(absContextDir, dockerfileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getContextFromURL uses a remote URL as context for a `docker build`. The
|
|
||||||
// remote resource is downloaded as either a Dockerfile or a context tar
|
|
||||||
// archive and stored in a temporary directory used as the context directory.
|
|
||||||
// Returns the absolute path to the temporary context directory, the relative
|
|
||||||
// path of the dockerfile in that context directory, and a non-nil error on
|
|
||||||
// success.
|
|
||||||
func getContextFromURL(out io.Writer, remoteURL, dockerfileName string) (absContextDir, relDockerfile string, err error) {
|
|
||||||
response, err := httputils.Download(remoteURL)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("unable to download remote context %s: %v", remoteURL, err)
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
// Pass the response body through a progress reader.
|
|
||||||
progReader := &progressreader.Config{
|
|
||||||
In: response.Body,
|
|
||||||
Out: out,
|
|
||||||
Formatter: streamformatter.NewStreamFormatter(),
|
|
||||||
Size: response.ContentLength,
|
|
||||||
NewLines: true,
|
|
||||||
ID: "",
|
|
||||||
Action: fmt.Sprintf("Downloading build context from remote url: %s", remoteURL),
|
|
||||||
}
|
|
||||||
|
|
||||||
return getContextFromReader(progReader, dockerfileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getContextFromLocalDir uses the given local directory as context for a
|
|
||||||
// `docker build`. Returns the absolute path to the local context directory,
|
|
||||||
// the relative path of the dockerfile in that context directory, and a non-nil
|
|
||||||
// error on success.
|
|
||||||
func getContextFromLocalDir(localDir, dockerfileName string) (absContextDir, relDockerfile string, err error) {
|
|
||||||
// When using a local context directory, when the Dockerfile is specified
|
|
||||||
// with the `-f/--file` option then it is considered relative to the
|
|
||||||
// current directory and not the context directory.
|
|
||||||
if dockerfileName != "" {
|
|
||||||
if dockerfileName, err = filepath.Abs(dockerfileName); err != nil {
|
|
||||||
return "", "", fmt.Errorf("unable to get absolute path to Dockerfile: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return getDockerfileRelPath(localDir, dockerfileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
var dockerfileFromLinePattern = regexp.MustCompile(`(?i)^[\s]*FROM[ \f\r\t\v]+(?P<image>[^ \f\r\t\v\n#]+)`)
|
|
||||||
|
|
||||||
type trustedDockerfile struct {
|
|
||||||
*os.File
|
|
||||||
size int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (td *trustedDockerfile) Close() error {
|
|
||||||
td.File.Close()
|
|
||||||
return os.Remove(td.File.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolvedTag records the repository, tag, and resolved digest reference
|
|
||||||
// from a Dockerfile rewrite.
|
|
||||||
type resolvedTag struct {
|
|
||||||
repoInfo *registry.RepositoryInfo
|
|
||||||
digestRef, tagRef registry.Reference
|
|
||||||
}
|
|
||||||
|
|
||||||
// rewriteDockerfileFrom rewrites the given Dockerfile by resolving images in
|
|
||||||
// "FROM <image>" instructions to a digest reference. `translator` is a
|
|
||||||
// function that takes a repository name and tag reference and returns a
|
|
||||||
// trusted digest reference.
|
|
||||||
func rewriteDockerfileFrom(dockerfileName string, translator func(string, registry.Reference) (registry.Reference, error)) (newDockerfile *trustedDockerfile, resolvedTags []*resolvedTag, err error) {
|
|
||||||
dockerfile, err := os.Open(dockerfileName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("unable to open Dockerfile: %v", err)
|
|
||||||
}
|
|
||||||
defer dockerfile.Close()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(dockerfile)
|
|
||||||
|
|
||||||
// Make a tempfile to store the rewritten Dockerfile.
|
|
||||||
tempFile, err := ioutil.TempFile("", "trusted-dockerfile-")
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("unable to make temporary trusted Dockerfile: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
trustedFile := &trustedDockerfile{
|
|
||||||
File: tempFile,
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
// Close the tempfile if there was an error during Notary lookups.
|
|
||||||
// Otherwise the caller should close it.
|
|
||||||
trustedFile.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Scan the lines of the Dockerfile, looking for a "FROM" line.
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
|
|
||||||
matches := dockerfileFromLinePattern.FindStringSubmatch(line)
|
|
||||||
if matches != nil && matches[1] != "scratch" {
|
|
||||||
// Replace the line with a resolved "FROM repo@digest"
|
|
||||||
repo, tag := parsers.ParseRepositoryTag(matches[1])
|
|
||||||
if tag == "" {
|
|
||||||
tag = tags.DefaultTag
|
|
||||||
}
|
|
||||||
|
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(repo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("unable to parse repository info: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ref := registry.ParseReference(tag)
|
|
||||||
|
|
||||||
if !ref.HasDigest() && isTrusted() {
|
|
||||||
trustedRef, err := translator(repo, ref)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", trustedRef.ImageName(repo)))
|
|
||||||
resolvedTags = append(resolvedTags, &resolvedTag{
|
|
||||||
repoInfo: repoInfo,
|
|
||||||
digestRef: trustedRef,
|
|
||||||
tagRef: ref,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := fmt.Fprintln(tempFile, line)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
trustedFile.size += int64(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
tempFile.Seek(0, os.SEEK_SET)
|
|
||||||
|
|
||||||
return trustedFile, resolvedTags, scanner.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// replaceDockerfileTarWrapper wraps the given input tar archive stream and
|
|
||||||
// replaces the entry with the given Dockerfile name with the contents of the
|
|
||||||
// new Dockerfile. Returns a new tar archive stream with the replaced
|
|
||||||
// Dockerfile.
|
|
||||||
func replaceDockerfileTarWrapper(inputTarStream io.ReadCloser, newDockerfile *trustedDockerfile, dockerfileName string) io.ReadCloser {
|
|
||||||
pipeReader, pipeWriter := io.Pipe()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
tarReader := tar.NewReader(inputTarStream)
|
|
||||||
tarWriter := tar.NewWriter(pipeWriter)
|
|
||||||
|
|
||||||
defer inputTarStream.Close()
|
|
||||||
|
|
||||||
for {
|
|
||||||
hdr, err := tarReader.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
// Signals end of archive.
|
|
||||||
tarWriter.Close()
|
|
||||||
pipeWriter.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
pipeWriter.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var content io.Reader = tarReader
|
|
||||||
|
|
||||||
if hdr.Name == dockerfileName {
|
|
||||||
// This entry is the Dockerfile. Since the tar archive was
|
|
||||||
// generated from a directory on the local filesystem, the
|
|
||||||
// Dockerfile will only appear once in the archive.
|
|
||||||
hdr.Size = newDockerfile.size
|
|
||||||
content = newDockerfile
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tarWriter.WriteHeader(hdr); err != nil {
|
|
||||||
pipeWriter.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.Copy(tarWriter, content); err != nil {
|
|
||||||
pipeWriter.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return pipeReader
|
|
||||||
}
|
}
|
||||||
|
|||||||
+130
-94
@@ -2,34 +2,30 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"path/filepath"
|
||||||
"os"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/cliconfig"
|
"github.com/docker/docker/cliconfig"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/pkg/homedir"
|
||||||
"github.com/docker/docker/pkg/sockets"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
"github.com/docker/docker/pkg/tlsconfig"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DockerCli represents the docker command line client.
|
// DockerCli represents the docker command line client.
|
||||||
// Instances of the client can be returned from NewDockerCli.
|
// Instances of the client can be returned from NewDockerCli.
|
||||||
type DockerCli struct {
|
type DockerCli struct {
|
||||||
// initializing closure
|
|
||||||
init func() error
|
|
||||||
|
|
||||||
// proto holds the client protocol i.e. unix.
|
// proto holds the client protocol i.e. unix.
|
||||||
proto string
|
proto string
|
||||||
// addr holds the client address.
|
// addr holds the client address.
|
||||||
addr string
|
addr string
|
||||||
// basePath holds the path to prepend to the requests
|
|
||||||
basePath string
|
|
||||||
|
|
||||||
// configFile has the client configuration file
|
// configFile has the client configuration file
|
||||||
configFile *cliconfig.ConfigFile
|
configFile *cliconfig.ConfigFile
|
||||||
@@ -58,13 +54,83 @@ type DockerCli struct {
|
|||||||
transport *http.Transport
|
transport *http.Transport
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize calls the init function that will setup the configuration for the client
|
var funcMap = template.FuncMap{
|
||||||
// such as the TLS, tcp and other parameters used to run the client.
|
"json": func(v interface{}) string {
|
||||||
func (cli *DockerCli) Initialize() error {
|
a, _ := json.Marshal(v)
|
||||||
if cli.init == nil {
|
return string(a)
|
||||||
return nil
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) Out() io.Writer {
|
||||||
|
return cli.out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) Err() io.Writer {
|
||||||
|
return cli.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cli *DockerCli) getMethod(args ...string) (func(...string) error, bool) {
|
||||||
|
camelArgs := make([]string, len(args))
|
||||||
|
for i, s := range args {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
||||||
}
|
}
|
||||||
return cli.init()
|
methodName := "Cmd" + strings.Join(camelArgs, "")
|
||||||
|
method := reflect.ValueOf(cli).MethodByName(methodName)
|
||||||
|
if !method.IsValid() {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return method.Interface().(func(...string) error), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cmd executes the specified command.
|
||||||
|
func (cli *DockerCli) Cmd(args ...string) error {
|
||||||
|
if len(args) > 1 {
|
||||||
|
method, exists := cli.getMethod(args[:2]...)
|
||||||
|
if exists {
|
||||||
|
return method(args[2:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
method, exists := cli.getMethod(args[0])
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("docker: '%s' is not a docker command.\nSee 'docker --help'.", args[0])
|
||||||
|
}
|
||||||
|
return method(args[1:]...)
|
||||||
|
}
|
||||||
|
return cli.CmdHelp()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subcmd is a subcommand of the main "docker" command.
|
||||||
|
// A subcommand represents an action that can be performed
|
||||||
|
// from the Docker command line client.
|
||||||
|
//
|
||||||
|
// To see all available subcommands, run "docker --help".
|
||||||
|
func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bool) *flag.FlagSet {
|
||||||
|
var errorHandling flag.ErrorHandling
|
||||||
|
if exitOnError {
|
||||||
|
errorHandling = flag.ExitOnError
|
||||||
|
} else {
|
||||||
|
errorHandling = flag.ContinueOnError
|
||||||
|
}
|
||||||
|
flags := flag.NewFlagSet(name, errorHandling)
|
||||||
|
if signature != "" {
|
||||||
|
signature = " " + signature
|
||||||
|
}
|
||||||
|
flags.Usage = func() {
|
||||||
|
flags.ShortUsage()
|
||||||
|
flags.PrintDefaults()
|
||||||
|
}
|
||||||
|
flags.ShortUsage = func() {
|
||||||
|
options := ""
|
||||||
|
if flags.FlagCountUndeprecated() > 0 {
|
||||||
|
options = " [OPTIONS]"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(flags.Out(), "\nUsage: docker %s%s%s\n\n%s\n", name, options, signature, description)
|
||||||
|
}
|
||||||
|
return flags
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckTtyInput checks if we are trying to attach to a container tty
|
// CheckTtyInput checks if we are trying to attach to a container tty
|
||||||
@@ -79,89 +145,59 @@ func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PsFormat returns the format string specified in the configuration.
|
|
||||||
// String contains columns and format specification, for example {{ID}\t{{Name}}.
|
|
||||||
func (cli *DockerCli) PsFormat() string {
|
|
||||||
return cli.configFile.PsFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
|
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
|
||||||
// The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config
|
// The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config
|
||||||
// is set the client scheme will be set to https.
|
// is set the client scheme will be set to https.
|
||||||
// The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035).
|
// The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035).
|
||||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientFlags) *DockerCli {
|
func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, addr string, tlsConfig *tls.Config) *DockerCli {
|
||||||
cli := &DockerCli{
|
var (
|
||||||
in: in,
|
inFd uintptr
|
||||||
out: out,
|
outFd uintptr
|
||||||
err: err,
|
isTerminalIn = false
|
||||||
keyFile: clientFlags.Common.TrustKey,
|
isTerminalOut = false
|
||||||
|
scheme = "http"
|
||||||
|
)
|
||||||
|
|
||||||
|
if tlsConfig != nil {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
if in != nil {
|
||||||
|
inFd, isTerminalIn = term.GetFdInfo(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.init = func() error {
|
if out != nil {
|
||||||
|
outFd, isTerminalOut = term.GetFdInfo(out)
|
||||||
clientFlags.PostParse()
|
|
||||||
|
|
||||||
hosts := clientFlags.Common.Hosts
|
|
||||||
|
|
||||||
switch len(hosts) {
|
|
||||||
case 0:
|
|
||||||
defaultHost := os.Getenv("DOCKER_HOST")
|
|
||||||
if defaultHost == "" {
|
|
||||||
defaultHost = opts.DefaultHost
|
|
||||||
}
|
|
||||||
defaultHost, err := opts.ValidateHost(defaultHost)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
hosts = []string{defaultHost}
|
|
||||||
case 1:
|
|
||||||
// only accept one host to talk to
|
|
||||||
default:
|
|
||||||
return errors.New("Please specify only one -H")
|
|
||||||
}
|
|
||||||
|
|
||||||
protoAddrParts := strings.SplitN(hosts[0], "://", 2)
|
|
||||||
cli.proto, cli.addr = protoAddrParts[0], protoAddrParts[1]
|
|
||||||
|
|
||||||
if cli.proto == "tcp" {
|
|
||||||
// error is checked in pkg/parsers already
|
|
||||||
parsed, _ := url.Parse("tcp://" + cli.addr)
|
|
||||||
cli.addr = parsed.Host
|
|
||||||
cli.basePath = parsed.Path
|
|
||||||
}
|
|
||||||
|
|
||||||
if clientFlags.Common.TLSOptions != nil {
|
|
||||||
cli.scheme = "https"
|
|
||||||
var e error
|
|
||||||
cli.tlsConfig, e = tlsconfig.Client(*clientFlags.Common.TLSOptions)
|
|
||||||
if e != nil {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cli.scheme = "http"
|
|
||||||
}
|
|
||||||
|
|
||||||
if cli.in != nil {
|
|
||||||
cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in)
|
|
||||||
}
|
|
||||||
if cli.out != nil {
|
|
||||||
cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The transport is created here for reuse during the client session.
|
|
||||||
cli.transport = &http.Transport{
|
|
||||||
TLSClientConfig: cli.tlsConfig,
|
|
||||||
}
|
|
||||||
sockets.ConfigureTCPTransport(cli.transport, cli.proto, cli.addr)
|
|
||||||
|
|
||||||
configFile, e := cliconfig.Load(cliconfig.ConfigDir())
|
|
||||||
if e != nil {
|
|
||||||
fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e)
|
|
||||||
}
|
|
||||||
cli.configFile = configFile
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cli
|
if err == nil {
|
||||||
|
err = out
|
||||||
|
}
|
||||||
|
|
||||||
|
// The transport is created here for reuse during the client session.
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
utils.ConfigureTCPTransport(tr, proto, addr)
|
||||||
|
|
||||||
|
configFile, e := cliconfig.Load(filepath.Join(homedir.Get(), ".docker"))
|
||||||
|
if e != nil {
|
||||||
|
fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DockerCli{
|
||||||
|
proto: proto,
|
||||||
|
addr: addr,
|
||||||
|
configFile: configFile,
|
||||||
|
in: in,
|
||||||
|
out: out,
|
||||||
|
err: err,
|
||||||
|
keyFile: keyFile,
|
||||||
|
inFd: inFd,
|
||||||
|
outFd: outFd,
|
||||||
|
isTerminalIn: isTerminalIn,
|
||||||
|
isTerminalOut: isTerminalOut,
|
||||||
|
tlsConfig: tlsConfig,
|
||||||
|
scheme: scheme,
|
||||||
|
transport: tr,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,3 +3,15 @@
|
|||||||
// Run "docker help SUBCOMMAND" or "docker SUBCOMMAND --help" to see more information on any Docker subcommand, including the full list of options supported for the subcommand.
|
// Run "docker help SUBCOMMAND" or "docker SUBCOMMAND --help" to see more information on any Docker subcommand, including the full list of options supported for the subcommand.
|
||||||
// See https://docs.docker.com/installation/ for instructions on installing Docker.
|
// See https://docs.docker.com/installation/ for instructions on installing Docker.
|
||||||
package client
|
package client
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// An StatusError reports an unsuccessful exit by a command.
|
||||||
|
type StatusError struct {
|
||||||
|
Status string
|
||||||
|
StatusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e StatusError) Error() string {
|
||||||
|
return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode)
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
@@ -18,7 +17,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
|
// Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
|
||||||
func (cli *DockerCli) CmdCommit(args ...string) error {
|
func (cli *DockerCli) CmdCommit(args ...string) error {
|
||||||
cmd := Cli.Subcmd("commit", []string{"CONTAINER [REPOSITORY[:TAG]]"}, "Create a new image from a container's changes", true)
|
cmd := cli.Subcmd("commit", "CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes", true)
|
||||||
flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit")
|
flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit")
|
||||||
flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
|
flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
|
||||||
flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
|
flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
|
||||||
@@ -28,7 +27,6 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
|
|||||||
flConfig := cmd.String([]string{"#run", "#-run"}, "", "This option is deprecated and will be removed in a future version in favor of inline Dockerfile-compatible commands")
|
flConfig := cmd.String([]string{"#run", "#-run"}, "", "This option is deprecated and will be removed in a future version in favor of inline Dockerfile-compatible commands")
|
||||||
cmd.Require(flag.Max, 2)
|
cmd.Require(flag.Max, 2)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -68,14 +66,12 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
serverResp, err := cli.call("POST", "/commit?"+v.Encode(), config, nil)
|
stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
if err := json.NewDecoder(stream).Decode(&response); err != nil {
|
||||||
|
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+24
-292
@@ -1,325 +1,57 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type copyDirection int
|
// CmdCp copies files/folders from a path on the container to a directory on the host running the command.
|
||||||
|
|
||||||
const (
|
|
||||||
fromContainer copyDirection = (1 << iota)
|
|
||||||
toContainer
|
|
||||||
acrossContainers = fromContainer | toContainer
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdCp copies files/folders to or from a path in a container.
|
|
||||||
//
|
//
|
||||||
// When copying from a container, if LOCALPATH is '-' the data is written as a
|
// If HOSTDIR is '-', the data is written as a tar file to STDOUT.
|
||||||
// tar archive file to STDOUT.
|
|
||||||
//
|
//
|
||||||
// When copying to a container, if LOCALPATH is '-' the data is read as a tar
|
// Usage: docker cp CONTAINER:PATH HOSTDIR
|
||||||
// archive file from STDIN, and the destination CONTAINER:PATH, must specify
|
|
||||||
// a directory.
|
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
// docker cp CONTAINER:PATH LOCALPATH|-
|
|
||||||
// docker cp LOCALPATH|- CONTAINER:PATH
|
|
||||||
func (cli *DockerCli) CmdCp(args ...string) error {
|
func (cli *DockerCli) CmdCp(args ...string) error {
|
||||||
cmd := Cli.Subcmd(
|
cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTDIR|-", "Copy files/folders from a PATH on the container to a HOSTDIR on the host\nrunning the command. Use '-' to write the data as a tar file to STDOUT.", true)
|
||||||
"cp",
|
|
||||||
[]string{"CONTAINER:PATH LOCALPATH|-", "LOCALPATH|- CONTAINER:PATH"},
|
|
||||||
strings.Join([]string{
|
|
||||||
"Copy files/folders between a container and your host.\n",
|
|
||||||
"Use '-' as the source to read a tar archive from stdin\n",
|
|
||||||
"and extract it to a directory destination in a container.\n",
|
|
||||||
"Use '-' as the destination to stream a tar archive of a\n",
|
|
||||||
"container source to stdout.",
|
|
||||||
}, ""),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
|
|
||||||
cmd.Require(flag.Exact, 2)
|
cmd.Require(flag.Exact, 2)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
if cmd.Arg(0) == "" {
|
// deal with path name with `:`
|
||||||
return fmt.Errorf("source can not be empty")
|
info := strings.SplitN(cmd.Arg(0), ":", 2)
|
||||||
}
|
|
||||||
if cmd.Arg(1) == "" {
|
if len(info) != 2 {
|
||||||
return fmt.Errorf("destination can not be empty")
|
return fmt.Errorf("Error: Path not specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
srcContainer, srcPath := splitCpArg(cmd.Arg(0))
|
cfg := &types.CopyConfig{
|
||||||
dstContainer, dstPath := splitCpArg(cmd.Arg(1))
|
Resource: info[1],
|
||||||
|
|
||||||
var direction copyDirection
|
|
||||||
if srcContainer != "" {
|
|
||||||
direction |= fromContainer
|
|
||||||
}
|
}
|
||||||
if dstContainer != "" {
|
stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", cfg, nil)
|
||||||
direction |= toContainer
|
if stream != nil {
|
||||||
|
defer stream.Close()
|
||||||
}
|
}
|
||||||
|
if statusCode == 404 {
|
||||||
switch direction {
|
return fmt.Errorf("No such container: %v", info[0])
|
||||||
case fromContainer:
|
|
||||||
return cli.copyFromContainer(srcContainer, srcPath, dstPath)
|
|
||||||
case toContainer:
|
|
||||||
return cli.copyToContainer(srcPath, dstContainer, dstPath)
|
|
||||||
case acrossContainers:
|
|
||||||
// Copying between containers isn't supported.
|
|
||||||
return fmt.Errorf("copying between containers is not supported")
|
|
||||||
default:
|
|
||||||
// User didn't specify any container.
|
|
||||||
return fmt.Errorf("must specify at least one container source")
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be
|
|
||||||
// in a valid LOCALPATH, like `file:name.txt`. We can resolve this ambiguity by
|
|
||||||
// requiring a LOCALPATH with a `:` to be made explicit with a relative or
|
|
||||||
// absolute path:
|
|
||||||
// `/path/to/file:name.txt` or `./file:name.txt`
|
|
||||||
//
|
|
||||||
// This is apparently how `scp` handles this as well:
|
|
||||||
// http://www.cyberciti.biz/faq/rsync-scp-file-name-with-colon-punctuation-in-it/
|
|
||||||
//
|
|
||||||
// We can't simply check for a filepath separator because container names may
|
|
||||||
// have a separator, e.g., "host0/cname1" if container is in a Docker cluster,
|
|
||||||
// so we have to check for a `/` or `.` prefix. Also, in the case of a Windows
|
|
||||||
// client, a `:` could be part of an absolute Windows path, in which case it
|
|
||||||
// is immediately proceeded by a backslash.
|
|
||||||
func splitCpArg(arg string) (container, path string) {
|
|
||||||
if system.IsAbs(arg) {
|
|
||||||
// Explicit local absolute path, e.g., `C:\foo` or `/foo`.
|
|
||||||
return "", arg
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.SplitN(arg, ":", 2)
|
|
||||||
|
|
||||||
if len(parts) == 1 || strings.HasPrefix(parts[0], ".") {
|
|
||||||
// Either there's no `:` in the arg
|
|
||||||
// OR it's an explicit local relative path like `./file:name.txt`.
|
|
||||||
return "", arg
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts[0], parts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) statContainerPath(containerName, path string) (types.ContainerPathStat, error) {
|
|
||||||
var stat types.ContainerPathStat
|
|
||||||
|
|
||||||
query := make(url.Values, 1)
|
|
||||||
query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
|
|
||||||
|
|
||||||
urlStr := fmt.Sprintf("/containers/%s/archive?%s", containerName, query.Encode())
|
|
||||||
|
|
||||||
response, err := cli.call("HEAD", urlStr, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return stat, err
|
|
||||||
}
|
|
||||||
defer response.body.Close()
|
|
||||||
|
|
||||||
if response.statusCode != http.StatusOK {
|
|
||||||
return stat, fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return getContainerPathStatFromHeader(response.header)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) {
|
|
||||||
var stat types.ContainerPathStat
|
|
||||||
|
|
||||||
encodedStat := header.Get("X-Docker-Container-Path-Stat")
|
|
||||||
statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat))
|
|
||||||
|
|
||||||
err := json.NewDecoder(statDecoder).Decode(&stat)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("unable to decode container path stat header: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return stat, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveLocalPath(localPath string) (absPath string, err error) {
|
|
||||||
if absPath, err = filepath.Abs(localPath); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return archive.PreserveTrailingDotOrSeparator(absPath, localPath), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) copyFromContainer(srcContainer, srcPath, dstPath string) (err error) {
|
|
||||||
if dstPath != "-" {
|
|
||||||
// Get an absolute destination path.
|
|
||||||
dstPath, err = resolveLocalPath(dstPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query := make(url.Values, 1)
|
|
||||||
query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
|
|
||||||
|
|
||||||
urlStr := fmt.Sprintf("/containers/%s/archive?%s", srcContainer, query.Encode())
|
|
||||||
|
|
||||||
response, err := cli.call("GET", urlStr, nil, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer response.body.Close()
|
|
||||||
|
|
||||||
if response.statusCode != http.StatusOK {
|
hostPath := cmd.Arg(1)
|
||||||
return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
|
if statusCode == 200 {
|
||||||
}
|
if hostPath == "-" {
|
||||||
|
_, err = io.Copy(cli.out, stream)
|
||||||
if dstPath == "-" {
|
} else {
|
||||||
// Send the response to STDOUT.
|
err = archive.Untar(stream, hostPath, &archive.TarOptions{NoLchown: true})
|
||||||
_, err = io.Copy(os.Stdout, response.body)
|
}
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// In order to get the copy behavior right, we need to know information
|
|
||||||
// about both the source and the destination. The response headers include
|
|
||||||
// stat info about the source that we can use in deciding exactly how to
|
|
||||||
// copy it locally. Along with the stat info about the local destination,
|
|
||||||
// we have everything we need to handle the multiple possibilities there
|
|
||||||
// can be when copying a file/dir from one location to another file/dir.
|
|
||||||
stat, err := getContainerPathStatFromHeader(response.header)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to get resource stat from response: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare source copy info.
|
|
||||||
srcInfo := archive.CopyInfo{
|
|
||||||
Path: srcPath,
|
|
||||||
Exists: true,
|
|
||||||
IsDir: stat.Mode.IsDir(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// See comments in the implementation of `archive.CopyTo` for exactly what
|
|
||||||
// goes into deciding how and whether the source archive needs to be
|
|
||||||
// altered for the correct copy behavior.
|
|
||||||
return archive.CopyTo(response.body, srcInfo, dstPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) copyToContainer(srcPath, dstContainer, dstPath string) (err error) {
|
|
||||||
if srcPath != "-" {
|
|
||||||
// Get an absolute source path.
|
|
||||||
srcPath, err = resolveLocalPath(srcPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In order to get the copy behavior right, we need to know information
|
|
||||||
// about both the source and destination. The API is a simple tar
|
|
||||||
// archive/extract API but we can use the stat info header about the
|
|
||||||
// destination to be more informed about exactly what the destination is.
|
|
||||||
|
|
||||||
// Prepare destination copy info by stat-ing the container path.
|
|
||||||
dstInfo := archive.CopyInfo{Path: dstPath}
|
|
||||||
dstStat, err := cli.statContainerPath(dstContainer, dstPath)
|
|
||||||
|
|
||||||
// If the destination is a symbolic link, we should evaluate it.
|
|
||||||
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
|
|
||||||
linkTarget := dstStat.LinkTarget
|
|
||||||
if !system.IsAbs(linkTarget) {
|
|
||||||
// Join with the parent directory.
|
|
||||||
dstParent, _ := archive.SplitPathDirEntry(dstPath)
|
|
||||||
linkTarget = filepath.Join(dstParent, linkTarget)
|
|
||||||
}
|
|
||||||
|
|
||||||
dstInfo.Path = linkTarget
|
|
||||||
dstStat, err = cli.statContainerPath(dstContainer, linkTarget)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore any error and assume that the parent directory of the destination
|
|
||||||
// path exists, in which case the copy may still succeed. If there is any
|
|
||||||
// type of conflict (e.g., non-directory overwriting an existing directory
|
|
||||||
// or vice versia) the extraction will fail. If the destination simply did
|
|
||||||
// not exist, but the parent directory does, the extraction will still
|
|
||||||
// succeed.
|
|
||||||
if err == nil {
|
|
||||||
dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
content io.Reader
|
|
||||||
resolvedDstPath string
|
|
||||||
)
|
|
||||||
|
|
||||||
if srcPath == "-" {
|
|
||||||
// Use STDIN.
|
|
||||||
content = os.Stdin
|
|
||||||
resolvedDstPath = dstInfo.Path
|
|
||||||
if !dstInfo.IsDir {
|
|
||||||
return fmt.Errorf("destination %q must be a directory", fmt.Sprintf("%s:%s", dstContainer, dstPath))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Prepare source copy info.
|
|
||||||
srcInfo, err := archive.CopyInfoSourcePath(srcPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
srcArchive, err := archive.TarResource(srcInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer srcArchive.Close()
|
|
||||||
|
|
||||||
// With the stat info about the local source as well as the
|
|
||||||
// destination, we have enough information to know whether we need to
|
|
||||||
// alter the archive that we upload so that when the server extracts
|
|
||||||
// it to the specified directory in the container we get the disired
|
|
||||||
// copy behavior.
|
|
||||||
|
|
||||||
// See comments in the implementation of `archive.PrepareArchiveCopy`
|
|
||||||
// for exactly what goes into deciding how and whether the source
|
|
||||||
// archive needs to be altered for the correct copy behavior when it is
|
|
||||||
// extracted. This function also infers from the source and destination
|
|
||||||
// info which directory to extract to, which may be the parent of the
|
|
||||||
// destination that the user specified.
|
|
||||||
dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, dstInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer preparedArchive.Close()
|
|
||||||
|
|
||||||
resolvedDstPath = dstDir
|
|
||||||
content = preparedArchive
|
|
||||||
}
|
|
||||||
|
|
||||||
query := make(url.Values, 2)
|
|
||||||
query.Set("path", filepath.ToSlash(resolvedDstPath)) // Normalize the paths used in the API.
|
|
||||||
// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
|
|
||||||
query.Set("noOverwriteDirNonDir", "true")
|
|
||||||
|
|
||||||
urlStr := fmt.Sprintf("/containers/%s/archive?%s", dstContainer, query.Encode())
|
|
||||||
|
|
||||||
response, err := cli.stream("PUT", urlStr, &streamOpts{in: content})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.body.Close()
|
|
||||||
|
|
||||||
if response.statusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-38
@@ -10,11 +10,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/graph/tags"
|
"github.com/docker/docker/graph/tags"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (cli *DockerCli) pullImage(image string) error {
|
func (cli *DockerCli) pullImage(image string) error {
|
||||||
@@ -26,7 +26,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
|||||||
repos, tag := parsers.ParseRepositoryTag(image)
|
repos, tag := parsers.ParseRepositoryTag(image)
|
||||||
// pull only the image tagged 'latest' if no tag was specified
|
// pull only the image tagged 'latest' if no tag was specified
|
||||||
if tag == "" {
|
if tag == "" {
|
||||||
tag = tags.DefaultTag
|
tag = tags.DEFAULTTAG
|
||||||
}
|
}
|
||||||
v.Set("fromImage", repos)
|
v.Set("fromImage", repos)
|
||||||
v.Set("tag", tag)
|
v.Set("tag", tag)
|
||||||
@@ -52,7 +52,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
|||||||
out: out,
|
out: out,
|
||||||
headers: map[string][]string{"X-Registry-Auth": registryAuthHeader},
|
headers: map[string][]string{"X-Registry-Auth": registryAuthHeader},
|
||||||
}
|
}
|
||||||
if _, err := cli.stream("POST", "/images/create?"+v.Encode(), sopts); err != nil {
|
if err := cli.stream("POST", "/images/create?"+v.Encode(), sopts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -94,54 +94,30 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
|
|||||||
defer containerIDFile.Close()
|
defer containerIDFile.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, tag := parsers.ParseRepositoryTag(config.Image)
|
|
||||||
if tag == "" {
|
|
||||||
tag = tags.DefaultTag
|
|
||||||
}
|
|
||||||
|
|
||||||
ref := registry.ParseReference(tag)
|
|
||||||
var trustedRef registry.Reference
|
|
||||||
|
|
||||||
if isTrusted() && !ref.HasDigest() {
|
|
||||||
var err error
|
|
||||||
trustedRef, err = cli.trustedReference(repo, ref)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.Image = trustedRef.ImageName(repo)
|
|
||||||
}
|
|
||||||
|
|
||||||
//create the container
|
//create the container
|
||||||
serverResp, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil)
|
stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil)
|
||||||
//if image not found try to pull it
|
//if image not found try to pull it
|
||||||
if serverResp.statusCode == 404 && strings.Contains(err.Error(), config.Image) {
|
if statusCode == 404 && strings.Contains(err.Error(), config.Image) {
|
||||||
fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", ref.ImageName(repo))
|
repo, tag := parsers.ParseRepositoryTag(config.Image)
|
||||||
|
if tag == "" {
|
||||||
|
tag = tags.DEFAULTTAG
|
||||||
|
}
|
||||||
|
fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", utils.ImageReference(repo, tag))
|
||||||
|
|
||||||
// we don't want to write to stdout anything apart from container.ID
|
// we don't want to write to stdout anything apart from container.ID
|
||||||
if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil {
|
if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if trustedRef != nil && !ref.HasDigest() {
|
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(repo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := cli.tagTrusted(repoInfo, trustedRef, ref); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Retry
|
// Retry
|
||||||
if serverResp, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil); err != nil {
|
if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
var response types.ContainerCreateResponse
|
var response types.ContainerCreateResponse
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
|
if err := json.NewDecoder(stream).Decode(&response); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, warning := range response.Warnings {
|
for _, warning := range response.Warnings {
|
||||||
@@ -159,8 +135,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
|
|||||||
//
|
//
|
||||||
// Usage: docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
|
// Usage: docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
|
||||||
func (cli *DockerCli) CmdCreate(args ...string) error {
|
func (cli *DockerCli) CmdCreate(args ...string) error {
|
||||||
cmd := Cli.Subcmd("create", []string{"IMAGE [COMMAND] [ARG...]"}, "Create a new container", true)
|
cmd := cli.Subcmd("create", "IMAGE [COMMAND] [ARG...]", "Create a new container", true)
|
||||||
addTrustedFlags(cmd, true)
|
|
||||||
|
|
||||||
// These are flags not stored in Config/HostConfig
|
// These are flags not stored in Config/HostConfig
|
||||||
var (
|
var (
|
||||||
|
|||||||
+3
-7
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
@@ -18,24 +17,21 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker diff CONTAINER
|
// Usage: docker diff CONTAINER
|
||||||
func (cli *DockerCli) CmdDiff(args ...string) error {
|
func (cli *DockerCli) CmdDiff(args ...string) error {
|
||||||
cmd := Cli.Subcmd("diff", []string{"CONTAINER"}, "Inspect changes on a container's filesystem", true)
|
cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem", true)
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
if cmd.Arg(0) == "" {
|
if cmd.Arg(0) == "" {
|
||||||
return fmt.Errorf("Container name cannot be empty")
|
return fmt.Errorf("Container name cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, nil)
|
rdr, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
changes := []types.ContainerChange{}
|
changes := []types.ContainerChange{}
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&changes); err != nil {
|
if err := json.NewDecoder(rdr).Decode(&changes); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers/filters"
|
"github.com/docker/docker/pkg/parsers/filters"
|
||||||
@@ -15,7 +13,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker events [OPTIONS]
|
// Usage: docker events [OPTIONS]
|
||||||
func (cli *DockerCli) CmdEvents(args ...string) error {
|
func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||||
cmd := Cli.Subcmd("events", nil, "Get real time events from the server", true)
|
cmd := cli.Subcmd("events", "", "Get real time events from the server", true)
|
||||||
since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
|
since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
|
||||||
until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
|
until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
|
||||||
flFilter := opts.NewListOpts(nil)
|
flFilter := opts.NewListOpts(nil)
|
||||||
@@ -38,12 +36,11 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ref := time.Now()
|
|
||||||
if *since != "" {
|
if *since != "" {
|
||||||
v.Set("since", timeutils.GetTimestamp(*since, ref))
|
v.Set("since", timeutils.GetTimestamp(*since))
|
||||||
}
|
}
|
||||||
if *until != "" {
|
if *until != "" {
|
||||||
v.Set("until", timeutils.GetTimestamp(*until, ref))
|
v.Set("until", timeutils.GetTimestamp(*until))
|
||||||
}
|
}
|
||||||
if len(eventFilterArgs) > 0 {
|
if len(eventFilterArgs) > 0 {
|
||||||
filterJSON, err := filters.ToParam(eventFilterArgs)
|
filterJSON, err := filters.ToParam(eventFilterArgs)
|
||||||
@@ -56,7 +53,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
|||||||
rawTerminal: true,
|
rawTerminal: true,
|
||||||
out: cli.out,
|
out: cli.out,
|
||||||
}
|
}
|
||||||
if _, err := cli.stream("GET", "/events?"+v.Encode(), sopts); err != nil {
|
if err := cli.stream("GET", "/events?"+v.Encode(), sopts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+5
-8
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/promise"
|
"github.com/docker/docker/pkg/promise"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
)
|
)
|
||||||
@@ -16,23 +15,21 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
|
// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
|
||||||
func (cli *DockerCli) CmdExec(args ...string) error {
|
func (cli *DockerCli) CmdExec(args ...string) error {
|
||||||
cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, "Run a command in a running container", true)
|
cmd := cli.Subcmd("exec", "CONTAINER COMMAND [ARG...]", "Run a command in a running container", true)
|
||||||
|
|
||||||
execConfig, err := runconfig.ParseExec(cmd, args)
|
execConfig, err := runconfig.ParseExec(cmd, args)
|
||||||
// just in case the ParseExec does not exit
|
// just in case the ParseExec does not exit
|
||||||
if execConfig.Container == "" || err != nil {
|
if execConfig.Container == "" || err != nil {
|
||||||
return Cli.StatusError{StatusCode: 1}
|
return StatusError{StatusCode: 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
serverResp, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil)
|
stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
var response types.ContainerExecCreateResponse
|
var response types.ContainerExecCreateResponse
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
|
if err := json.NewDecoder(stream).Decode(&response); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +124,7 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
return Cli.StatusError{StatusCode: status}
|
return StatusError{StatusCode: status}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,14 +14,14 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker export [OPTIONS] CONTAINER
|
// Usage: docker export [OPTIONS] CONTAINER
|
||||||
func (cli *DockerCli) CmdExport(args ...string) error {
|
func (cli *DockerCli) CmdExport(args ...string) error {
|
||||||
cmd := Cli.Subcmd("export", []string{"CONTAINER"}, "Export the contents of a container's filesystem as a tar archive", true)
|
cmd := cli.Subcmd("export", "CONTAINER", "Export a filesystem as a tar archive (streamed to STDOUT by default)", true)
|
||||||
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
|
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
output = cli.out
|
output io.Writer = cli.out
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if *outfile != "" {
|
if *outfile != "" {
|
||||||
@@ -38,7 +38,7 @@ func (cli *DockerCli) CmdExport(args ...string) error {
|
|||||||
rawTerminal: true,
|
rawTerminal: true,
|
||||||
out: output,
|
out: output,
|
||||||
}
|
}
|
||||||
if _, err := cli.stream("GET", "/containers/"+image+"/export", sopts); err != nil {
|
if err := cli.stream("GET", "/containers/"+image+"/export", sopts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CmdHelp displays information on a Docker command.
|
||||||
|
//
|
||||||
|
// If more than one command is specified, information is only shown for the first command.
|
||||||
|
//
|
||||||
|
// Usage: docker help COMMAND or docker COMMAND --help
|
||||||
|
func (cli *DockerCli) CmdHelp(args ...string) error {
|
||||||
|
if len(args) > 1 {
|
||||||
|
method, exists := cli.getMethod(args[:2]...)
|
||||||
|
if exists {
|
||||||
|
method("--help")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
method, exists := cli.getMethod(args[0])
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("docker: '%s' is not a docker command. See 'docker --help'.", args[0])
|
||||||
|
}
|
||||||
|
method("--help")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
flag.Usage()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -138,18 +138,18 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), params)
|
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
|
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
|
||||||
// then the user can't change OUR headers
|
// then the user can't change OUR headers
|
||||||
for k, v := range cli.configFile.HTTPHeaders {
|
for k, v := range cli.configFile.HttpHeaders {
|
||||||
req.Header.Set(k, v)
|
req.Header.Set(k, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
|
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
||||||
req.Header.Set("Content-Type", "text/plain")
|
req.Header.Set("Content-Type", "text/plain")
|
||||||
req.Header.Set("Connection", "Upgrade")
|
req.Header.Set("Connection", "Upgrade")
|
||||||
req.Header.Set("Upgrade", "tcp")
|
req.Header.Set("Upgrade", "tcp")
|
||||||
@@ -167,7 +167,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "connection refused") {
|
if strings.Contains(err.Error(), "connection refused") {
|
||||||
return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
|
return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
"github.com/docker/docker/pkg/stringutils"
|
"github.com/docker/docker/pkg/stringutils"
|
||||||
@@ -18,23 +17,20 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker history [OPTIONS] IMAGE
|
// Usage: docker history [OPTIONS] IMAGE
|
||||||
func (cli *DockerCli) CmdHistory(args ...string) error {
|
func (cli *DockerCli) CmdHistory(args ...string) error {
|
||||||
cmd := Cli.Subcmd("history", []string{"IMAGE"}, "Show the history of an image", true)
|
cmd := cli.Subcmd("history", "IMAGE", "Show the history of an image", true)
|
||||||
human := cmd.Bool([]string{"H", "-human"}, true, "Print sizes and dates in human readable format")
|
human := cmd.Bool([]string{"H", "-human"}, true, "Print sizes and dates in human readable format")
|
||||||
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
||||||
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
serverResp, err := cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, nil)
|
rdr, _, err := cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
history := []types.ImageHistory{}
|
history := []types.ImageHistory{}
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&history); err != nil {
|
if err := json.NewDecoder(rdr).Decode(&history); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
@@ -22,7 +21,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker images [OPTIONS] [REPOSITORY]
|
// Usage: docker images [OPTIONS] [REPOSITORY]
|
||||||
func (cli *DockerCli) CmdImages(args ...string) error {
|
func (cli *DockerCli) CmdImages(args ...string) error {
|
||||||
cmd := Cli.Subcmd("images", []string{"[REPOSITORY[:TAG]]"}, "List images", true)
|
cmd := cli.Subcmd("images", "[REPOSITORY]", "List images", true)
|
||||||
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
||||||
all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)")
|
all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)")
|
||||||
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
||||||
@@ -31,7 +30,6 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
|||||||
flFilter := opts.NewListOpts(nil)
|
flFilter := opts.NewListOpts(nil)
|
||||||
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
|
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
|
||||||
cmd.Require(flag.Max, 1)
|
cmd.Require(flag.Max, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
// Consolidate all filter flags, and sanity check them early.
|
// Consolidate all filter flags, and sanity check them early.
|
||||||
@@ -63,15 +61,13 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
|||||||
v.Set("all", "1")
|
v.Set("all", "1")
|
||||||
}
|
}
|
||||||
|
|
||||||
serverResp, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil)
|
rdr, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
images := []types.Image{}
|
images := []types.Image{}
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&images); err != nil {
|
if err := json.NewDecoder(rdr).Decode(&images); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-20
@@ -4,26 +4,22 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/pkg/urlutil"
|
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdImport creates an empty filesystem image, imports the contents of the tarball into the image, and optionally tags the image.
|
// CmdImport creates an empty filesystem image, imports the contents of the tarball into the image, and optionally tags the image.
|
||||||
//
|
//
|
||||||
// The URL argument is the address of a tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) file or a path to local file relative to docker client. If the URL is '-', then the tar file is read from STDIN.
|
// The URL argument is the address of a tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) file. If the URL is '-', then the tar file is read from STDIN.
|
||||||
//
|
//
|
||||||
// Usage: docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]
|
// Usage: docker import [OPTIONS] URL [REPOSITORY[:TAG]]
|
||||||
func (cli *DockerCli) CmdImport(args ...string) error {
|
func (cli *DockerCli) CmdImport(args ...string) error {
|
||||||
cmd := Cli.Subcmd("import", []string{"file|URL|- [REPOSITORY[:TAG]]"}, "Create an empty filesystem image and import the contents of the\ntarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then\noptionally tag it.", true)
|
cmd := cli.Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create an empty filesystem image and import the contents of the\ntarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then\noptionally tag it.", true)
|
||||||
flChanges := opts.NewListOpts(nil)
|
flChanges := opts.NewListOpts(nil)
|
||||||
cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
|
cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
|
||||||
message := cmd.String([]string{"m", "-message"}, "", "Set commit message for imported image")
|
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
@@ -36,12 +32,11 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
|||||||
|
|
||||||
v.Set("fromSrc", src)
|
v.Set("fromSrc", src)
|
||||||
v.Set("repo", repository)
|
v.Set("repo", repository)
|
||||||
v.Set("message", *message)
|
|
||||||
for _, change := range flChanges.GetAll() {
|
for _, change := range flChanges.GetAll() {
|
||||||
v.Add("changes", change)
|
v.Add("changes", change)
|
||||||
}
|
}
|
||||||
if cmd.NArg() == 3 {
|
if cmd.NArg() == 3 {
|
||||||
fmt.Fprintf(cli.err, "[DEPRECATED] The format 'file|URL|- [REPOSITORY [TAG]]' has been deprecated. Please use file|URL|- [REPOSITORY[:TAG]]\n")
|
fmt.Fprintf(cli.err, "[DEPRECATED] The format 'URL|- [REPOSITORY [TAG]]' has been deprecated. Please use URL|- [REPOSITORY[:TAG]]\n")
|
||||||
v.Set("tag", cmd.Arg(2))
|
v.Set("tag", cmd.Arg(2))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,15 +52,6 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
|||||||
|
|
||||||
if src == "-" {
|
if src == "-" {
|
||||||
in = cli.in
|
in = cli.in
|
||||||
} else if !urlutil.IsURL(src) {
|
|
||||||
v.Set("fromSrc", "-")
|
|
||||||
file, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
in = file
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sopts := &streamOpts{
|
sopts := &streamOpts{
|
||||||
@@ -74,6 +60,5 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
|||||||
out: cli.out,
|
out: cli.out,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := cli.stream("POST", "/images/create?"+v.Encode(), sopts)
|
return cli.stream("POST", "/images/create?"+v.Encode(), sopts)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-30
@@ -5,8 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/pkg/httputils"
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/units"
|
"github.com/docker/docker/pkg/units"
|
||||||
@@ -16,20 +14,17 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker info
|
// Usage: docker info
|
||||||
func (cli *DockerCli) CmdInfo(args ...string) error {
|
func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||||
cmd := Cli.Subcmd("info", nil, "Display system-wide information", true)
|
cmd := cli.Subcmd("info", "", "Display system-wide information", true)
|
||||||
cmd.Require(flag.Exact, 0)
|
cmd.Require(flag.Exact, 0)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
serverResp, err := cli.call("GET", "/info", nil, nil)
|
rdr, _, err := cli.call("GET", "/info", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
info := &types.Info{}
|
info := &types.Info{}
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(info); err != nil {
|
if err := json.NewDecoder(rdr).Decode(info); err != nil {
|
||||||
return fmt.Errorf("Error reading remote info: %v", err)
|
return fmt.Errorf("Error reading remote info: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,8 +56,8 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||||||
fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", info.DockerRootDir)
|
fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", info.DockerRootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
ioutils.FprintfIfNotEmpty(cli.out, "Http Proxy: %s\n", info.HTTPProxy)
|
ioutils.FprintfIfNotEmpty(cli.out, "Http Proxy: %s\n", info.HttpProxy)
|
||||||
ioutils.FprintfIfNotEmpty(cli.out, "Https Proxy: %s\n", info.HTTPSProxy)
|
ioutils.FprintfIfNotEmpty(cli.out, "Https Proxy: %s\n", info.HttpsProxy)
|
||||||
ioutils.FprintfIfNotEmpty(cli.out, "No Proxy: %s\n", info.NoProxy)
|
ioutils.FprintfIfNotEmpty(cli.out, "No Proxy: %s\n", info.NoProxy)
|
||||||
|
|
||||||
if info.IndexServerAddress != "" {
|
if info.IndexServerAddress != "" {
|
||||||
@@ -72,27 +67,15 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||||||
fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress)
|
fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Only output these warnings if the server supports these features
|
if !info.MemoryLimit {
|
||||||
if h, err := httputils.ParseServerHeader(serverResp.header.Get("Server")); err == nil {
|
fmt.Fprintf(cli.err, "WARNING: No memory limit support\n")
|
||||||
if h.OS != "windows" {
|
}
|
||||||
if !info.MemoryLimit {
|
if !info.SwapLimit {
|
||||||
fmt.Fprintf(cli.err, "WARNING: No memory limit support\n")
|
fmt.Fprintf(cli.err, "WARNING: No swap limit support\n")
|
||||||
}
|
}
|
||||||
if !info.SwapLimit {
|
if !info.IPv4Forwarding {
|
||||||
fmt.Fprintf(cli.err, "WARNING: No swap limit support\n")
|
fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n")
|
||||||
}
|
|
||||||
if !info.IPv4Forwarding {
|
|
||||||
fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled\n")
|
|
||||||
}
|
|
||||||
if !info.BridgeNfIptables {
|
|
||||||
fmt.Fprintf(cli.err, "WARNING: bridge-nf-call-iptables is disabled\n")
|
|
||||||
}
|
|
||||||
if !info.BridgeNfIP6tables {
|
|
||||||
fmt.Fprintf(cli.err, "WARNING: bridge-nf-call-ip6tables is disabled\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.Labels != nil {
|
if info.Labels != nil {
|
||||||
fmt.Fprintln(cli.out, "Labels:")
|
fmt.Fprintln(cli.out, "Labels:")
|
||||||
for _, attribute := range info.Labels {
|
for _, attribute := range info.Labels {
|
||||||
|
|||||||
+8
-44
@@ -9,84 +9,51 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
var funcMap = template.FuncMap{
|
|
||||||
"json": func(v interface{}) string {
|
|
||||||
a, _ := json.Marshal(v)
|
|
||||||
return string(a)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdInspect displays low-level information on one or more containers or images.
|
// CmdInspect displays low-level information on one or more containers or images.
|
||||||
//
|
//
|
||||||
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
|
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
|
||||||
func (cli *DockerCli) CmdInspect(args ...string) error {
|
func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||||
cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE [CONTAINER|IMAGE...]"}, "Return low-level information on a container or image", true)
|
cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container or image", true)
|
||||||
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
|
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
|
||||||
inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image or container)")
|
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var tmpl *template.Template
|
var tmpl *template.Template
|
||||||
var err error
|
|
||||||
var obj []byte
|
|
||||||
|
|
||||||
if *tmplStr != "" {
|
if *tmplStr != "" {
|
||||||
|
var err error
|
||||||
if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
|
if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
|
||||||
return Cli.StatusError{StatusCode: 64,
|
return StatusError{StatusCode: 64,
|
||||||
Status: "Template parsing error: " + err.Error()}
|
Status: "Template parsing error: " + err.Error()}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if *inspectType != "" && *inspectType != "container" && *inspectType != "image" {
|
|
||||||
return fmt.Errorf("%q is not a valid value for --type", *inspectType)
|
|
||||||
}
|
|
||||||
|
|
||||||
indented := new(bytes.Buffer)
|
indented := new(bytes.Buffer)
|
||||||
indented.WriteString("[\n")
|
indented.WriteString("[\n")
|
||||||
status := 0
|
status := 0
|
||||||
isImage := false
|
isImage := false
|
||||||
|
|
||||||
for _, name := range cmd.Args() {
|
for _, name := range cmd.Args() {
|
||||||
|
obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, nil))
|
||||||
if *inspectType == "" || *inspectType == "container" {
|
if err != nil {
|
||||||
obj, _, err = readBody(cli.call("GET", "/containers/"+name+"/json", nil, nil))
|
|
||||||
if err != nil && *inspectType == "container" {
|
|
||||||
if strings.Contains(err.Error(), "No such") {
|
|
||||||
fmt.Fprintf(cli.err, "Error: No such container: %s\n", name)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(cli.err, "%s", err)
|
|
||||||
}
|
|
||||||
status = 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj == nil && (*inspectType == "" || *inspectType == "image") {
|
|
||||||
obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil))
|
obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil))
|
||||||
isImage = true
|
isImage = true
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "No such") {
|
if strings.Contains(err.Error(), "No such") {
|
||||||
if *inspectType == "" {
|
fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name)
|
||||||
fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(cli.err, "Error: No such image: %s\n", name)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(cli.err, "%s", err)
|
fmt.Fprintf(cli.err, "%s", err)
|
||||||
}
|
}
|
||||||
status = 1
|
status = 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if tmpl == nil {
|
if tmpl == nil {
|
||||||
if err := json.Indent(indented, obj, "", " "); err != nil {
|
if err = json.Indent(indented, obj, "", " "); err != nil {
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
fmt.Fprintf(cli.err, "%s\n", err)
|
||||||
status = 1
|
status = 1
|
||||||
continue
|
continue
|
||||||
@@ -142,16 +109,13 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
|||||||
indented.WriteString("]\n")
|
indented.WriteString("]\n")
|
||||||
|
|
||||||
if tmpl == nil {
|
if tmpl == nil {
|
||||||
// Note that we will always write "[]" when "-f" isn't specified,
|
|
||||||
// to make sure the output would always be array, see
|
|
||||||
// https://github.com/docker/docker/pull/9500#issuecomment-65846734
|
|
||||||
if _, err := io.Copy(cli.out, indented); err != nil {
|
if _, err := io.Copy(cli.out, indented); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
return Cli.StatusError{StatusCode: status}
|
return StatusError{StatusCode: status}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -3,7 +3,6 @@ package client
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,7 +10,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
|
// Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdKill(args ...string) error {
|
func (cli *DockerCli) CmdKill(args ...string) error {
|
||||||
cmd := Cli.Subcmd("kill", []string{"CONTAINER [CONTAINER...]"}, "Kill a running container using SIGKILL or a specified signal", true)
|
cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container using SIGKILL or a specified signal", true)
|
||||||
signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
|
signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
|
|||||||
+2
-3
@@ -4,7 +4,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker load [OPTIONS]
|
// Usage: docker load [OPTIONS]
|
||||||
func (cli *DockerCli) CmdLoad(args ...string) error {
|
func (cli *DockerCli) CmdLoad(args ...string) error {
|
||||||
cmd := Cli.Subcmd("load", nil, "Load an image from a tar archive or STDIN", true)
|
cmd := cli.Subcmd("load", "", "Load an image from a tar archive on STDIN", true)
|
||||||
infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN")
|
infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN")
|
||||||
cmd.Require(flag.Exact, 0)
|
cmd.Require(flag.Exact, 0)
|
||||||
|
|
||||||
@@ -35,7 +34,7 @@ func (cli *DockerCli) CmdLoad(args ...string) error {
|
|||||||
in: input,
|
in: input,
|
||||||
out: cli.out,
|
out: cli.out,
|
||||||
}
|
}
|
||||||
if _, err := cli.stream("POST", "/images/load", sopts); err != nil {
|
if err := cli.stream("POST", "/images/load", sopts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+5
-8
@@ -9,7 +9,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/cliconfig"
|
"github.com/docker/docker/cliconfig"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
@@ -22,7 +21,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker login SERVER
|
// Usage: docker login SERVER
|
||||||
func (cli *DockerCli) CmdLogin(args ...string) error {
|
func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||||
cmd := Cli.Subcmd("login", []string{"[SERVER]"}, "Register or log in to a Docker registry server, if no server is\nspecified \""+registry.IndexServer+"\" is the default.", true)
|
cmd := cli.Subcmd("login", "[SERVER]", "Register or log in to a Docker registry server, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true)
|
||||||
cmd.Require(flag.Max, 1)
|
cmd.Require(flag.Max, 1)
|
||||||
|
|
||||||
var username, password, email string
|
var username, password, email string
|
||||||
@@ -33,7 +32,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
serverAddress := registry.IndexServer
|
serverAddress := registry.IndexServerAddress()
|
||||||
if len(cmd.Args()) > 0 {
|
if len(cmd.Args()) > 0 {
|
||||||
serverAddress = cmd.Arg(0)
|
serverAddress = cmd.Arg(0)
|
||||||
}
|
}
|
||||||
@@ -114,8 +113,8 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|||||||
authconfig.ServerAddress = serverAddress
|
authconfig.ServerAddress = serverAddress
|
||||||
cli.configFile.AuthConfigs[serverAddress] = authconfig
|
cli.configFile.AuthConfigs[serverAddress] = authconfig
|
||||||
|
|
||||||
serverResp, err := cli.call("POST", "/auth", cli.configFile.AuthConfigs[serverAddress], nil)
|
stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.AuthConfigs[serverAddress], nil)
|
||||||
if serverResp.statusCode == 401 {
|
if statusCode == 401 {
|
||||||
delete(cli.configFile.AuthConfigs, serverAddress)
|
delete(cli.configFile.AuthConfigs, serverAddress)
|
||||||
if err2 := cli.configFile.Save(); err2 != nil {
|
if err2 := cli.configFile.Save(); err2 != nil {
|
||||||
fmt.Fprintf(cli.out, "WARNING: could not save config file: %v\n", err2)
|
fmt.Fprintf(cli.out, "WARNING: could not save config file: %v\n", err2)
|
||||||
@@ -126,10 +125,8 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
var response types.AuthResponse
|
var response types.AuthResponse
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
|
if err := json.NewDecoder(stream).Decode(&response); err != nil {
|
||||||
// Upon error, remove entry
|
// Upon error, remove entry
|
||||||
delete(cli.configFile.AuthConfigs, serverAddress)
|
delete(cli.configFile.AuthConfigs, serverAddress)
|
||||||
return err
|
return err
|
||||||
|
|||||||
+8
-11
@@ -3,7 +3,6 @@ package client
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
)
|
)
|
||||||
@@ -14,26 +13,24 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker logout [SERVER]
|
// Usage: docker logout [SERVER]
|
||||||
func (cli *DockerCli) CmdLogout(args ...string) error {
|
func (cli *DockerCli) CmdLogout(args ...string) error {
|
||||||
cmd := Cli.Subcmd("logout", []string{"[SERVER]"}, "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServer+"\" is the default.", true)
|
cmd := cli.Subcmd("logout", "[SERVER]", "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true)
|
||||||
cmd.Require(flag.Max, 1)
|
cmd.Require(flag.Max, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
serverAddress := registry.IndexServerAddress()
|
||||||
serverAddress := registry.IndexServer
|
|
||||||
if len(cmd.Args()) > 0 {
|
if len(cmd.Args()) > 0 {
|
||||||
serverAddress = cmd.Arg(0)
|
serverAddress = cmd.Arg(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok {
|
if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok {
|
||||||
fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress)
|
fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress)
|
||||||
return nil
|
} else {
|
||||||
}
|
fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress)
|
||||||
|
delete(cli.configFile.AuthConfigs, serverAddress)
|
||||||
|
|
||||||
fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress)
|
if err := cli.configFile.Save(); err != nil {
|
||||||
delete(cli.configFile.AuthConfigs, serverAddress)
|
return fmt.Errorf("Failed to save docker config: %v", err)
|
||||||
if err := cli.configFile.Save(); err != nil {
|
}
|
||||||
return fmt.Errorf("Failed to save docker config: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-12
@@ -4,10 +4,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/timeutils"
|
"github.com/docker/docker/pkg/timeutils"
|
||||||
)
|
)
|
||||||
@@ -16,24 +14,26 @@ import (
|
|||||||
//
|
//
|
||||||
// docker logs [OPTIONS] CONTAINER
|
// docker logs [OPTIONS] CONTAINER
|
||||||
func (cli *DockerCli) CmdLogs(args ...string) error {
|
func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||||
cmd := Cli.Subcmd("logs", []string{"CONTAINER"}, "Fetch the logs of a container", true)
|
var (
|
||||||
follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
|
cmd = cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container", true)
|
||||||
since := cmd.String([]string{"-since"}, "", "Show logs since timestamp")
|
follow = cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
|
||||||
times := cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
|
since = cmd.String([]string{"-since"}, "", "Show logs since timestamp")
|
||||||
tail := cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
|
times = cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
|
||||||
|
tail = cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
|
||||||
|
)
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
name := cmd.Arg(0)
|
name := cmd.Arg(0)
|
||||||
|
|
||||||
serverResp, err := cli.call("GET", "/containers/"+name+"/json", nil, nil)
|
stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var c types.ContainerJSON
|
var c types.ContainerJSON
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
if err := json.NewDecoder(stream).Decode(&c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
|||||||
v.Set("stderr", "1")
|
v.Set("stderr", "1")
|
||||||
|
|
||||||
if *since != "" {
|
if *since != "" {
|
||||||
v.Set("since", timeutils.GetTimestamp(*since, time.Now()))
|
v.Set("since", timeutils.GetTimestamp(*since))
|
||||||
}
|
}
|
||||||
|
|
||||||
if *times {
|
if *times {
|
||||||
@@ -64,6 +64,5 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
|||||||
err: cli.err,
|
err: cli.err,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cli.stream("GET", "/containers/"+name+"/logs?"+v.Encode(), sopts)
|
return cli.stream("GET", "/containers/"+name+"/logs?"+v.Encode(), sopts)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
// +build experimental
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
nwclient "github.com/docker/libnetwork/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdNetwork is used to create, display and configure network endpoints.
|
|
||||||
func (cli *DockerCli) CmdNetwork(args ...string) error {
|
|
||||||
nCli := nwclient.NewNetworkCli(cli.out, cli.err, nwclient.CallFunc(cli.callWrapper))
|
|
||||||
args = append([]string{"network"}, args...)
|
|
||||||
return nCli.Cmd(os.Args[0], args...)
|
|
||||||
}
|
|
||||||
+1
-3
@@ -3,7 +3,6 @@ package client
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,9 +10,8 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker pause CONTAINER [CONTAINER...]
|
// Usage: docker pause CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdPause(args ...string) error {
|
func (cli *DockerCli) CmdPause(args ...string) error {
|
||||||
cmd := Cli.Subcmd("pause", []string{"CONTAINER [CONTAINER...]"}, "Pause all processes within a container", true)
|
cmd := cli.Subcmd("pause", "CONTAINER [CONTAINER...]", "Pause all processes within a container", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var errNames []string
|
var errNames []string
|
||||||
|
|||||||
+7
-15
@@ -5,9 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
"github.com/docker/docker/nat"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/nat"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdPort lists port mappings for a container.
|
// CmdPort lists port mappings for a container.
|
||||||
@@ -15,25 +14,22 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker port CONTAINER [PRIVATE_PORT[/PROTO]]
|
// Usage: docker port CONTAINER [PRIVATE_PORT[/PROTO]]
|
||||||
func (cli *DockerCli) CmdPort(args ...string) error {
|
func (cli *DockerCli) CmdPort(args ...string) error {
|
||||||
cmd := Cli.Subcmd("port", []string{"CONTAINER [PRIVATE_PORT[/PROTO]]"}, "List port mappings for the CONTAINER, or lookup the public-facing port that\nis NAT-ed to the PRIVATE_PORT", true)
|
cmd := cli.Subcmd("port", "CONTAINER [PRIVATE_PORT[/PROTO]]", "List port mappings for the CONTAINER, or lookup the public-facing port that\nis NAT-ed to the PRIVATE_PORT", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
var c struct {
|
var c struct {
|
||||||
NetworkSettings struct {
|
NetworkSettings struct {
|
||||||
Ports nat.PortMap
|
Ports nat.PortMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
if err := json.NewDecoder(stream).Decode(&c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,13 +45,9 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
|||||||
proto = parts[1]
|
proto = parts[1]
|
||||||
}
|
}
|
||||||
natPort := port + "/" + proto
|
natPort := port + "/" + proto
|
||||||
newP, err := nat.NewPort(proto, port)
|
if frontends, exists := c.NetworkSettings.Ports[nat.Port(port+"/"+proto)]; exists && frontends != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if frontends, exists := c.NetworkSettings.Ports[newP]; exists && frontends != nil {
|
|
||||||
for _, frontend := range frontends {
|
for _, frontend := range frontends {
|
||||||
fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIP, frontend.HostPort)
|
fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -64,7 +56,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
|||||||
|
|
||||||
for from, frontends := range c.NetworkSettings.Ports {
|
for from, frontends := range c.NetworkSettings.Ports {
|
||||||
for _, frontend := range frontends {
|
for _, frontend := range frontends {
|
||||||
fmt.Fprintf(cli.out, "%s -> %s:%s\n", from, frontend.HostIP, frontend.HostPort)
|
fmt.Fprintf(cli.out, "%s -> %s:%s\n", from, frontend.HostIp, frontend.HostPort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+79
-20
@@ -2,15 +2,21 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/client/ps"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers/filters"
|
"github.com/docker/docker/pkg/parsers/filters"
|
||||||
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
"github.com/docker/docker/pkg/stringutils"
|
||||||
|
"github.com/docker/docker/pkg/units"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdPs outputs a list of Docker containers.
|
// CmdPs outputs a list of Docker containers.
|
||||||
@@ -23,7 +29,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
|||||||
psFilterArgs = filters.Args{}
|
psFilterArgs = filters.Args{}
|
||||||
v = url.Values{}
|
v = url.Values{}
|
||||||
|
|
||||||
cmd = Cli.Subcmd("ps", nil, "List containers", true)
|
cmd = cli.Subcmd("ps", "", "List containers", true)
|
||||||
quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
||||||
size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes")
|
size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes")
|
||||||
all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
|
all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
|
||||||
@@ -32,7 +38,6 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
|||||||
since = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show created since Id or Name, include non-running")
|
since = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show created since Id or Name, include non-running")
|
||||||
before = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name")
|
before = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name")
|
||||||
last = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running")
|
last = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running")
|
||||||
format = cmd.String([]string{"-format"}, "", "Pretty-print containers using a Go template")
|
|
||||||
flFilter = opts.NewListOpts(nil)
|
flFilter = opts.NewListOpts(nil)
|
||||||
)
|
)
|
||||||
cmd.Require(flag.Exact, 0)
|
cmd.Require(flag.Exact, 0)
|
||||||
@@ -81,36 +86,90 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
|||||||
v.Set("filters", filterJSON)
|
v.Set("filters", filterJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
serverResp, err := cli.call("GET", "/containers/json?"+v.Encode(), nil, nil)
|
rdr, _, err := cli.call("GET", "/containers/json?"+v.Encode(), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
containers := []types.Container{}
|
containers := []types.Container{}
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&containers); err != nil {
|
if err := json.NewDecoder(rdr).Decode(&containers); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
f := *format
|
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||||
if len(f) == 0 {
|
if !*quiet {
|
||||||
if len(cli.PsFormat()) > 0 && !*quiet {
|
fmt.Fprint(w, "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")
|
||||||
f = cli.PsFormat()
|
|
||||||
|
if *size {
|
||||||
|
fmt.Fprintln(w, "\tSIZE")
|
||||||
} else {
|
} else {
|
||||||
f = "table"
|
fmt.Fprint(w, "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
psCtx := ps.Context{
|
stripNamePrefix := func(ss []string) []string {
|
||||||
Output: cli.out,
|
for i, s := range ss {
|
||||||
Format: f,
|
ss[i] = s[1:]
|
||||||
Quiet: *quiet,
|
}
|
||||||
Size: *size,
|
|
||||||
Trunc: !*noTrunc,
|
return ss
|
||||||
}
|
}
|
||||||
|
|
||||||
ps.Format(psCtx, containers)
|
for _, container := range containers {
|
||||||
|
ID := container.ID
|
||||||
|
|
||||||
|
if !*noTrunc {
|
||||||
|
ID = stringid.TruncateID(ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *quiet {
|
||||||
|
fmt.Fprintln(w, ID)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
names = stripNamePrefix(container.Names)
|
||||||
|
command = strconv.Quote(container.Command)
|
||||||
|
)
|
||||||
|
|
||||||
|
if !*noTrunc {
|
||||||
|
command = stringutils.Truncate(command, 20)
|
||||||
|
|
||||||
|
// only display the default name for the container with notrunc is passed
|
||||||
|
for _, name := range names {
|
||||||
|
if len(strings.Split(name, "/")) == 1 {
|
||||||
|
names = []string{name}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
image := container.Image
|
||||||
|
if image == "" {
|
||||||
|
image = "<no image>"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", ID, image, command,
|
||||||
|
units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(container.Created), 0))),
|
||||||
|
container.Status, api.DisplayablePorts(container.Ports), strings.Join(names, ","))
|
||||||
|
|
||||||
|
if *size {
|
||||||
|
if container.SizeRootFs > 0 {
|
||||||
|
fmt.Fprintf(w, "%s (virtual %s)\n", units.HumanSize(float64(container.SizeRw)), units.HumanSize(float64(container.SizeRootFs)))
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "%s\n", units.HumanSize(float64(container.SizeRw)))
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(w, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !*quiet {
|
||||||
|
w.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,223 +0,0 @@
|
|||||||
package ps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
"text/template"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/pkg/stringid"
|
|
||||||
"github.com/docker/docker/pkg/stringutils"
|
|
||||||
"github.com/docker/docker/pkg/units"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
tableKey = "table"
|
|
||||||
|
|
||||||
idHeader = "CONTAINER ID"
|
|
||||||
imageHeader = "IMAGE"
|
|
||||||
namesHeader = "NAMES"
|
|
||||||
commandHeader = "COMMAND"
|
|
||||||
createdAtHeader = "CREATED AT"
|
|
||||||
runningForHeader = "CREATED"
|
|
||||||
statusHeader = "STATUS"
|
|
||||||
portsHeader = "PORTS"
|
|
||||||
sizeHeader = "SIZE"
|
|
||||||
labelsHeader = "LABELS"
|
|
||||||
)
|
|
||||||
|
|
||||||
type containerContext struct {
|
|
||||||
trunc bool
|
|
||||||
header []string
|
|
||||||
c types.Container
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) ID() string {
|
|
||||||
c.addHeader(idHeader)
|
|
||||||
if c.trunc {
|
|
||||||
return stringid.TruncateID(c.c.ID)
|
|
||||||
}
|
|
||||||
return c.c.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Names() string {
|
|
||||||
c.addHeader(namesHeader)
|
|
||||||
names := stripNamePrefix(c.c.Names)
|
|
||||||
if c.trunc {
|
|
||||||
for _, name := range names {
|
|
||||||
if len(strings.Split(name, "/")) == 1 {
|
|
||||||
names = []string{name}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(names, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Image() string {
|
|
||||||
c.addHeader(imageHeader)
|
|
||||||
if c.c.Image == "" {
|
|
||||||
return "<no image>"
|
|
||||||
}
|
|
||||||
if c.trunc {
|
|
||||||
return stringutils.Truncate(c.c.Image, 12)
|
|
||||||
}
|
|
||||||
return c.c.Image
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Command() string {
|
|
||||||
c.addHeader(commandHeader)
|
|
||||||
command := c.c.Command
|
|
||||||
if c.trunc {
|
|
||||||
command = stringutils.Truncate(command, 20)
|
|
||||||
}
|
|
||||||
return strconv.Quote(command)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) CreatedAt() string {
|
|
||||||
c.addHeader(createdAtHeader)
|
|
||||||
return time.Unix(int64(c.c.Created), 0).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) RunningFor() string {
|
|
||||||
c.addHeader(runningForHeader)
|
|
||||||
createdAt := time.Unix(int64(c.c.Created), 0)
|
|
||||||
return units.HumanDuration(time.Now().UTC().Sub(createdAt))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Ports() string {
|
|
||||||
c.addHeader(portsHeader)
|
|
||||||
return api.DisplayablePorts(c.c.Ports)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Status() string {
|
|
||||||
c.addHeader(statusHeader)
|
|
||||||
return c.c.Status
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Size() string {
|
|
||||||
c.addHeader(sizeHeader)
|
|
||||||
srw := units.HumanSize(float64(c.c.SizeRw))
|
|
||||||
sv := units.HumanSize(float64(c.c.SizeRootFs))
|
|
||||||
|
|
||||||
sf := srw
|
|
||||||
if c.c.SizeRootFs > 0 {
|
|
||||||
sf = fmt.Sprintf("%s (virtual %s)", srw, sv)
|
|
||||||
}
|
|
||||||
return sf
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Labels() string {
|
|
||||||
c.addHeader(labelsHeader)
|
|
||||||
if c.c.Labels == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var joinLabels []string
|
|
||||||
for k, v := range c.c.Labels {
|
|
||||||
joinLabels = append(joinLabels, fmt.Sprintf("%s=%s", k, v))
|
|
||||||
}
|
|
||||||
return strings.Join(joinLabels, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) Label(name string) string {
|
|
||||||
n := strings.Split(name, ".")
|
|
||||||
r := strings.NewReplacer("-", " ", "_", " ")
|
|
||||||
h := r.Replace(n[len(n)-1])
|
|
||||||
|
|
||||||
c.addHeader(h)
|
|
||||||
|
|
||||||
if c.c.Labels == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return c.c.Labels[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) fullHeader() string {
|
|
||||||
if c.header == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return strings.Join(c.header, "\t")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerContext) addHeader(header string) {
|
|
||||||
if c.header == nil {
|
|
||||||
c.header = []string{}
|
|
||||||
}
|
|
||||||
c.header = append(c.header, strings.ToUpper(header))
|
|
||||||
}
|
|
||||||
|
|
||||||
func customFormat(ctx Context, containers []types.Container) {
|
|
||||||
var (
|
|
||||||
table bool
|
|
||||||
header string
|
|
||||||
format = ctx.Format
|
|
||||||
buffer = bytes.NewBufferString("")
|
|
||||||
)
|
|
||||||
|
|
||||||
if strings.HasPrefix(ctx.Format, tableKey) {
|
|
||||||
table = true
|
|
||||||
format = format[len(tableKey):]
|
|
||||||
}
|
|
||||||
|
|
||||||
format = strings.Trim(format, " ")
|
|
||||||
r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
|
|
||||||
format = r.Replace(format)
|
|
||||||
|
|
||||||
if table && ctx.Size {
|
|
||||||
format += "\t{{.Size}}"
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpl, err := template.New("").Parse(format)
|
|
||||||
if err != nil {
|
|
||||||
buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
|
|
||||||
buffer.WriteTo(ctx.Output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, container := range containers {
|
|
||||||
containerCtx := &containerContext{
|
|
||||||
trunc: ctx.Trunc,
|
|
||||||
c: container,
|
|
||||||
}
|
|
||||||
if err := tmpl.Execute(buffer, containerCtx); err != nil {
|
|
||||||
buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err))
|
|
||||||
buffer.WriteTo(ctx.Output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if table && len(header) == 0 {
|
|
||||||
header = containerCtx.fullHeader()
|
|
||||||
}
|
|
||||||
buffer.WriteString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
if table {
|
|
||||||
if len(header) == 0 {
|
|
||||||
// if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template
|
|
||||||
containerCtx := &containerContext{}
|
|
||||||
tmpl.Execute(bytes.NewBufferString(""), containerCtx)
|
|
||||||
header = containerCtx.fullHeader()
|
|
||||||
}
|
|
||||||
|
|
||||||
t := tabwriter.NewWriter(ctx.Output, 20, 1, 3, ' ', 0)
|
|
||||||
t.Write([]byte(header))
|
|
||||||
t.Write([]byte("\n"))
|
|
||||||
buffer.WriteTo(t)
|
|
||||||
t.Flush()
|
|
||||||
} else {
|
|
||||||
buffer.WriteTo(ctx.Output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stripNamePrefix(ss []string) []string {
|
|
||||||
for i, s := range ss {
|
|
||||||
ss[i] = s[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return ss
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
package ps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/pkg/stringid"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestContainerPsContext(t *testing.T) {
|
|
||||||
containerID := stringid.GenerateRandomID()
|
|
||||||
unix := time.Now().Unix()
|
|
||||||
|
|
||||||
var ctx containerContext
|
|
||||||
cases := []struct {
|
|
||||||
container types.Container
|
|
||||||
trunc bool
|
|
||||||
expValue string
|
|
||||||
expHeader string
|
|
||||||
call func() string
|
|
||||||
}{
|
|
||||||
{types.Container{ID: containerID}, true, stringid.TruncateID(containerID), idHeader, ctx.ID},
|
|
||||||
{types.Container{Names: []string{"/foobar_baz"}}, true, "foobar_baz", namesHeader, ctx.Names},
|
|
||||||
{types.Container{Image: "ubuntu"}, true, "ubuntu", imageHeader, ctx.Image},
|
|
||||||
{types.Container{Image: ""}, true, "<no image>", imageHeader, ctx.Image},
|
|
||||||
{types.Container{Command: "sh -c 'ls -la'"}, true, `"sh -c 'ls -la'"`, commandHeader, ctx.Command},
|
|
||||||
{types.Container{Created: unix}, true, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
|
|
||||||
{types.Container{Ports: []types.Port{{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}}}, true, "8080/tcp", portsHeader, ctx.Ports},
|
|
||||||
{types.Container{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status},
|
|
||||||
{types.Container{SizeRw: 10}, true, "10 B", sizeHeader, ctx.Size},
|
|
||||||
{types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10 B (virtual 20 B)", sizeHeader, ctx.Size},
|
|
||||||
{types.Container{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", labelsHeader, ctx.Labels},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
ctx = containerContext{c: c.container, trunc: c.trunc}
|
|
||||||
v := c.call()
|
|
||||||
if strings.Contains(v, ",") {
|
|
||||||
// comma-separated values means probably a map input, which won't
|
|
||||||
// be guaranteed to have the same order as our expected value
|
|
||||||
// We'll create maps and use reflect.DeepEquals to check instead:
|
|
||||||
entriesMap := make(map[string]string)
|
|
||||||
expMap := make(map[string]string)
|
|
||||||
entries := strings.Split(v, ",")
|
|
||||||
expectedEntries := strings.Split(c.expValue, ",")
|
|
||||||
for _, entry := range entries {
|
|
||||||
keyval := strings.Split(entry, "=")
|
|
||||||
entriesMap[keyval[0]] = keyval[1]
|
|
||||||
}
|
|
||||||
for _, expected := range expectedEntries {
|
|
||||||
keyval := strings.Split(expected, "=")
|
|
||||||
expMap[keyval[0]] = keyval[1]
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(expMap, entriesMap) {
|
|
||||||
t.Fatalf("Expected entries: %v, got: %v", c.expValue, v)
|
|
||||||
}
|
|
||||||
} else if v != c.expValue {
|
|
||||||
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
h := ctx.fullHeader()
|
|
||||||
if h != c.expHeader {
|
|
||||||
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c := types.Container{Labels: map[string]string{"com.docker.swarm.swarm-id": "33", "com.docker.swarm.node_name": "ubuntu"}}
|
|
||||||
ctx = containerContext{c: c, trunc: true}
|
|
||||||
|
|
||||||
sid := ctx.Label("com.docker.swarm.swarm-id")
|
|
||||||
node := ctx.Label("com.docker.swarm.node_name")
|
|
||||||
if sid != "33" {
|
|
||||||
t.Fatalf("Expected 33, was %s\n", sid)
|
|
||||||
}
|
|
||||||
|
|
||||||
if node != "ubuntu" {
|
|
||||||
t.Fatalf("Expected ubuntu, was %s\n", node)
|
|
||||||
}
|
|
||||||
|
|
||||||
h := ctx.fullHeader()
|
|
||||||
if h != "SWARM ID\tNODE NAME" {
|
|
||||||
t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContainerPsFormatError(t *testing.T) {
|
|
||||||
out := bytes.NewBufferString("")
|
|
||||||
ctx := Context{
|
|
||||||
Format: "{{InvalidFunction}}",
|
|
||||||
Output: out,
|
|
||||||
}
|
|
||||||
|
|
||||||
customFormat(ctx, make([]types.Container, 0))
|
|
||||||
if out.String() != "Template parsing error: template: :1: function \"InvalidFunction\" not defined\n" {
|
|
||||||
t.Fatalf("Expected format error, got `%v`\n", out.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package ps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
tableFormatKey = "table"
|
|
||||||
rawFormatKey = "raw"
|
|
||||||
|
|
||||||
defaultTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}} ago\t{{.Status}}\t{{.Ports}}\t{{.Names}}"
|
|
||||||
defaultQuietFormat = "{{.ID}}"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Context contains information required by the formatter to print the output as desired.
|
|
||||||
type Context struct {
|
|
||||||
// Output is the output stream to which the formatted string is written.
|
|
||||||
Output io.Writer
|
|
||||||
// Format is used to choose raw, table or custom format for the output.
|
|
||||||
Format string
|
|
||||||
// Size when set to true will display the size of the output.
|
|
||||||
Size bool
|
|
||||||
// Quiet when set to true will simply print minimal information.
|
|
||||||
Quiet bool
|
|
||||||
// Trunc when set to true will truncate the output of certain fields such as Container ID.
|
|
||||||
Trunc bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format helps to format the output using the parameters set in the Context.
|
|
||||||
// Currently Format allow to display in raw, table or custom format the output.
|
|
||||||
func Format(ctx Context, containers []types.Container) {
|
|
||||||
switch ctx.Format {
|
|
||||||
case tableFormatKey:
|
|
||||||
tableFormat(ctx, containers)
|
|
||||||
case rawFormatKey:
|
|
||||||
rawFormat(ctx, containers)
|
|
||||||
default:
|
|
||||||
customFormat(ctx, containers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func rawFormat(ctx Context, containers []types.Container) {
|
|
||||||
if ctx.Quiet {
|
|
||||||
ctx.Format = `container_id: {{.ID}}`
|
|
||||||
} else {
|
|
||||||
ctx.Format = `container_id: {{.ID}}
|
|
||||||
image: {{.Image}}
|
|
||||||
command: {{.Command}}
|
|
||||||
created_at: {{.CreatedAt}}
|
|
||||||
status: {{.Status}}
|
|
||||||
names: {{.Names}}
|
|
||||||
labels: {{.Labels}}
|
|
||||||
ports: {{.Ports}}
|
|
||||||
`
|
|
||||||
if ctx.Size {
|
|
||||||
ctx.Format += `size: {{.Size}}
|
|
||||||
`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customFormat(ctx, containers)
|
|
||||||
}
|
|
||||||
|
|
||||||
func tableFormat(ctx Context, containers []types.Container) {
|
|
||||||
ctx.Format = defaultTableFormat
|
|
||||||
if ctx.Quiet {
|
|
||||||
ctx.Format = defaultQuietFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
customFormat(ctx, containers)
|
|
||||||
}
|
|
||||||
+11
-17
@@ -4,34 +4,37 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/graph/tags"
|
"github.com/docker/docker/graph/tags"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdPull pulls an image or a repository from the registry.
|
// CmdPull pulls an image or a repository from the registry.
|
||||||
//
|
//
|
||||||
// Usage: docker pull [OPTIONS] IMAGENAME[:TAG|@DIGEST]
|
// Usage: docker pull [OPTIONS] IMAGENAME[:TAG|@DIGEST]
|
||||||
func (cli *DockerCli) CmdPull(args ...string) error {
|
func (cli *DockerCli) CmdPull(args ...string) error {
|
||||||
cmd := Cli.Subcmd("pull", []string{"NAME[:TAG|@DIGEST]"}, "Pull an image or a repository from a registry", true)
|
cmd := cli.Subcmd("pull", "NAME[:TAG|@DIGEST]", "Pull an image or a repository from the registry", true)
|
||||||
allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository")
|
allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository")
|
||||||
addTrustedFlags(cmd, true)
|
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
remote := cmd.Arg(0)
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
v = url.Values{}
|
||||||
|
remote = cmd.Arg(0)
|
||||||
|
newRemote = remote
|
||||||
|
)
|
||||||
taglessRemote, tag := parsers.ParseRepositoryTag(remote)
|
taglessRemote, tag := parsers.ParseRepositoryTag(remote)
|
||||||
if tag == "" && !*allTags {
|
if tag == "" && !*allTags {
|
||||||
tag = tags.DefaultTag
|
newRemote = utils.ImageReference(taglessRemote, tags.DEFAULTTAG)
|
||||||
fmt.Fprintf(cli.out, "Using default tag: %s\n", tag)
|
}
|
||||||
} else if tag != "" && *allTags {
|
if tag != "" && *allTags {
|
||||||
return fmt.Errorf("tag can't be used with --all-tags/-a")
|
return fmt.Errorf("tag can't be used with --all-tags/-a")
|
||||||
}
|
}
|
||||||
|
|
||||||
ref := registry.ParseReference(tag)
|
v.Set("fromImage", newRemote)
|
||||||
|
|
||||||
// Resolve the Repository name from fqn to RepositoryInfo
|
// Resolve the Repository name from fqn to RepositoryInfo
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)
|
repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)
|
||||||
@@ -39,15 +42,6 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if isTrusted() && !ref.HasDigest() {
|
|
||||||
// Check if tag is digest
|
|
||||||
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
|
|
||||||
return cli.trustedPull(repoInfo, ref, authConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
v := url.Values{}
|
|
||||||
v.Set("fromImage", ref.ImageName(taglessRemote))
|
|
||||||
|
|
||||||
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
|
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-8
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
@@ -14,13 +13,14 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker push NAME[:TAG]
|
// Usage: docker push NAME[:TAG]
|
||||||
func (cli *DockerCli) CmdPush(args ...string) error {
|
func (cli *DockerCli) CmdPush(args ...string) error {
|
||||||
cmd := Cli.Subcmd("push", []string{"NAME[:TAG]"}, "Push an image or a repository to a registry", true)
|
cmd := cli.Subcmd("push", "NAME[:TAG]", "Push an image or a repository to the registry", true)
|
||||||
addTrustedFlags(cmd, false)
|
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
remote, tag := parsers.ParseRepositoryTag(cmd.Arg(0))
|
name := cmd.Arg(0)
|
||||||
|
|
||||||
|
remote, tag := parsers.ParseRepositoryTag(name)
|
||||||
|
|
||||||
// Resolve the Repository name from fqn to RepositoryInfo
|
// Resolve the Repository name from fqn to RepositoryInfo
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(remote)
|
repoInfo, err := registry.ParseRepositoryInfo(remote)
|
||||||
@@ -41,10 +41,6 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
|||||||
return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository to <user>/<repo> (ex: %s/%s)", username, repoInfo.LocalName)
|
return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository to <user>/<repo> (ex: %s/%s)", username, repoInfo.LocalName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isTrusted() {
|
|
||||||
return cli.trustedPush(repoInfo, tag, authConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
v.Set("tag", tag)
|
v.Set("tag", tag)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package client
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,9 +10,8 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker rename OLD_NAME NEW_NAME
|
// Usage: docker rename OLD_NAME NEW_NAME
|
||||||
func (cli *DockerCli) CmdRename(args ...string) error {
|
func (cli *DockerCli) CmdRename(args ...string) error {
|
||||||
cmd := Cli.Subcmd("rename", []string{"OLD_NAME NEW_NAME"}, "Rename a container", true)
|
cmd := cli.Subcmd("rename", "OLD_NAME NEW_NAME", "Rename a container", true)
|
||||||
cmd.Require(flag.Exact, 2)
|
cmd.Require(flag.Exact, 2)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
oldName := cmd.Arg(0)
|
oldName := cmd.Arg(0)
|
||||||
|
|||||||
@@ -5,15 +5,14 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdRestart restarts one or more running containers.
|
// CmdRestart restarts one or more running containers.
|
||||||
//
|
//
|
||||||
// Usage: docker restart [OPTIONS] CONTAINER [CONTAINER...]
|
// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdRestart(args ...string) error {
|
func (cli *DockerCli) CmdRestart(args ...string) error {
|
||||||
cmd := Cli.Subcmd("restart", []string{"CONTAINER [CONTAINER...]"}, "Restart a running container", true)
|
cmd := cli.Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container", true)
|
||||||
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container")
|
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -5,7 +5,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...]
|
// Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdRm(args ...string) error {
|
func (cli *DockerCli) CmdRm(args ...string) error {
|
||||||
cmd := Cli.Subcmd("rm", []string{"CONTAINER [CONTAINER...]"}, "Remove one or more containers", true)
|
cmd := cli.Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove one or more containers", true)
|
||||||
v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container")
|
v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container")
|
||||||
link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link")
|
link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link")
|
||||||
force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)")
|
force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)")
|
||||||
|
|||||||
+7
-9
@@ -6,7 +6,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,11 +13,12 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker rmi [OPTIONS] IMAGE [IMAGE...]
|
// Usage: docker rmi [OPTIONS] IMAGE [IMAGE...]
|
||||||
func (cli *DockerCli) CmdRmi(args ...string) error {
|
func (cli *DockerCli) CmdRmi(args ...string) error {
|
||||||
cmd := Cli.Subcmd("rmi", []string{"IMAGE [IMAGE...]"}, "Remove one or more images", true)
|
var (
|
||||||
force := cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image")
|
cmd = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images", true)
|
||||||
noprune := cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents")
|
force = cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image")
|
||||||
|
noprune = cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents")
|
||||||
|
)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
@@ -31,15 +31,13 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
|
|||||||
|
|
||||||
var errNames []string
|
var errNames []string
|
||||||
for _, name := range cmd.Args() {
|
for _, name := range cmd.Args() {
|
||||||
serverResp, err := cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, nil)
|
rdr, _, err := cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
fmt.Fprintf(cli.err, "%s\n", err)
|
||||||
errNames = append(errNames, name)
|
errNames = append(errNames, name)
|
||||||
} else {
|
} else {
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
dels := []types.ImageDelete{}
|
dels := []types.ImageDelete{}
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&dels); err != nil {
|
if err := json.NewDecoder(rdr).Decode(&dels); err != nil {
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
fmt.Fprintf(cli.err, "%s\n", err)
|
||||||
errNames = append(errNames, name)
|
errNames = append(errNames, name)
|
||||||
continue
|
continue
|
||||||
|
|||||||
+4
-14
@@ -5,10 +5,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
"github.com/docker/docker/pkg/promise"
|
"github.com/docker/docker/pkg/promise"
|
||||||
"github.com/docker/docker/pkg/signal"
|
"github.com/docker/docker/pkg/signal"
|
||||||
@@ -40,8 +38,7 @@ func (cid *cidFile) Write(id string) error {
|
|||||||
//
|
//
|
||||||
// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
|
// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
|
||||||
func (cli *DockerCli) CmdRun(args ...string) error {
|
func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, "Run a command in a new container", true)
|
cmd := cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container", true)
|
||||||
addTrustedFlags(cmd, true)
|
|
||||||
|
|
||||||
// These are flags not stored in Config/HostConfig
|
// These are flags not stored in Config/HostConfig
|
||||||
var (
|
var (
|
||||||
@@ -63,11 +60,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(hostConfig.DNS) > 0 {
|
if len(hostConfig.Dns) > 0 {
|
||||||
// check the DNS settings passed via --dns against
|
// check the DNS settings passed via --dns against
|
||||||
// localhost regexp to warn if they are trying to
|
// localhost regexp to warn if they are trying to
|
||||||
// set a DNS to a localhost address
|
// set a DNS to a localhost address
|
||||||
for _, dnsIP := range hostConfig.DNS {
|
for _, dnsIP := range hostConfig.Dns {
|
||||||
if dns.IsLocalhost(dnsIP) {
|
if dns.IsLocalhost(dnsIP) {
|
||||||
fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
|
fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
|
||||||
break
|
break
|
||||||
@@ -106,13 +103,6 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|||||||
sigProxy = false
|
sigProxy = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Telling the Windows daemon the initial size of the tty during start makes
|
|
||||||
// a far better user experience rather than relying on subsequent resizes
|
|
||||||
// to cause things to catch up.
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
|
|
||||||
}
|
|
||||||
|
|
||||||
createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
|
createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -251,7 +241,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
return Cli.StatusError{StatusCode: status}
|
return StatusError{StatusCode: status}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -2,10 +2,10 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,14 +15,14 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker save [OPTIONS] IMAGE [IMAGE...]
|
// Usage: docker save [OPTIONS] IMAGE [IMAGE...]
|
||||||
func (cli *DockerCli) CmdSave(args ...string) error {
|
func (cli *DockerCli) CmdSave(args ...string) error {
|
||||||
cmd := Cli.Subcmd("save", []string{"IMAGE [IMAGE...]"}, "Save an image(s) to a tar archive (streamed to STDOUT by default)", true)
|
cmd := cli.Subcmd("save", "IMAGE [IMAGE...]", "Save an image(s) to a tar archive (streamed to STDOUT by default)", true)
|
||||||
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
|
outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
output = cli.out
|
output io.Writer = cli.out
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if *outfile != "" {
|
if *outfile != "" {
|
||||||
@@ -41,7 +41,7 @@ func (cli *DockerCli) CmdSave(args ...string) error {
|
|||||||
|
|
||||||
if len(cmd.Args()) == 1 {
|
if len(cmd.Args()) == 1 {
|
||||||
image := cmd.Arg(0)
|
image := cmd.Arg(0)
|
||||||
if _, err := cli.stream("GET", "/images/"+image+"/get", sopts); err != nil {
|
if err := cli.stream("GET", "/images/"+image+"/get", sopts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -49,7 +49,7 @@ func (cli *DockerCli) CmdSave(args ...string) error {
|
|||||||
for _, arg := range cmd.Args() {
|
for _, arg := range cmd.Args() {
|
||||||
v.Add("names", arg)
|
v.Add("names", arg)
|
||||||
}
|
}
|
||||||
if _, err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
|
if err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/pkg/stringutils"
|
"github.com/docker/docker/pkg/stringutils"
|
||||||
@@ -26,7 +25,7 @@ func (r ByStars) Less(i, j int) bool { return r[i].StarCount < r[j].StarCount }
|
|||||||
//
|
//
|
||||||
// Usage: docker search [OPTIONS] TERM
|
// Usage: docker search [OPTIONS] TERM
|
||||||
func (cli *DockerCli) CmdSearch(args ...string) error {
|
func (cli *DockerCli) CmdSearch(args ...string) error {
|
||||||
cmd := Cli.Subcmd("search", []string{"TERM"}, "Search the Docker Hub for images", true)
|
cmd := cli.Subcmd("search", "TERM", "Search the Docker Hub for images", true)
|
||||||
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
||||||
trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds")
|
trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds")
|
||||||
automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds")
|
automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds")
|
||||||
@@ -51,8 +50,6 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer rdr.Close()
|
|
||||||
|
|
||||||
results := ByStars{}
|
results := ByStars{}
|
||||||
if err := json.NewDecoder(rdr).Decode(&results); err != nil {
|
if err := json.NewDecoder(rdr).Decode(&results); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
// +build experimental
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
nwclient "github.com/docker/libnetwork/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdService is used to manage network services.
|
|
||||||
// service command is user to publish, attach and list a service from a container.
|
|
||||||
func (cli *DockerCli) CmdService(args ...string) error {
|
|
||||||
nCli := nwclient.NewNetworkCli(cli.out, cli.err, nwclient.CallFunc(cli.callWrapper))
|
|
||||||
args = append([]string{"service"}, args...)
|
|
||||||
return nCli.Cmd(os.Args[0], args...)
|
|
||||||
}
|
|
||||||
+10
-13
@@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/promise"
|
"github.com/docker/docker/pkg/promise"
|
||||||
"github.com/docker/docker/pkg/signal"
|
"github.com/docker/docker/pkg/signal"
|
||||||
@@ -45,32 +44,30 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
|||||||
//
|
//
|
||||||
// Usage: docker start [OPTIONS] CONTAINER [CONTAINER...]
|
// Usage: docker start [OPTIONS] CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdStart(args ...string) error {
|
func (cli *DockerCli) CmdStart(args ...string) error {
|
||||||
cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, "Start one or more stopped containers", true)
|
|
||||||
attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
|
|
||||||
openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
|
|
||||||
cmd.Require(flag.Min, 1)
|
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cErr chan error
|
cErr chan error
|
||||||
tty bool
|
tty bool
|
||||||
|
|
||||||
|
cmd = cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Start one or more stopped containers", true)
|
||||||
|
attach = cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
|
||||||
|
openStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cmd.Require(flag.Min, 1)
|
||||||
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
if *attach || *openStdin {
|
if *attach || *openStdin {
|
||||||
if cmd.NArg() > 1 {
|
if cmd.NArg() > 1 {
|
||||||
return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
||||||
}
|
}
|
||||||
|
|
||||||
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
var c types.ContainerJSON
|
var c types.ContainerJSON
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
if err := json.NewDecoder(stream).Decode(&c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +160,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
return Cli.StatusError{StatusCode: status}
|
return StatusError{StatusCode: status}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+12
-34
@@ -12,7 +12,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/units"
|
"github.com/docker/docker/pkg/units"
|
||||||
)
|
)
|
||||||
@@ -25,8 +24,6 @@ type containerStats struct {
|
|||||||
MemoryPercentage float64
|
MemoryPercentage float64
|
||||||
NetworkRx float64
|
NetworkRx float64
|
||||||
NetworkTx float64
|
NetworkTx float64
|
||||||
BlockRead float64
|
|
||||||
BlockWrite float64
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
@@ -38,20 +35,18 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
|||||||
} else {
|
} else {
|
||||||
v.Set("stream", "0")
|
v.Set("stream", "0")
|
||||||
}
|
}
|
||||||
serverResp, err := cli.call("GET", "/containers/"+s.Name+"/stats?"+v.Encode(), nil, nil)
|
stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats?"+v.Encode(), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.err = err
|
s.err = err
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer stream.Close()
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
previousCPU uint64
|
previousCPU uint64
|
||||||
previousSystem uint64
|
previousSystem uint64
|
||||||
dec = json.NewDecoder(serverResp.body)
|
dec = json.NewDecoder(stream)
|
||||||
u = make(chan error, 1)
|
u = make(chan error, 1)
|
||||||
)
|
)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -65,10 +60,9 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
|||||||
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
||||||
cpuPercent = 0.0
|
cpuPercent = 0.0
|
||||||
)
|
)
|
||||||
previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
|
previousCPU = v.PreCpuStats.CpuUsage.TotalUsage
|
||||||
previousSystem = v.PreCPUStats.SystemUsage
|
previousSystem = v.PreCpuStats.SystemUsage
|
||||||
cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
|
cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
|
||||||
blkRead, blkWrite := calculateBlockIO(v.BlkioStats)
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.CPUPercentage = cpuPercent
|
s.CPUPercentage = cpuPercent
|
||||||
s.Memory = float64(v.MemoryStats.Usage)
|
s.Memory = float64(v.MemoryStats.Usage)
|
||||||
@@ -76,8 +70,6 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
|||||||
s.MemoryPercentage = memPercent
|
s.MemoryPercentage = memPercent
|
||||||
s.NetworkRx = float64(v.Network.RxBytes)
|
s.NetworkRx = float64(v.Network.RxBytes)
|
||||||
s.NetworkTx = float64(v.Network.TxBytes)
|
s.NetworkTx = float64(v.Network.TxBytes)
|
||||||
s.BlockRead = float64(blkRead)
|
|
||||||
s.BlockWrite = float64(blkWrite)
|
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
u <- nil
|
u <- nil
|
||||||
if !streamStats {
|
if !streamStats {
|
||||||
@@ -115,13 +107,12 @@ func (s *containerStats) Display(w io.Writer) error {
|
|||||||
if s.err != nil {
|
if s.err != nil {
|
||||||
return s.err
|
return s.err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\n",
|
fmt.Fprintf(w, "%s\t%.2f%%\t%s/%s\t%.2f%%\t%s/%s\n",
|
||||||
s.Name,
|
s.Name,
|
||||||
s.CPUPercentage,
|
s.CPUPercentage,
|
||||||
units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit),
|
units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit),
|
||||||
s.MemoryPercentage,
|
s.MemoryPercentage,
|
||||||
units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx),
|
units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx))
|
||||||
units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,10 +122,9 @@ func (s *containerStats) Display(w io.Writer) error {
|
|||||||
//
|
//
|
||||||
// Usage: docker stats CONTAINER [CONTAINER...]
|
// Usage: docker stats CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdStats(args ...string) error {
|
func (cli *DockerCli) CmdStats(args ...string) error {
|
||||||
cmd := Cli.Subcmd("stats", []string{"CONTAINER [CONTAINER...]"}, "Display a live stream of one or more containers' resource usage statistics", true)
|
cmd := cli.Subcmd("stats", "CONTAINER [CONTAINER...]", "Display a live stream of one or more containers' resource usage statistics", true)
|
||||||
noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
|
noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
names := cmd.Args()
|
names := cmd.Args()
|
||||||
@@ -148,7 +138,7 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
|||||||
fmt.Fprint(cli.out, "\033[2J")
|
fmt.Fprint(cli.out, "\033[2J")
|
||||||
fmt.Fprint(cli.out, "\033[H")
|
fmt.Fprint(cli.out, "\033[H")
|
||||||
}
|
}
|
||||||
io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\n")
|
io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE/LIMIT\tMEM %\tNET I/O\n")
|
||||||
}
|
}
|
||||||
for _, n := range names {
|
for _, n := range names {
|
||||||
s := &containerStats{Name: n}
|
s := &containerStats{Name: n}
|
||||||
@@ -196,25 +186,13 @@ func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.Stats) flo
|
|||||||
var (
|
var (
|
||||||
cpuPercent = 0.0
|
cpuPercent = 0.0
|
||||||
// calculate the change for the cpu usage of the container in between readings
|
// calculate the change for the cpu usage of the container in between readings
|
||||||
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage - previousCPU)
|
cpuDelta = float64(v.CpuStats.CpuUsage.TotalUsage - previousCPU)
|
||||||
// calculate the change for the entire system between readings
|
// calculate the change for the entire system between readings
|
||||||
systemDelta = float64(v.CPUStats.SystemUsage - previousSystem)
|
systemDelta = float64(v.CpuStats.SystemUsage - previousSystem)
|
||||||
)
|
)
|
||||||
|
|
||||||
if systemDelta > 0.0 && cpuDelta > 0.0 {
|
if systemDelta > 0.0 && cpuDelta > 0.0 {
|
||||||
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
|
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CpuStats.CpuUsage.PercpuUsage)) * 100.0
|
||||||
}
|
}
|
||||||
return cpuPercent
|
return cpuPercent
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
|
|
||||||
for _, bioEntry := range blkio.IoServiceBytesRecursive {
|
|
||||||
switch strings.ToLower(bioEntry.Op) {
|
|
||||||
case "read":
|
|
||||||
blkRead = blkRead + bioEntry.Value
|
|
||||||
case "write":
|
|
||||||
blkWrite = blkWrite + bioEntry.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDisplay(t *testing.T) {
|
func TestDisplay(t *testing.T) {
|
||||||
@@ -17,8 +15,6 @@ func TestDisplay(t *testing.T) {
|
|||||||
MemoryPercentage: 100.0 / 2048.0 * 100.0,
|
MemoryPercentage: 100.0 / 2048.0 * 100.0,
|
||||||
NetworkRx: 100 * 1024 * 1024,
|
NetworkRx: 100 * 1024 * 1024,
|
||||||
NetworkTx: 800 * 1024 * 1024,
|
NetworkTx: 800 * 1024 * 1024,
|
||||||
BlockRead: 100 * 1024 * 1024,
|
|
||||||
BlockWrite: 800 * 1024 * 1024,
|
|
||||||
mu: sync.RWMutex{},
|
mu: sync.RWMutex{},
|
||||||
}
|
}
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
@@ -26,21 +22,8 @@ func TestDisplay(t *testing.T) {
|
|||||||
t.Fatalf("c.Display() gave error: %s", err)
|
t.Fatalf("c.Display() gave error: %s", err)
|
||||||
}
|
}
|
||||||
got := b.String()
|
got := b.String()
|
||||||
want := "app\t30.00%\t104.9 MB / 2.147 GB\t4.88%\t104.9 MB / 838.9 MB\t104.9 MB / 838.9 MB\n"
|
want := "app\t30.00%\t104.9 MB/2.147 GB\t4.88%\t104.9 MB/838.9 MB\n"
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Fatalf("c.Display() = %q, want %q", got, want)
|
t.Fatalf("c.Display() = %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCalculBlockIO(t *testing.T) {
|
|
||||||
blkio := types.BlkioStats{
|
|
||||||
IoServiceBytesRecursive: []types.BlkioStatEntry{{8, 0, "read", 1234}, {8, 1, "read", 4567}, {8, 0, "write", 123}, {8, 1, "write", 456}},
|
|
||||||
}
|
|
||||||
blkRead, blkWrite := calculateBlockIO(blkio)
|
|
||||||
if blkRead != 5801 {
|
|
||||||
t.Fatalf("blkRead = %d, want 5801", blkRead)
|
|
||||||
}
|
|
||||||
if blkWrite != 579 {
|
|
||||||
t.Fatalf("blkWrite = %d, want 579", blkWrite)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-2
@@ -5,7 +5,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,7 +14,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
|
// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdStop(args ...string) error {
|
func (cli *DockerCli) CmdStop(args ...string) error {
|
||||||
cmd := Cli.Subcmd("stop", []string{"CONTAINER [CONTAINER...]"}, "Stop a running container by sending SIGTERM and then SIGKILL after a\ngrace period", true)
|
cmd := cli.Subcmd("stop", "CONTAINER [CONTAINER...]", "Stop a running container by sending SIGTERM and then SIGKILL after a\ngrace period", true)
|
||||||
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it")
|
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -3,7 +3,6 @@ package client
|
|||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
@@ -13,7 +12,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
|
// Usage: docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
|
||||||
func (cli *DockerCli) CmdTag(args ...string) error {
|
func (cli *DockerCli) CmdTag(args ...string) error {
|
||||||
cmd := Cli.Subcmd("tag", []string{"IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]"}, "Tag an image into a repository", true)
|
cmd := cli.Subcmd("tag", "IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]", "Tag an image into a repository", true)
|
||||||
force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force")
|
force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force")
|
||||||
cmd.Require(flag.Exact, 2)
|
cmd.Require(flag.Exact, 2)
|
||||||
|
|
||||||
|
|||||||
+3
-6
@@ -8,7 +8,6 @@ import (
|
|||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker top CONTAINER
|
// Usage: docker top CONTAINER
|
||||||
func (cli *DockerCli) CmdTop(args ...string) error {
|
func (cli *DockerCli) CmdTop(args ...string) error {
|
||||||
cmd := Cli.Subcmd("top", []string{"CONTAINER [ps OPTIONS]"}, "Display the running processes of a container", true)
|
cmd := cli.Subcmd("top", "CONTAINER [ps OPTIONS]", "Display the running processes of a container", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
@@ -26,15 +25,13 @@ func (cli *DockerCli) CmdTop(args ...string) error {
|
|||||||
val.Set("ps_args", strings.Join(cmd.Args()[1:], " "))
|
val.Set("ps_args", strings.Join(cmd.Args()[1:], " "))
|
||||||
}
|
}
|
||||||
|
|
||||||
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, nil)
|
stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
procList := types.ContainerProcessList{}
|
procList := types.ContainerProcessList{}
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&procList); err != nil {
|
if err := json.NewDecoder(stream).Decode(&procList); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,454 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/distribution/digest"
|
|
||||||
"github.com/docker/distribution/registry/client/auth"
|
|
||||||
"github.com/docker/distribution/registry/client/transport"
|
|
||||||
"github.com/docker/docker/cliconfig"
|
|
||||||
"github.com/docker/docker/pkg/ansiescape"
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
|
||||||
"github.com/docker/docker/pkg/tlsconfig"
|
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
"github.com/docker/notary/client"
|
|
||||||
"github.com/docker/notary/pkg/passphrase"
|
|
||||||
"github.com/docker/notary/trustmanager"
|
|
||||||
"github.com/endophage/gotuf/data"
|
|
||||||
)
|
|
||||||
|
|
||||||
var untrusted bool
|
|
||||||
|
|
||||||
func addTrustedFlags(fs *flag.FlagSet, verify bool) {
|
|
||||||
var trusted bool
|
|
||||||
if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
|
|
||||||
if t, err := strconv.ParseBool(e); t || err != nil {
|
|
||||||
// treat any other value as true
|
|
||||||
trusted = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
message := "Skip image signing"
|
|
||||||
if verify {
|
|
||||||
message = "Skip image verification"
|
|
||||||
}
|
|
||||||
fs.BoolVar(&untrusted, []string{"-disable-content-trust"}, !trusted, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isTrusted() bool {
|
|
||||||
return !untrusted
|
|
||||||
}
|
|
||||||
|
|
||||||
var targetRegexp = regexp.MustCompile(`([\S]+): digest: ([\S]+) size: ([\d]+)`)
|
|
||||||
|
|
||||||
type target struct {
|
|
||||||
reference registry.Reference
|
|
||||||
digest digest.Digest
|
|
||||||
size int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) trustDirectory() string {
|
|
||||||
return filepath.Join(cliconfig.ConfigDir(), "trust")
|
|
||||||
}
|
|
||||||
|
|
||||||
// certificateDirectory returns the directory containing
|
|
||||||
// TLS certificates for the given server. An error is
|
|
||||||
// returned if there was an error parsing the server string.
|
|
||||||
func (cli *DockerCli) certificateDirectory(server string) (string, error) {
|
|
||||||
u, err := url.Parse(server)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func trustServer(index *registry.IndexInfo) string {
|
|
||||||
if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
|
|
||||||
if !strings.HasPrefix(s, "https://") {
|
|
||||||
return "https://" + s
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
if index.Official {
|
|
||||||
return registry.NotaryServer
|
|
||||||
}
|
|
||||||
return "https://" + index.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
type simpleCredentialStore struct {
|
|
||||||
auth cliconfig.AuthConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
|
|
||||||
return scs.auth.Username, scs.auth.Password
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig cliconfig.AuthConfig) (*client.NotaryRepository, error) {
|
|
||||||
server := trustServer(repoInfo.Index)
|
|
||||||
if !strings.HasPrefix(server, "https://") {
|
|
||||||
return nil, errors.New("unsupported scheme: https required for trust server")
|
|
||||||
}
|
|
||||||
|
|
||||||
var cfg = tlsconfig.ClientDefault
|
|
||||||
cfg.InsecureSkipVerify = !repoInfo.Index.Secure
|
|
||||||
|
|
||||||
// Get certificate base directory
|
|
||||||
certDir, err := cli.certificateDirectory(server)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
logrus.Debugf("reading certificate directory: %s", certDir)
|
|
||||||
|
|
||||||
if err := registry.ReadCertsDirectory(&cfg, certDir); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
base := &http.Transport{
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
Dial: (&net.Dialer{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
KeepAlive: 30 * time.Second,
|
|
||||||
DualStack: true,
|
|
||||||
}).Dial,
|
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
|
||||||
TLSClientConfig: &cfg,
|
|
||||||
DisableKeepAlives: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip configuration headers since request is not going to Docker daemon
|
|
||||||
modifiers := registry.DockerHeaders(http.Header{})
|
|
||||||
authTransport := transport.NewTransport(base, modifiers...)
|
|
||||||
pingClient := &http.Client{
|
|
||||||
Transport: authTransport,
|
|
||||||
Timeout: 5 * time.Second,
|
|
||||||
}
|
|
||||||
endpointStr := server + "/v2/"
|
|
||||||
req, err := http.NewRequest("GET", endpointStr, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp, err := pingClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
challengeManager := auth.NewSimpleChallengeManager()
|
|
||||||
if err := challengeManager.AddResponse(resp); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
creds := simpleCredentialStore{auth: authConfig}
|
|
||||||
tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.CanonicalName, "push", "pull")
|
|
||||||
basicHandler := auth.NewBasicHandler(creds)
|
|
||||||
modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
|
|
||||||
tr := transport.NewTransport(base, modifiers...)
|
|
||||||
|
|
||||||
return client.NewNotaryRepository(cli.trustDirectory(), repoInfo.CanonicalName, server, tr, cli.getPassphraseRetriever())
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertTarget(t client.Target) (target, error) {
|
|
||||||
h, ok := t.Hashes["sha256"]
|
|
||||||
if !ok {
|
|
||||||
return target{}, errors.New("no valid hash, expecting sha256")
|
|
||||||
}
|
|
||||||
return target{
|
|
||||||
reference: registry.ParseReference(t.Name),
|
|
||||||
digest: digest.NewDigestFromHex("sha256", hex.EncodeToString(h)),
|
|
||||||
size: t.Length,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
|
|
||||||
aliasMap := map[string]string{
|
|
||||||
"root": "offline",
|
|
||||||
"snapshot": "tagging",
|
|
||||||
"targets": "tagging",
|
|
||||||
}
|
|
||||||
baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap)
|
|
||||||
env := map[string]string{
|
|
||||||
"root": os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE"),
|
|
||||||
"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"),
|
|
||||||
"targets": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"),
|
|
||||||
}
|
|
||||||
return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
|
|
||||||
if v := env[alias]; v != "" {
|
|
||||||
return v, numAttempts > 1, nil
|
|
||||||
}
|
|
||||||
return baseRetriever(keyName, alias, createNew, numAttempts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) trustedReference(repo string, ref registry.Reference) (registry.Reference, error) {
|
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(repo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve the Auth config relevant for this server
|
|
||||||
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
|
|
||||||
|
|
||||||
notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := notaryRepo.GetTargetByName(ref.String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r, err := convertTarget(*t)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return registry.DigestReference(r.digest), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) tagTrusted(repoInfo *registry.RepositoryInfo, trustedRef, ref registry.Reference) error {
|
|
||||||
fullName := trustedRef.ImageName(repoInfo.LocalName)
|
|
||||||
fmt.Fprintf(cli.out, "Tagging %s as %s\n", fullName, ref.ImageName(repoInfo.LocalName))
|
|
||||||
tv := url.Values{}
|
|
||||||
tv.Set("repo", repoInfo.LocalName)
|
|
||||||
tv.Set("tag", ref.String())
|
|
||||||
tv.Set("force", "1")
|
|
||||||
|
|
||||||
if _, _, err := readBody(cli.call("POST", "/images/"+fullName+"/tag?"+tv.Encode(), nil, nil)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func notaryError(err error) error {
|
|
||||||
switch err.(type) {
|
|
||||||
case *json.SyntaxError:
|
|
||||||
logrus.Debugf("Notary syntax error: %s", err)
|
|
||||||
return errors.New("no trust data available for remote repository")
|
|
||||||
case client.ErrExpired:
|
|
||||||
return fmt.Errorf("remote repository out-of-date: %v", err)
|
|
||||||
case trustmanager.ErrKeyNotFound:
|
|
||||||
return fmt.Errorf("signing keys not found: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig cliconfig.AuthConfig) error {
|
|
||||||
var (
|
|
||||||
v = url.Values{}
|
|
||||||
refs = []target{}
|
|
||||||
)
|
|
||||||
|
|
||||||
notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ref.String() == "" {
|
|
||||||
// List all targets
|
|
||||||
targets, err := notaryRepo.ListTargets()
|
|
||||||
if err != nil {
|
|
||||||
return notaryError(err)
|
|
||||||
}
|
|
||||||
for _, tgt := range targets {
|
|
||||||
t, err := convertTarget(*tgt)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(cli.out, "Skipping target for %q\n", repoInfo.LocalName)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
refs = append(refs, t)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t, err := notaryRepo.GetTargetByName(ref.String())
|
|
||||||
if err != nil {
|
|
||||||
return notaryError(err)
|
|
||||||
}
|
|
||||||
r, err := convertTarget(*t)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
|
|
||||||
}
|
|
||||||
refs = append(refs, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
v.Set("fromImage", repoInfo.LocalName)
|
|
||||||
for i, r := range refs {
|
|
||||||
displayTag := r.reference.String()
|
|
||||||
if displayTag != "" {
|
|
||||||
displayTag = ":" + displayTag
|
|
||||||
}
|
|
||||||
fmt.Fprintf(cli.out, "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.LocalName, displayTag, r.digest)
|
|
||||||
v.Set("tag", r.digest.String())
|
|
||||||
|
|
||||||
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If reference is not trusted, tag by trusted reference
|
|
||||||
if !r.reference.HasDigest() {
|
|
||||||
if err := cli.tagTrusted(repoInfo, registry.DigestReference(r.digest), r.reference); err != nil {
|
|
||||||
return err
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func selectKey(keys map[string]string) string {
|
|
||||||
if len(keys) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
keyIDs := []string{}
|
|
||||||
for k := range keys {
|
|
||||||
keyIDs = append(keyIDs, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(dmcgowan): let user choose if multiple keys, now pick consistently
|
|
||||||
sort.Strings(keyIDs)
|
|
||||||
|
|
||||||
return keyIDs[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func targetStream(in io.Writer) (io.WriteCloser, <-chan []target) {
|
|
||||||
r, w := io.Pipe()
|
|
||||||
out := io.MultiWriter(in, w)
|
|
||||||
targetChan := make(chan []target)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
targets := []target{}
|
|
||||||
scanner := bufio.NewScanner(r)
|
|
||||||
scanner.Split(ansiescape.ScanANSILines)
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Bytes()
|
|
||||||
if matches := targetRegexp.FindSubmatch(line); len(matches) == 4 {
|
|
||||||
dgst, err := digest.ParseDigest(string(matches[2]))
|
|
||||||
if err != nil {
|
|
||||||
// Line does match what is expected, continue looking for valid lines
|
|
||||||
logrus.Debugf("Bad digest value %q in matched line, ignoring\n", string(matches[2]))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s, err := strconv.ParseInt(string(matches[3]), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
// Line does match what is expected, continue looking for valid lines
|
|
||||||
logrus.Debugf("Bad size value %q in matched line, ignoring\n", string(matches[3]))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
targets = append(targets, target{
|
|
||||||
reference: registry.ParseReference(string(matches[1])),
|
|
||||||
digest: dgst,
|
|
||||||
size: s,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
targetChan <- targets
|
|
||||||
}()
|
|
||||||
|
|
||||||
return ioutils.NewWriteCloserWrapper(out, w.Close), targetChan
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, authConfig cliconfig.AuthConfig) error {
|
|
||||||
streamOut, targetChan := targetStream(cli.out)
|
|
||||||
|
|
||||||
v := url.Values{}
|
|
||||||
v.Set("tag", tag)
|
|
||||||
|
|
||||||
_, _, err := cli.clientRequestAttemptLogin("POST", "/images/"+repoInfo.LocalName+"/push?"+v.Encode(), nil, streamOut, repoInfo.Index, "push")
|
|
||||||
// Close stream channel to finish target parsing
|
|
||||||
if err := streamOut.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Check error from request
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get target results
|
|
||||||
targets := <-targetChan
|
|
||||||
|
|
||||||
if tag == "" {
|
|
||||||
fmt.Fprintf(cli.out, "No tag specified, skipping trust metadata push\n")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if len(targets) == 0 {
|
|
||||||
fmt.Fprintf(cli.out, "No targets found, skipping trust metadata push\n")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(cli.out, "Signing and pushing trust metadata\n")
|
|
||||||
|
|
||||||
repo, err := cli.getNotaryRepository(repoInfo, authConfig)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(cli.out, "Error establishing connection to notary repository: %s\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, target := range targets {
|
|
||||||
h, err := hex.DecodeString(target.digest.Hex())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
t := &client.Target{
|
|
||||||
Name: target.reference.String(),
|
|
||||||
Hashes: data.Hashes{
|
|
||||||
string(target.digest.Algorithm()): h,
|
|
||||||
},
|
|
||||||
Length: int64(target.size),
|
|
||||||
}
|
|
||||||
if err := repo.AddTarget(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = repo.Publish()
|
|
||||||
if _, ok := err.(*client.ErrRepoNotInitialized); !ok {
|
|
||||||
return notaryError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ks := repo.KeyStoreManager
|
|
||||||
keys := ks.RootKeyStore().ListKeys()
|
|
||||||
|
|
||||||
rootKey := selectKey(keys)
|
|
||||||
if rootKey == "" {
|
|
||||||
rootKey, err = ks.GenRootKey("ecdsa")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cryptoService, err := ks.GetRootCryptoService(rootKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := repo.Initialize(cryptoService); err != nil {
|
|
||||||
return notaryError(err)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.CanonicalName)
|
|
||||||
|
|
||||||
return notaryError(repo.Publish())
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@ package client
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,9 +10,8 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker unpause CONTAINER [CONTAINER...]
|
// Usage: docker unpause CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdUnpause(args ...string) error {
|
func (cli *DockerCli) CmdUnpause(args ...string) error {
|
||||||
cmd := Cli.Subcmd("unpause", []string{"CONTAINER [CONTAINER...]"}, "Unpause all processes within a container", true)
|
cmd := cli.Subcmd("unpause", "CONTAINER [CONTAINER...]", "Unpause all processes within a container", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var errNames []string
|
var errNames []string
|
||||||
|
|||||||
+72
-110
@@ -30,16 +30,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errConnectionFailed = errors.New("Cannot connect to the Docker daemon. Is the docker daemon running on this host?")
|
errConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
||||||
)
|
)
|
||||||
|
|
||||||
type serverResponse struct {
|
// HTTPClient creates a new HTP client with the cli's client transport instance.
|
||||||
body io.ReadCloser
|
|
||||||
header http.Header
|
|
||||||
statusCode int
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPClient creates a new HTTP client with the cli's client transport instance.
|
|
||||||
func (cli *DockerCli) HTTPClient() *http.Client {
|
func (cli *DockerCli) HTTPClient() *http.Client {
|
||||||
return &http.Client{Transport: cli.transport}
|
return &http.Client{Transport: cli.transport}
|
||||||
}
|
}
|
||||||
@@ -54,29 +48,23 @@ func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
|
|||||||
return params, nil
|
return params, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (*serverResponse, error) {
|
func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (io.ReadCloser, string, int, error) {
|
||||||
|
|
||||||
serverResp := &serverResponse{
|
|
||||||
body: nil,
|
|
||||||
statusCode: -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedPayload := (method == "POST" || method == "PUT")
|
expectedPayload := (method == "POST" || method == "PUT")
|
||||||
if expectedPayload && in == nil {
|
if expectedPayload && in == nil {
|
||||||
in = bytes.NewReader([]byte{})
|
in = bytes.NewReader([]byte{})
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), in)
|
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return serverResp, err
|
return nil, "", -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
|
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
|
||||||
// then the user can't change OUR headers
|
// then the user can't change OUR headers
|
||||||
for k, v := range cli.configFile.HTTPHeaders {
|
for k, v := range cli.configFile.HttpHeaders {
|
||||||
req.Header.Set(k, v)
|
req.Header.Set(k, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
|
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
|
||||||
req.URL.Host = cli.addr
|
req.URL.Host = cli.addr
|
||||||
req.URL.Scheme = cli.scheme
|
req.URL.Scheme = cli.scheme
|
||||||
|
|
||||||
@@ -91,104 +79,84 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m
|
|||||||
}
|
}
|
||||||
|
|
||||||
resp, err := cli.HTTPClient().Do(req)
|
resp, err := cli.HTTPClient().Do(req)
|
||||||
|
statusCode := -1
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
serverResp.statusCode = resp.StatusCode
|
statusCode = resp.StatusCode
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if types.IsTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") {
|
if strings.Contains(err.Error(), "connection refused") {
|
||||||
return serverResp, errConnectionFailed
|
return nil, "", statusCode, errConnectionRefused
|
||||||
}
|
}
|
||||||
|
|
||||||
if cli.tlsConfig == nil && strings.Contains(err.Error(), "malformed HTTP response") {
|
if cli.tlsConfig == nil {
|
||||||
return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)
|
return nil, "", statusCode, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err)
|
||||||
}
|
}
|
||||||
if cli.tlsConfig != nil && strings.Contains(err.Error(), "remote error: bad certificate") {
|
return nil, "", statusCode, fmt.Errorf("An error occurred trying to connect: %v", err)
|
||||||
return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if serverResp.statusCode < 200 || serverResp.statusCode >= 400 {
|
if statusCode < 200 || statusCode >= 400 {
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return serverResp, err
|
return nil, "", statusCode, err
|
||||||
}
|
}
|
||||||
if len(body) == 0 {
|
if len(body) == 0 {
|
||||||
return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL)
|
return nil, "", statusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(statusCode), req.URL)
|
||||||
}
|
}
|
||||||
return serverResp, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
return nil, "", statusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
serverResp.body = resp.Body
|
return resp.Body, resp.Header.Get("Content-Type"), statusCode, nil
|
||||||
serverResp.header = resp.Header
|
|
||||||
return serverResp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cmdAttempt builds the corresponding registry Auth Header from the given
|
|
||||||
// authConfig. It returns the servers body, status, error response
|
|
||||||
func (cli *DockerCli) cmdAttempt(authConfig cliconfig.AuthConfig, method, path string, in io.Reader, out io.Writer) (io.ReadCloser, int, error) {
|
|
||||||
buf, err := json.Marshal(authConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, err
|
|
||||||
}
|
|
||||||
registryAuthHeader := []string{
|
|
||||||
base64.URLEncoding.EncodeToString(buf),
|
|
||||||
}
|
|
||||||
|
|
||||||
// begin the request
|
|
||||||
serverResp, err := cli.clientRequest(method, path, in, map[string][]string{
|
|
||||||
"X-Registry-Auth": registryAuthHeader,
|
|
||||||
})
|
|
||||||
if err == nil && out != nil {
|
|
||||||
// If we are streaming output, complete the stream since
|
|
||||||
// errors may not appear until later.
|
|
||||||
err = cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), true, out, nil)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
// Since errors in a stream appear after status 200 has been written,
|
|
||||||
// we may need to change the status code.
|
|
||||||
if strings.Contains(err.Error(), "Authentication is required") ||
|
|
||||||
strings.Contains(err.Error(), "Status 401") ||
|
|
||||||
strings.Contains(err.Error(), "401 Unauthorized") ||
|
|
||||||
strings.Contains(err.Error(), "status code 401") {
|
|
||||||
serverResp.statusCode = http.StatusUnauthorized
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return serverResp.body, serverResp.statusCode, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) {
|
func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) {
|
||||||
|
cmdAttempt := func(authConfig cliconfig.AuthConfig) (io.ReadCloser, int, error) {
|
||||||
|
buf, err := json.Marshal(authConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, err
|
||||||
|
}
|
||||||
|
registryAuthHeader := []string{
|
||||||
|
base64.URLEncoding.EncodeToString(buf),
|
||||||
|
}
|
||||||
|
|
||||||
|
// begin the request
|
||||||
|
body, contentType, statusCode, err := cli.clientRequest(method, path, in, map[string][]string{
|
||||||
|
"X-Registry-Auth": registryAuthHeader,
|
||||||
|
})
|
||||||
|
if err == nil && out != nil {
|
||||||
|
// If we are streaming output, complete the stream since
|
||||||
|
// errors may not appear until later.
|
||||||
|
err = cli.streamBody(body, contentType, true, out, nil)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// Since errors in a stream appear after status 200 has been written,
|
||||||
|
// we may need to change the status code.
|
||||||
|
if strings.Contains(err.Error(), "Authentication is required") ||
|
||||||
|
strings.Contains(err.Error(), "Status 401") ||
|
||||||
|
strings.Contains(err.Error(), "status code 401") {
|
||||||
|
statusCode = http.StatusUnauthorized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return body, statusCode, err
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve the Auth config relevant for this server
|
// Resolve the Auth config relevant for this server
|
||||||
authConfig := registry.ResolveAuthConfig(cli.configFile, index)
|
authConfig := registry.ResolveAuthConfig(cli.configFile, index)
|
||||||
body, statusCode, err := cli.cmdAttempt(authConfig, method, path, in, out)
|
body, statusCode, err := cmdAttempt(authConfig)
|
||||||
if statusCode == http.StatusUnauthorized {
|
if statusCode == http.StatusUnauthorized {
|
||||||
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
|
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
|
||||||
if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil {
|
if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
authConfig = registry.ResolveAuthConfig(cli.configFile, index)
|
authConfig = registry.ResolveAuthConfig(cli.configFile, index)
|
||||||
return cli.cmdAttempt(authConfig, method, path, in, out)
|
return cmdAttempt(authConfig)
|
||||||
}
|
}
|
||||||
return body, statusCode, err
|
return body, statusCode, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) callWrapper(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
|
func (cli *DockerCli) call(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
|
||||||
sr, err := cli.call(method, path, data, headers)
|
|
||||||
return sr.body, sr.header, sr.statusCode, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *DockerCli) call(method, path string, data interface{}, headers map[string][]string) (*serverResponse, error) {
|
|
||||||
params, err := cli.encodeData(data)
|
params, err := cli.encodeData(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sr := &serverResponse{
|
return nil, -1, err
|
||||||
body: nil,
|
|
||||||
header: nil,
|
|
||||||
statusCode: -1,
|
|
||||||
}
|
|
||||||
return sr, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if data != nil {
|
if data != nil {
|
||||||
@@ -198,8 +166,8 @@ func (cli *DockerCli) call(method, path string, data interface{}, headers map[st
|
|||||||
headers["Content-Type"] = []string{"application/json"}
|
headers["Content-Type"] = []string{"application/json"}
|
||||||
}
|
}
|
||||||
|
|
||||||
serverResp, err := cli.clientRequest(method, path, params, headers)
|
body, _, statusCode, err := cli.clientRequest(method, path, params, headers)
|
||||||
return serverResp, err
|
return body, statusCode, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type streamOpts struct {
|
type streamOpts struct {
|
||||||
@@ -210,12 +178,12 @@ type streamOpts struct {
|
|||||||
headers map[string][]string
|
headers map[string][]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) stream(method, path string, opts *streamOpts) (*serverResponse, error) {
|
func (cli *DockerCli) stream(method, path string, opts *streamOpts) error {
|
||||||
serverResp, err := cli.clientRequest(method, path, opts.in, opts.headers)
|
body, contentType, _, err := cli.clientRequest(method, path, opts.in, opts.headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return serverResp, err
|
return err
|
||||||
}
|
}
|
||||||
return serverResp, cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), opts.rawTerminal, opts.out, opts.err)
|
return cli.streamBody(body, contentType, opts.rawTerminal, opts.out, opts.err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, rawTerminal bool, stdout, stderr io.Writer) error {
|
func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, rawTerminal bool, stdout, stderr io.Writer) error {
|
||||||
@@ -260,15 +228,13 @@ func (cli *DockerCli) resizeTty(id string, isExec bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func waitForExit(cli *DockerCli, containerID string) (int, error) {
|
func waitForExit(cli *DockerCli, containerID string) (int, error) {
|
||||||
serverResp, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, nil)
|
stream, _, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
var res types.ContainerWaitResponse
|
var res types.ContainerWaitResponse
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&res); err != nil {
|
if err := json.NewDecoder(stream).Decode(&res); err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,19 +244,17 @@ func waitForExit(cli *DockerCli, containerID string) (int, error) {
|
|||||||
// getExitCode perform an inspect on the container. It returns
|
// getExitCode perform an inspect on the container. It returns
|
||||||
// the running state and the exit code.
|
// the running state and the exit code.
|
||||||
func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
|
func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
|
||||||
serverResp, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil)
|
stream, _, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we can't connect, then the daemon probably died.
|
// If we can't connect, then the daemon probably died.
|
||||||
if err != errConnectionFailed {
|
if err != errConnectionRefused {
|
||||||
return false, -1, err
|
return false, -1, err
|
||||||
}
|
}
|
||||||
return false, -1, nil
|
return false, -1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
var c types.ContainerJSON
|
var c types.ContainerJSON
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
if err := json.NewDecoder(stream).Decode(&c); err != nil {
|
||||||
return false, -1, err
|
return false, -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,17 +264,15 @@ func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
|
|||||||
// getExecExitCode perform an inspect on the exec command. It returns
|
// getExecExitCode perform an inspect on the exec command. It returns
|
||||||
// the running state and the exit code.
|
// the running state and the exit code.
|
||||||
func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
|
func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
|
||||||
serverResp, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil)
|
stream, _, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we can't connect, then the daemon probably died.
|
// If we can't connect, then the daemon probably died.
|
||||||
if err != errConnectionFailed {
|
if err != errConnectionRefused {
|
||||||
return false, -1, err
|
return false, -1, err
|
||||||
}
|
}
|
||||||
return false, -1, nil
|
return false, -1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
|
||||||
|
|
||||||
//TODO: Should we reconsider having a type in api/types?
|
//TODO: Should we reconsider having a type in api/types?
|
||||||
//this is a response to exex/id/json not container
|
//this is a response to exex/id/json not container
|
||||||
var c struct {
|
var c struct {
|
||||||
@@ -318,7 +280,7 @@ func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
|
|||||||
ExitCode int
|
ExitCode int
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
if err := json.NewDecoder(stream).Decode(&c); err != nil {
|
||||||
return false, -1, err
|
return false, -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,16 +330,16 @@ func (cli *DockerCli) getTtySize() (int, int) {
|
|||||||
return int(ws.Height), int(ws.Width)
|
return int(ws.Height), int(ws.Width)
|
||||||
}
|
}
|
||||||
|
|
||||||
func readBody(serverResp *serverResponse, err error) ([]byte, int, error) {
|
func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, error) {
|
||||||
if serverResp.body != nil {
|
if stream != nil {
|
||||||
defer serverResp.body.Close()
|
defer stream.Close()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, serverResp.statusCode, err
|
return nil, statusCode, err
|
||||||
}
|
}
|
||||||
body, err := ioutil.ReadAll(serverResp.body)
|
body, err := ioutil.ReadAll(stream)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
return body, serverResp.statusCode, nil
|
return body, statusCode, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-66
@@ -2,95 +2,60 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/autogen/dockerversion"
|
"github.com/docker/docker/autogen/dockerversion"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var versionTemplate = `Client:
|
|
||||||
Version: {{.Client.Version}}
|
|
||||||
API version: {{.Client.APIVersion}}
|
|
||||||
Go version: {{.Client.GoVersion}}
|
|
||||||
Git commit: {{.Client.GitCommit}}
|
|
||||||
Built: {{.Client.BuildTime}}
|
|
||||||
OS/Arch: {{.Client.Os}}/{{.Client.Arch}}{{if .Client.Experimental}}
|
|
||||||
Experimental: {{.Client.Experimental}}{{end}}{{if .ServerOK}}
|
|
||||||
|
|
||||||
Server:
|
|
||||||
Version: {{.Server.Version}}
|
|
||||||
API version: {{.Server.APIVersion}}
|
|
||||||
Go version: {{.Server.GoVersion}}
|
|
||||||
Git commit: {{.Server.GitCommit}}
|
|
||||||
Built: {{.Server.BuildTime}}
|
|
||||||
OS/Arch: {{.Server.Os}}/{{.Server.Arch}}{{if .Server.Experimental}}
|
|
||||||
Experimental: {{.Server.Experimental}}{{end}}{{end}}`
|
|
||||||
|
|
||||||
type versionData struct {
|
|
||||||
Client types.Version
|
|
||||||
ServerOK bool
|
|
||||||
Server types.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdVersion shows Docker version information.
|
// CmdVersion shows Docker version information.
|
||||||
//
|
//
|
||||||
// Available version information is shown for: client Docker version, client API version, client Go version, client Git commit, client OS/Arch, server Docker version, server API version, server Go version, server Git commit, and server OS/Arch.
|
// Available version information is shown for: client Docker version, client API version, client Go version, client Git commit, client OS/Arch, server Docker version, server API version, server Go version, server Git commit, and server OS/Arch.
|
||||||
//
|
//
|
||||||
// Usage: docker version
|
// Usage: docker version
|
||||||
func (cli *DockerCli) CmdVersion(args ...string) (err error) {
|
func (cli *DockerCli) CmdVersion(args ...string) error {
|
||||||
cmd := Cli.Subcmd("version", nil, "Show the Docker version information.", true)
|
cmd := cli.Subcmd("version", "", "Show the Docker version information.", true)
|
||||||
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
|
|
||||||
cmd.Require(flag.Exact, 0)
|
cmd.Require(flag.Exact, 0)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
if *tmplStr == "" {
|
|
||||||
*tmplStr = versionTemplate
|
if dockerversion.VERSION != "" {
|
||||||
|
fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(cli.out, "Client API version: %s\n", api.APIVERSION)
|
||||||
|
fmt.Fprintf(cli.out, "Go version (client): %s\n", runtime.Version())
|
||||||
|
if dockerversion.GITCOMMIT != "" {
|
||||||
|
fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(cli.out, "OS/Arch (client): %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||||
|
if utils.ExperimentalBuild() {
|
||||||
|
fmt.Fprintf(cli.out, "Experimental (client): true\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmpl *template.Template
|
stream, _, err := cli.call("GET", "/version", nil, nil)
|
||||||
if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
|
|
||||||
return Cli.StatusError{StatusCode: 64,
|
|
||||||
Status: "Template parsing error: " + err.Error()}
|
|
||||||
}
|
|
||||||
|
|
||||||
vd := versionData{
|
|
||||||
Client: types.Version{
|
|
||||||
Version: dockerversion.VERSION,
|
|
||||||
APIVersion: api.Version,
|
|
||||||
GoVersion: runtime.Version(),
|
|
||||||
GitCommit: dockerversion.GITCOMMIT,
|
|
||||||
BuildTime: dockerversion.BUILDTIME,
|
|
||||||
Os: runtime.GOOS,
|
|
||||||
Arch: runtime.GOARCH,
|
|
||||||
Experimental: utils.ExperimentalBuild(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err2 := tmpl.Execute(cli.out, vd); err2 != nil && err == nil {
|
|
||||||
err = err2
|
|
||||||
}
|
|
||||||
cli.out.Write([]byte{'\n'})
|
|
||||||
}()
|
|
||||||
|
|
||||||
serverResp, err := cli.call("GET", "/version", nil, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
var v types.Version
|
||||||
|
if err := json.NewDecoder(stream).Decode(&v); err != nil {
|
||||||
if err = json.NewDecoder(serverResp.body).Decode(&vd.Server); err != nil {
|
fmt.Fprintf(cli.err, "Error reading remote version: %s\n", err)
|
||||||
return Cli.StatusError{StatusCode: 1,
|
return err
|
||||||
Status: "Error reading remote version: " + err.Error()}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vd.ServerOK = true
|
fmt.Fprintf(cli.out, "Server version: %s\n", v.Version)
|
||||||
|
if v.ApiVersion != "" {
|
||||||
return
|
fmt.Fprintf(cli.out, "Server API version: %s\n", v.ApiVersion)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(cli.out, "Go version (server): %s\n", v.GoVersion)
|
||||||
|
fmt.Fprintf(cli.out, "Git commit (server): %s\n", v.GitCommit)
|
||||||
|
fmt.Fprintf(cli.out, "OS/Arch (server): %s/%s\n", v.Os, v.Arch)
|
||||||
|
if v.Experimental {
|
||||||
|
fmt.Fprintf(cli.out, "Experimental (server): true\n")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,232 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/url"
|
|
||||||
"text/tabwriter"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
|
||||||
"github.com/docker/docker/pkg/parsers/filters"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdVolume is the parent subcommand for all volume commands
|
|
||||||
//
|
|
||||||
// Usage: docker volume <COMMAND> <OPTS>
|
|
||||||
func (cli *DockerCli) CmdVolume(args ...string) error {
|
|
||||||
description := "Manage Docker volumes\n\nCommands:\n"
|
|
||||||
commands := [][]string{
|
|
||||||
{"create", "Create a volume"},
|
|
||||||
{"inspect", "Return low-level information on a volume"},
|
|
||||||
{"ls", "List volumes"},
|
|
||||||
{"rm", "Remove a volume"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, cmd := range commands {
|
|
||||||
description += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
description += "\nRun 'docker volume COMMAND --help' for more information on a command."
|
|
||||||
cmd := Cli.Subcmd("volume", []string{"[COMMAND]"}, description, true)
|
|
||||||
cmd.Require(flag.Exact, 0)
|
|
||||||
cmd.ParseFlags(args, true)
|
|
||||||
|
|
||||||
return cli.CmdVolumeLs(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdVolumeLs outputs a list of Docker volumes.
|
|
||||||
//
|
|
||||||
// Usage: docker volume ls [OPTIONS]
|
|
||||||
func (cli *DockerCli) CmdVolumeLs(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("volume ls", nil, "List volumes", true)
|
|
||||||
|
|
||||||
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display volume names")
|
|
||||||
flFilter := opts.NewListOpts(nil)
|
|
||||||
cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'dangling=true')")
|
|
||||||
|
|
||||||
cmd.Require(flag.Exact, 0)
|
|
||||||
cmd.ParseFlags(args, true)
|
|
||||||
|
|
||||||
volFilterArgs := filters.Args{}
|
|
||||||
for _, f := range flFilter.GetAll() {
|
|
||||||
var err error
|
|
||||||
volFilterArgs, err = filters.ParseFlag(f, volFilterArgs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
v := url.Values{}
|
|
||||||
if len(volFilterArgs) > 0 {
|
|
||||||
filterJSON, err := filters.ToParam(volFilterArgs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
v.Set("filters", filterJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := cli.call("GET", "/volumes?"+v.Encode(), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var volumes types.VolumesListResponse
|
|
||||||
if err := json.NewDecoder(resp.body).Decode(&volumes); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
|
||||||
if !*quiet {
|
|
||||||
fmt.Fprintf(w, "DRIVER \tVOLUME NAME")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, vol := range volumes.Volumes {
|
|
||||||
if *quiet {
|
|
||||||
fmt.Fprintln(w, vol.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "%s\t%s\n", vol.Driver, vol.Name)
|
|
||||||
}
|
|
||||||
w.Flush()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdVolumeInspect displays low-level information on one or more volumes.
|
|
||||||
//
|
|
||||||
// Usage: docker volume inspect [OPTIONS] VOLUME [VOLUME...]
|
|
||||||
func (cli *DockerCli) CmdVolumeInspect(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("volume inspect", []string{"[VOLUME NAME]"}, "Return low-level information on a volume", true)
|
|
||||||
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template.")
|
|
||||||
if err := cmd.Parse(args); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Require(flag.Min, 1)
|
|
||||||
cmd.ParseFlags(args, true)
|
|
||||||
|
|
||||||
var tmpl *template.Template
|
|
||||||
if *tmplStr != "" {
|
|
||||||
var err error
|
|
||||||
tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var status = 0
|
|
||||||
var volumes []*types.Volume
|
|
||||||
for _, name := range cmd.Args() {
|
|
||||||
resp, err := cli.call("GET", "/volumes/"+name, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var volume types.Volume
|
|
||||||
if err := json.NewDecoder(resp.body).Decode(&volume); err != nil {
|
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
|
||||||
status = 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if tmpl == nil {
|
|
||||||
volumes = append(volumes, &volume)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tmpl.Execute(cli.out, &volume); err != nil {
|
|
||||||
if err := tmpl.Execute(cli.out, &volume); err != nil {
|
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
|
||||||
status = 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
io.WriteString(cli.out, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
if tmpl != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := json.MarshalIndent(volumes, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = io.Copy(cli.out, bytes.NewReader(b))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
io.WriteString(cli.out, "\n")
|
|
||||||
|
|
||||||
if status != 0 {
|
|
||||||
return Cli.StatusError{StatusCode: status}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdVolumeCreate creates a new container from a given image.
|
|
||||||
//
|
|
||||||
// Usage: docker volume create [OPTIONS]
|
|
||||||
func (cli *DockerCli) CmdVolumeCreate(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("volume create", nil, "Create a volume", true)
|
|
||||||
flDriver := cmd.String([]string{"d", "-driver"}, "local", "Specify volume driver name")
|
|
||||||
flName := cmd.String([]string{"-name"}, "", "Specify volume name")
|
|
||||||
|
|
||||||
flDriverOpts := opts.NewMapOpts(nil, nil)
|
|
||||||
cmd.Var(flDriverOpts, []string{"o", "-opt"}, "Set driver specific options")
|
|
||||||
|
|
||||||
cmd.Require(flag.Exact, 0)
|
|
||||||
cmd.ParseFlags(args, true)
|
|
||||||
|
|
||||||
volReq := &types.VolumeCreateRequest{
|
|
||||||
Driver: *flDriver,
|
|
||||||
DriverOpts: flDriverOpts.GetAll(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if *flName != "" {
|
|
||||||
volReq.Name = *flName
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := cli.call("POST", "/volumes", volReq, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var vol types.Volume
|
|
||||||
if err := json.NewDecoder(resp.body).Decode(&vol); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(cli.out, "%s\n", vol.Name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdVolumeRm removes one or more containers.
|
|
||||||
//
|
|
||||||
// Usage: docker volume rm VOLUME [VOLUME...]
|
|
||||||
func (cli *DockerCli) CmdVolumeRm(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("volume rm", []string{"[NAME]"}, "Remove a volume", true)
|
|
||||||
cmd.Require(flag.Min, 1)
|
|
||||||
cmd.ParseFlags(args, true)
|
|
||||||
|
|
||||||
var status = 0
|
|
||||||
for _, name := range cmd.Args() {
|
|
||||||
_, err := cli.call("DELETE", "/volumes/"+name, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
|
||||||
status = 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Fprintf(cli.out, "%s\n", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if status != 0 {
|
|
||||||
return Cli.StatusError{StatusCode: status}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
+1
-2
@@ -3,7 +3,6 @@ package client
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker wait CONTAINER [CONTAINER...]
|
// Usage: docker wait CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdWait(args ...string) error {
|
func (cli *DockerCli) CmdWait(args ...string) error {
|
||||||
cmd := Cli.Subcmd("wait", []string{"CONTAINER [CONTAINER...]"}, "Block until a container stops, then print its exit code.", true)
|
cmd := cli.Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|||||||
+10
-21
@@ -16,26 +16,16 @@ import (
|
|||||||
|
|
||||||
// Common constants for daemon and client.
|
// Common constants for daemon and client.
|
||||||
const (
|
const (
|
||||||
// Version of Current REST API
|
APIVERSION version.Version = "1.19" // Current REST API version
|
||||||
Version version.Version = "1.21"
|
DefaultDockerfileName string = "Dockerfile" // Default filename with Docker commands, read by docker build
|
||||||
|
|
||||||
// MinVersion represents Minimun REST API version supported
|
|
||||||
MinVersion version.Version = "1.12"
|
|
||||||
|
|
||||||
// DefaultDockerfileName is the Default filename with Docker commands, read by docker build
|
|
||||||
DefaultDockerfileName string = "Dockerfile"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// byPrivatePort is temporary type used to sort types.Port by PrivatePort
|
type ByPrivatePort []types.Port
|
||||||
type byPrivatePort []types.Port
|
|
||||||
|
|
||||||
func (r byPrivatePort) Len() int { return len(r) }
|
func (r ByPrivatePort) Len() int { return len(r) }
|
||||||
func (r byPrivatePort) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
func (r ByPrivatePort) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||||||
func (r byPrivatePort) Less(i, j int) bool { return r[i].PrivatePort < r[j].PrivatePort }
|
func (r ByPrivatePort) Less(i, j int) bool { return r[i].PrivatePort < r[j].PrivatePort }
|
||||||
|
|
||||||
// DisplayablePorts returns formatted string representing open ports of container
|
|
||||||
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
|
|
||||||
// it's used by command 'docker ps'
|
|
||||||
func DisplayablePorts(ports []types.Port) string {
|
func DisplayablePorts(ports []types.Port) string {
|
||||||
var (
|
var (
|
||||||
result = []string{}
|
result = []string{}
|
||||||
@@ -45,7 +35,7 @@ func DisplayablePorts(ports []types.Port) string {
|
|||||||
)
|
)
|
||||||
firstInGroupMap = make(map[string]int)
|
firstInGroupMap = make(map[string]int)
|
||||||
lastInGroupMap = make(map[string]int)
|
lastInGroupMap = make(map[string]int)
|
||||||
sort.Sort(byPrivatePort(ports))
|
sort.Sort(ByPrivatePort(ports))
|
||||||
for _, port := range ports {
|
for _, port := range ports {
|
||||||
var (
|
var (
|
||||||
current = port.PrivatePort
|
current = port.PrivatePort
|
||||||
@@ -73,18 +63,18 @@ func DisplayablePorts(ports []types.Port) string {
|
|||||||
lastInGroupMap[portKey] = current
|
lastInGroupMap[portKey] = current
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result = append(result, formGroup(portKey, firstInGroup, lastInGroup))
|
result = append(result, FormGroup(portKey, firstInGroup, lastInGroup))
|
||||||
firstInGroupMap[portKey] = current
|
firstInGroupMap[portKey] = current
|
||||||
lastInGroupMap[portKey] = current
|
lastInGroupMap[portKey] = current
|
||||||
}
|
}
|
||||||
for portKey, firstInGroup := range firstInGroupMap {
|
for portKey, firstInGroup := range firstInGroupMap {
|
||||||
result = append(result, formGroup(portKey, firstInGroup, lastInGroupMap[portKey]))
|
result = append(result, FormGroup(portKey, firstInGroup, lastInGroupMap[portKey]))
|
||||||
}
|
}
|
||||||
result = append(result, hostMappings...)
|
result = append(result, hostMappings...)
|
||||||
return strings.Join(result, ", ")
|
return strings.Join(result, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func formGroup(key string, start, last int) string {
|
func FormGroup(key string, start, last int) string {
|
||||||
var (
|
var (
|
||||||
group string
|
group string
|
||||||
parts = strings.Split(key, "/")
|
parts = strings.Split(key, "/")
|
||||||
@@ -106,7 +96,6 @@ func formGroup(key string, start, last int) string {
|
|||||||
return fmt.Sprintf("%s/%s", group, groupType)
|
return fmt.Sprintf("%s/%s", group, groupType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchesContentType validates the content type against the expected one
|
|
||||||
func MatchesContentType(contentType, expectedType string) bool {
|
func MatchesContentType(contentType, expectedType string) bool {
|
||||||
mimetype, _, err := mime.ParseMediaType(contentType)
|
mimetype, _, err := mime.ParseMediaType(contentType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,282 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ports struct {
|
|
||||||
ports []types.Port
|
|
||||||
expected string
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisplayablePorts
|
|
||||||
func TestDisplayablePorts(t *testing.T) {
|
|
||||||
cases := []ports{
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
PrivatePort: 9988,
|
|
||||||
Type: "tcp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"9988/tcp"},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
PrivatePort: 9988,
|
|
||||||
Type: "udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"9988/udp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
IP: "0.0.0.0",
|
|
||||||
PrivatePort: 9988,
|
|
||||||
Type: "tcp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"0.0.0.0:0->9988/tcp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
PrivatePort: 9988,
|
|
||||||
PublicPort: 8899,
|
|
||||||
Type: "tcp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"9988/tcp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
IP: "4.3.2.1",
|
|
||||||
PrivatePort: 9988,
|
|
||||||
PublicPort: 8899,
|
|
||||||
Type: "tcp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"4.3.2.1:8899->9988/tcp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
IP: "4.3.2.1",
|
|
||||||
PrivatePort: 9988,
|
|
||||||
PublicPort: 9988,
|
|
||||||
Type: "tcp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"4.3.2.1:9988->9988/tcp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
PrivatePort: 9988,
|
|
||||||
Type: "udp",
|
|
||||||
}, {
|
|
||||||
PrivatePort: 9988,
|
|
||||||
Type: "udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"9988/udp, 9988/udp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
IP: "1.2.3.4",
|
|
||||||
PublicPort: 9998,
|
|
||||||
PrivatePort: 9998,
|
|
||||||
Type: "udp",
|
|
||||||
}, {
|
|
||||||
IP: "1.2.3.4",
|
|
||||||
PublicPort: 9999,
|
|
||||||
PrivatePort: 9999,
|
|
||||||
Type: "udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"1.2.3.4:9998-9999->9998-9999/udp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
IP: "1.2.3.4",
|
|
||||||
PublicPort: 8887,
|
|
||||||
PrivatePort: 9998,
|
|
||||||
Type: "udp",
|
|
||||||
}, {
|
|
||||||
IP: "1.2.3.4",
|
|
||||||
PublicPort: 8888,
|
|
||||||
PrivatePort: 9999,
|
|
||||||
Type: "udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"1.2.3.4:8887->9998/udp, 1.2.3.4:8888->9999/udp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
PrivatePort: 9998,
|
|
||||||
Type: "udp",
|
|
||||||
}, {
|
|
||||||
PrivatePort: 9999,
|
|
||||||
Type: "udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"9998-9999/udp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
IP: "1.2.3.4",
|
|
||||||
PrivatePort: 6677,
|
|
||||||
PublicPort: 7766,
|
|
||||||
Type: "tcp",
|
|
||||||
}, {
|
|
||||||
PrivatePort: 9988,
|
|
||||||
PublicPort: 8899,
|
|
||||||
Type: "udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"9988/udp, 1.2.3.4:7766->6677/tcp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
IP: "1.2.3.4",
|
|
||||||
PrivatePort: 9988,
|
|
||||||
PublicPort: 8899,
|
|
||||||
Type: "udp",
|
|
||||||
}, {
|
|
||||||
IP: "1.2.3.4",
|
|
||||||
PrivatePort: 9988,
|
|
||||||
PublicPort: 8899,
|
|
||||||
Type: "tcp",
|
|
||||||
}, {
|
|
||||||
IP: "4.3.2.1",
|
|
||||||
PrivatePort: 2233,
|
|
||||||
PublicPort: 3322,
|
|
||||||
Type: "tcp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"4.3.2.1:3322->2233/tcp, 1.2.3.4:8899->9988/udp, 1.2.3.4:8899->9988/tcp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
PrivatePort: 9988,
|
|
||||||
PublicPort: 8899,
|
|
||||||
Type: "udp",
|
|
||||||
}, {
|
|
||||||
IP: "1.2.3.4",
|
|
||||||
PrivatePort: 6677,
|
|
||||||
PublicPort: 7766,
|
|
||||||
Type: "tcp",
|
|
||||||
}, {
|
|
||||||
IP: "4.3.2.1",
|
|
||||||
PrivatePort: 2233,
|
|
||||||
PublicPort: 3322,
|
|
||||||
Type: "tcp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"9988/udp, 4.3.2.1:3322->2233/tcp, 1.2.3.4:7766->6677/tcp",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, port := range cases {
|
|
||||||
actual := DisplayablePorts(port.ports)
|
|
||||||
if port.expected != actual {
|
|
||||||
t.Fatalf("Expected %s, got %s.", port.expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchesContentType
|
|
||||||
func TestJsonContentType(t *testing.T) {
|
|
||||||
if !MatchesContentType("application/json", "application/json") {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !MatchesContentType("application/json; charset=utf-8", "application/json") {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
if MatchesContentType("dockerapplication/json", "application/json") {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadOrCreateTrustKey
|
|
||||||
func TestLoadOrCreateTrustKeyInvalidKeyFile(t *testing.T) {
|
|
||||||
tmpKeyFolderPath, err := ioutil.TempDir("", "api-trustkey-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpKeyFolderPath)
|
|
||||||
|
|
||||||
tmpKeyFile, err := ioutil.TempFile(tmpKeyFolderPath, "keyfile")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := LoadOrCreateTrustKey(tmpKeyFile.Name()); err == nil {
|
|
||||||
t.Fatalf("expected an error, got nothing.")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadOrCreateTrustKeyCreateKey(t *testing.T) {
|
|
||||||
tmpKeyFolderPath, err := ioutil.TempDir("", "api-trustkey-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpKeyFolderPath)
|
|
||||||
|
|
||||||
// Without the need to create the folder hierarchy
|
|
||||||
tmpKeyFile := filepath.Join(tmpKeyFolderPath, "keyfile")
|
|
||||||
|
|
||||||
if key, err := LoadOrCreateTrustKey(tmpKeyFile); err != nil || key == nil {
|
|
||||||
t.Fatalf("expected a new key file, got : %v and %v", err, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(tmpKeyFile); err != nil {
|
|
||||||
t.Fatalf("Expected to find a file %s, got %v", tmpKeyFile, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// With the need to create the folder hierarchy as tmpKeyFie is in a path
|
|
||||||
// where some folder do not exists.
|
|
||||||
tmpKeyFile = filepath.Join(tmpKeyFolderPath, "folder/hierarchy/keyfile")
|
|
||||||
|
|
||||||
if key, err := LoadOrCreateTrustKey(tmpKeyFile); err != nil || key == nil {
|
|
||||||
t.Fatalf("expected a new key file, got : %v and %v", err, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(tmpKeyFile); err != nil {
|
|
||||||
t.Fatalf("Expected to find a file %s, got %v", tmpKeyFile, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// With no path at all
|
|
||||||
defer os.Remove("keyfile")
|
|
||||||
if key, err := LoadOrCreateTrustKey("keyfile"); err != nil || key == nil {
|
|
||||||
t.Fatalf("expected a new key file, got : %v and %v", err, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat("keyfile"); err != nil {
|
|
||||||
t.Fatalf("Expected to find a file keyfile, got %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadOrCreateTrustKeyLoadValidKey(t *testing.T) {
|
|
||||||
tmpKeyFile := filepath.Join("fixtures", "keyfile")
|
|
||||||
|
|
||||||
if key, err := LoadOrCreateTrustKey(tmpKeyFile); err != nil || key == nil {
|
|
||||||
t.Fatalf("expected a key file, got : %v and %v", err, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
-----BEGIN EC PRIVATE KEY-----
|
|
||||||
keyID: AWX2:I27X:WQFX:IOMK:CNAK:O7PW:VYNB:ZLKC:CVAE:YJP2:SI4A:XXAY
|
|
||||||
|
|
||||||
MHcCAQEEILHTRWdcpKWsnORxSFyBnndJ4ROU41hMtr/GCiLVvwBQoAoGCCqGSM49
|
|
||||||
AwEHoUQDQgAElpVFbQ2V2UQKajqdE3fVxJ+/pE/YuEFOxWbOxF2be19BY209/iky
|
|
||||||
NzeFFK7SLpQ4CBJ7zDVXOHsMzrkY/GquGA==
|
|
||||||
-----END EC PRIVATE KEY-----
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/cliconfig"
|
|
||||||
"github.com/docker/docker/pkg/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) postAuth(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
var config *cliconfig.AuthConfig
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&config)
|
|
||||||
r.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
status, err := s.daemon.RegistryService.Auth(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeJSON(w, http.StatusOK, &types.AuthResponse{
|
|
||||||
Status: status,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,535 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/websocket"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/daemon"
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
"github.com/docker/docker/pkg/signal"
|
|
||||||
"github.com/docker/docker/pkg/version"
|
|
||||||
"github.com/docker/docker/runconfig"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) getContainersByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
if version.LessThan("1.20") && runtime.GOOS != "windows" {
|
|
||||||
return getContainersByNameDownlevel(w, s, vars["name"])
|
|
||||||
}
|
|
||||||
|
|
||||||
containerJSON, err := s.daemon.ContainerInspect(vars["name"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeJSON(w, http.StatusOK, containerJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getContainersJSON(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &daemon.ContainersConfig{
|
|
||||||
All: boolValue(r, "all"),
|
|
||||||
Size: boolValue(r, "size"),
|
|
||||||
Since: r.Form.Get("since"),
|
|
||||||
Before: r.Form.Get("before"),
|
|
||||||
Filters: r.Form.Get("filters"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if tmpLimit := r.Form.Get("limit"); tmpLimit != "" {
|
|
||||||
limit, err := strconv.Atoi(tmpLimit)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
config.Limit = limit
|
|
||||||
}
|
|
||||||
|
|
||||||
containers, err := s.daemon.Containers(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, containers)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getContainersStats(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")
|
|
||||||
}
|
|
||||||
|
|
||||||
stream := boolValueOrDefault(r, "stream", true)
|
|
||||||
var out io.Writer
|
|
||||||
if !stream {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
out = w
|
|
||||||
} else {
|
|
||||||
out = ioutils.NewWriteFlusher(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
var closeNotifier <-chan bool
|
|
||||||
if notifier, ok := w.(http.CloseNotifier); ok {
|
|
||||||
closeNotifier = notifier.CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &daemon.ContainerStatsConfig{
|
|
||||||
Stream: stream,
|
|
||||||
OutStream: out,
|
|
||||||
Stop: closeNotifier,
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.daemon.ContainerStats(vars["name"], config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getContainersLogs(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")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate args here, because we can't return not StatusOK after job.Run() call
|
|
||||||
stdout, stderr := boolValue(r, "stdout"), boolValue(r, "stderr")
|
|
||||||
if !(stdout || stderr) {
|
|
||||||
return fmt.Errorf("Bad parameters: you must choose at least one stream")
|
|
||||||
}
|
|
||||||
|
|
||||||
var since time.Time
|
|
||||||
if r.Form.Get("since") != "" {
|
|
||||||
s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
since = time.Unix(s, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
var closeNotifier <-chan bool
|
|
||||||
if notifier, ok := w.(http.CloseNotifier); ok {
|
|
||||||
closeNotifier = notifier.CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := s.daemon.Get(vars["name"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
outStream := ioutils.NewWriteFlusher(w)
|
|
||||||
// write an empty chunk of data (this is to ensure that the
|
|
||||||
// HTTP Response is sent immediately, even if the container has
|
|
||||||
// not yet produced any data)
|
|
||||||
outStream.Write(nil)
|
|
||||||
|
|
||||||
logsConfig := &daemon.ContainerLogsConfig{
|
|
||||||
Follow: boolValue(r, "follow"),
|
|
||||||
Timestamps: boolValue(r, "timestamps"),
|
|
||||||
Since: since,
|
|
||||||
Tail: r.Form.Get("tail"),
|
|
||||||
UseStdout: stdout,
|
|
||||||
UseStderr: stderr,
|
|
||||||
OutStream: outStream,
|
|
||||||
Stop: closeNotifier,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerLogs(c, logsConfig); err != nil {
|
|
||||||
fmt.Fprintf(w, "Error running logs job: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getContainersExport(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.daemon.ContainerExport(vars["name"], w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersStart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
// If contentLength is -1, we can assumed chunked encoding
|
|
||||||
// or more technically that the length is unknown
|
|
||||||
// https://golang.org/src/pkg/net/http/request.go#L139
|
|
||||||
// net/http otherwise seems to swallow any headers related to chunked encoding
|
|
||||||
// including r.TransferEncoding
|
|
||||||
// allow a nil body for backwards compatibility
|
|
||||||
var hostConfig *runconfig.HostConfig
|
|
||||||
if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) {
|
|
||||||
if err := checkForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := runconfig.DecodeHostConfig(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
hostConfig = c
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerStart(vars["name"], hostConfig); err != nil {
|
|
||||||
if err.Error() == "Container already started" {
|
|
||||||
w.WriteHeader(http.StatusNotModified)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersStop(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")
|
|
||||||
}
|
|
||||||
|
|
||||||
seconds, _ := strconv.Atoi(r.Form.Get("t"))
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerStop(vars["name"], seconds); err != nil {
|
|
||||||
if err.Error() == "Container already stopped" {
|
|
||||||
w.WriteHeader(http.StatusNotModified)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersKill(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
|
|
||||||
}
|
|
||||||
|
|
||||||
var sig uint64
|
|
||||||
name := vars["name"]
|
|
||||||
|
|
||||||
// If we have a signal, look at it. Otherwise, do nothing
|
|
||||||
if sigStr := r.Form.Get("signal"); sigStr != "" {
|
|
||||||
// Check if we passed the signal as a number:
|
|
||||||
// The largest legal signal is 31, so let's parse on 5 bits
|
|
||||||
sigN, err := strconv.ParseUint(sigStr, 10, 5)
|
|
||||||
if err != nil {
|
|
||||||
// The signal is not a number, treat it as a string (either like
|
|
||||||
// "KILL" or like "SIGKILL")
|
|
||||||
syscallSig, ok := signal.SignalMap[strings.TrimPrefix(sigStr, "SIG")]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("Invalid signal: %s", sigStr)
|
|
||||||
}
|
|
||||||
sig = uint64(syscallSig)
|
|
||||||
} else {
|
|
||||||
sig = sigN
|
|
||||||
}
|
|
||||||
|
|
||||||
if sig == 0 {
|
|
||||||
return fmt.Errorf("Invalid signal: %s", sigStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerKill(name, sig); err != nil {
|
|
||||||
_, isStopped := err.(daemon.ErrContainerNotRunning)
|
|
||||||
// Return error that's not caused because the container is stopped.
|
|
||||||
// Return error if the container is not running and the api is >= 1.20
|
|
||||||
// to keep backwards compatibility.
|
|
||||||
if version.GreaterThanOrEqualTo("1.20") || !isStopped {
|
|
||||||
return fmt.Errorf("Cannot kill container %s: %v", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersRestart(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")
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout, _ := strconv.Atoi(r.Form.Get("t"))
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerRestart(vars["name"], timeout); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersPause(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
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerPause(vars["name"]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersUnpause(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
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerUnpause(vars["name"]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersWait(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err := s.daemon.ContainerWait(vars["name"], -1*time.Second)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, &types.ContainerWaitResponse{
|
|
||||||
StatusCode: status,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getContainersChanges(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
changes, err := s.daemon.ContainerChanges(vars["name"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, changes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getContainersTop(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
|
|
||||||
}
|
|
||||||
|
|
||||||
procList, err := s.daemon.ContainerTop(vars["name"], r.Form.Get("ps_args"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, procList)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainerRename(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")
|
|
||||||
}
|
|
||||||
|
|
||||||
name := vars["name"]
|
|
||||||
newName := r.Form.Get("name")
|
|
||||||
if err := s.daemon.ContainerRename(name, newName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := checkForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
warnings []string
|
|
||||||
name = r.Form.Get("name")
|
|
||||||
)
|
|
||||||
|
|
||||||
config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
adjustCPUShares := version.LessThan("1.19")
|
|
||||||
|
|
||||||
container, warnings, err := s.daemon.ContainerCreate(name, config, hostConfig, adjustCPUShares)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusCreated, &types.ContainerCreateResponse{
|
|
||||||
ID: container.ID,
|
|
||||||
Warnings: warnings,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) deleteContainers(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")
|
|
||||||
}
|
|
||||||
|
|
||||||
name := vars["name"]
|
|
||||||
config := &daemon.ContainerRmConfig{
|
|
||||||
ForceRemove: boolValue(r, "force"),
|
|
||||||
RemoveVolume: boolValue(r, "v"),
|
|
||||||
RemoveLink: boolValue(r, "link"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerRm(name, config); err != nil {
|
|
||||||
// Force a 404 for the empty string
|
|
||||||
if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") {
|
|
||||||
return fmt.Errorf("no such id: \"\"")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersResize(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")
|
|
||||||
}
|
|
||||||
|
|
||||||
height, err := strconv.Atoi(r.Form.Get("h"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
width, err := strconv.Atoi(r.Form.Get("w"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.daemon.ContainerResize(vars["name"], height, width)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainersAttach(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")
|
|
||||||
}
|
|
||||||
|
|
||||||
cont, err := s.daemon.Get(vars["name"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
inStream, outStream, err := hijackServer(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer closeStreams(inStream, outStream)
|
|
||||||
|
|
||||||
if _, ok := r.Header["Upgrade"]; ok {
|
|
||||||
fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{
|
|
||||||
InStream: inStream,
|
|
||||||
OutStream: outStream,
|
|
||||||
UseStdin: boolValue(r, "stdin"),
|
|
||||||
UseStdout: boolValue(r, "stdout"),
|
|
||||||
UseStderr: boolValue(r, "stderr"),
|
|
||||||
Logs: boolValue(r, "logs"),
|
|
||||||
Stream: boolValue(r, "stream"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerAttachWithLogs(cont, attachWithLogsConfig); err != nil {
|
|
||||||
fmt.Fprintf(outStream, "Error attaching: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) wsContainersAttach(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")
|
|
||||||
}
|
|
||||||
|
|
||||||
cont, err := s.daemon.Get(vars["name"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
h := websocket.Handler(func(ws *websocket.Conn) {
|
|
||||||
defer ws.Close()
|
|
||||||
|
|
||||||
wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{
|
|
||||||
InStream: ws,
|
|
||||||
OutStream: ws,
|
|
||||||
ErrStream: ws,
|
|
||||||
Logs: boolValue(r, "logs"),
|
|
||||||
Stream: boolValue(r, "stream"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerWsAttachWithLogs(cont, wsAttachWithLogsConfig); err != nil {
|
|
||||||
logrus.Errorf("Error attaching websocket: %s", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
ws := websocket.Server{Handler: h, Handshake: nil}
|
|
||||||
ws.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/pkg/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
// postContainersCopy is deprecated in favor of getContainersArchive.
|
|
||||||
func (s *Server) postContainersCopy(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := types.CopyConfig{}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Resource == "" {
|
|
||||||
return fmt.Errorf("Path cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := s.daemon.ContainerCopy(vars["name"], cfg.Resource)
|
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(strings.ToLower(err.Error()), "no such id") {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return fmt.Errorf("Could not find the file %s in container %s", cfg.Resource, vars["name"])
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer data.Close()
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/x-tar")
|
|
||||||
if _, err := io.Copy(w, data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// // Encode the stat to JSON, base64 encode, and place in a header.
|
|
||||||
func setContainerPathStatHeader(stat *types.ContainerPathStat, header http.Header) error {
|
|
||||||
statJSON, err := json.Marshal(stat)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
header.Set(
|
|
||||||
"X-Docker-Container-Path-Stat",
|
|
||||||
base64.StdEncoding.EncodeToString(statJSON),
|
|
||||||
)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) headContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
v, err := archiveFormValues(r, vars)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
stat, err := s.daemon.ContainerStatPath(v.name, v.path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return setContainerPathStatHeader(stat, w.Header())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
v, err := archiveFormValues(r, vars)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tarArchive, stat, err := s.daemon.ContainerArchivePath(v.name, v.path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tarArchive.Close()
|
|
||||||
|
|
||||||
if err := setContainerPathStatHeader(stat, w.Header()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/x-tar")
|
|
||||||
_, err = io.Copy(w, tarArchive)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) putContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
v, err := archiveFormValues(r, vars)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
noOverwriteDirNonDir := boolValue(r, "noOverwriteDirNonDir")
|
|
||||||
return s.daemon.ContainerExtractToDir(v.name, v.path, noOverwriteDirNonDir, r.Body)
|
|
||||||
}
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/autogen/dockerversion"
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
|
||||||
"github.com/docker/docker/pkg/parsers/filters"
|
|
||||||
"github.com/docker/docker/pkg/parsers/kernel"
|
|
||||||
"github.com/docker/docker/pkg/version"
|
|
||||||
"github.com/docker/docker/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) getVersion(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
v := &types.Version{
|
|
||||||
Version: dockerversion.VERSION,
|
|
||||||
APIVersion: api.Version,
|
|
||||||
GitCommit: dockerversion.GITCOMMIT,
|
|
||||||
GoVersion: runtime.Version(),
|
|
||||||
Os: runtime.GOOS,
|
|
||||||
Arch: runtime.GOARCH,
|
|
||||||
BuildTime: dockerversion.BUILDTIME,
|
|
||||||
}
|
|
||||||
|
|
||||||
if version.GreaterThanOrEqualTo("1.19") {
|
|
||||||
v.Experimental = utils.ExperimentalBuild()
|
|
||||||
}
|
|
||||||
|
|
||||||
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
|
|
||||||
v.KernelVersion = kernelVersion.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getInfo(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
info, err := s.daemon.SystemInfo()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getEvents(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var since int64 = -1
|
|
||||||
if r.Form.Get("since") != "" {
|
|
||||||
s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
since = s
|
|
||||||
}
|
|
||||||
|
|
||||||
var until int64 = -1
|
|
||||||
if r.Form.Get("until") != "" {
|
|
||||||
u, err := strconv.ParseInt(r.Form.Get("until"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
until = u
|
|
||||||
}
|
|
||||||
|
|
||||||
timer := time.NewTimer(0)
|
|
||||||
timer.Stop()
|
|
||||||
if until > 0 {
|
|
||||||
dur := time.Unix(until, 0).Sub(time.Now())
|
|
||||||
timer = time.NewTimer(dur)
|
|
||||||
}
|
|
||||||
|
|
||||||
ef, err := filters.FromParam(r.Form.Get("filters"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
isFiltered := func(field string, filter []string) bool {
|
|
||||||
if len(field) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(filter) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, v := range filter {
|
|
||||||
if v == field {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if strings.Contains(field, ":") {
|
|
||||||
image := strings.Split(field, ":")
|
|
||||||
if image[0] == v {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
d := s.daemon
|
|
||||||
es := d.EventsService
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
outStream := ioutils.NewWriteFlusher(w)
|
|
||||||
outStream.Write(nil) // make sure response is sent immediately
|
|
||||||
enc := json.NewEncoder(outStream)
|
|
||||||
|
|
||||||
getContainerID := func(cn string) string {
|
|
||||||
c, err := d.Get(cn)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return c.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
sendEvent := func(ev *jsonmessage.JSONMessage) error {
|
|
||||||
//incoming container filter can be name,id or partial id, convert and replace as a full container id
|
|
||||||
for i, cn := range ef["container"] {
|
|
||||||
ef["container"][i] = getContainerID(cn)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isFiltered(ev.Status, ef["event"]) || (isFiltered(ev.ID, ef["image"]) &&
|
|
||||||
isFiltered(ev.From, ef["image"])) || isFiltered(ev.ID, ef["container"]) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return enc.Encode(ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
current, l := es.Subscribe()
|
|
||||||
if since == -1 {
|
|
||||||
current = nil
|
|
||||||
}
|
|
||||||
defer es.Evict(l)
|
|
||||||
for _, ev := range current {
|
|
||||||
if ev.Time < since {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := sendEvent(ev); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var closeNotify <-chan bool
|
|
||||||
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
|
||||||
closeNotify = closeNotifier.CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case ev := <-l:
|
|
||||||
jev, ok := ev.(*jsonmessage.JSONMessage)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := sendEvent(jev); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case <-timer.C:
|
|
||||||
return nil
|
|
||||||
case <-closeNotify:
|
|
||||||
logrus.Debug("Client disconnected, stop sending events")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
|
||||||
"github.com/docker/docker/pkg/version"
|
|
||||||
"github.com/docker/docker/runconfig"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) getExecByID(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter 'id'")
|
|
||||||
}
|
|
||||||
|
|
||||||
eConfig, err := s.daemon.ContainerExecInspect(vars["id"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, eConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainerExecCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := checkForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
name := vars["name"]
|
|
||||||
|
|
||||||
execConfig := &runconfig.ExecConfig{}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(execConfig); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
execConfig.Container = name
|
|
||||||
|
|
||||||
if len(execConfig.Cmd) == 0 {
|
|
||||||
return fmt.Errorf("No exec command specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register an instance of Exec in container.
|
|
||||||
id, err := s.daemon.ContainerExecCreate(execConfig)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("Error setting up exec command in container %s: %s", name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusCreated, &types.ContainerExecCreateResponse{
|
|
||||||
ID: id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start.
|
|
||||||
func (s *Server) postContainerExecStart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
execName = vars["name"]
|
|
||||||
stdin, inStream io.ReadCloser
|
|
||||||
stdout, stderr, outStream io.Writer
|
|
||||||
)
|
|
||||||
|
|
||||||
execStartCheck := &types.ExecStartCheck{}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(execStartCheck); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !execStartCheck.Detach {
|
|
||||||
var err error
|
|
||||||
// Setting up the streaming http interface.
|
|
||||||
inStream, outStream, err = hijackServer(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer closeStreams(inStream, outStream)
|
|
||||||
|
|
||||||
if _, ok := r.Header["Upgrade"]; ok {
|
|
||||||
fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
stdin = inStream
|
|
||||||
stdout = outStream
|
|
||||||
if !execStartCheck.Tty {
|
|
||||||
stderr = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
|
|
||||||
stdout = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
outStream = w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now run the user process in container.
|
|
||||||
if err := s.daemon.ContainerExecStart(execName, stdin, stdout, stderr); err != nil {
|
|
||||||
fmt.Fprintf(outStream, "Error running exec in container: %v\n", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postContainerExecResize(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")
|
|
||||||
}
|
|
||||||
|
|
||||||
height, err := strconv.Atoi(r.Form.Get("h"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
width, err := strconv.Atoi(r.Form.Get("w"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.daemon.ContainerExecResize(vars["name"], height, width)
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -29,29 +27,3 @@ func int64ValueOrZero(r *http.Request, k string) int64 {
|
|||||||
}
|
}
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
type archiveOptions struct {
|
|
||||||
name string
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
func archiveFormValues(r *http.Request, vars map[string]string) (archiveOptions, error) {
|
|
||||||
if vars == nil {
|
|
||||||
return archiveOptions{}, fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return archiveOptions{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
name := vars["name"]
|
|
||||||
path := filepath.FromSlash(r.Form.Get("path"))
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case name == "":
|
|
||||||
return archiveOptions{}, fmt.Errorf("bad parameter: 'name' cannot be empty")
|
|
||||||
case path == "":
|
|
||||||
return archiveOptions{}, fmt.Errorf("bad parameter: 'path' cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return archiveOptions{name, path}, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,425 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/builder"
|
|
||||||
"github.com/docker/docker/cliconfig"
|
|
||||||
"github.com/docker/docker/graph"
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
"github.com/docker/docker/pkg/parsers"
|
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
|
||||||
"github.com/docker/docker/pkg/ulimit"
|
|
||||||
"github.com/docker/docker/pkg/version"
|
|
||||||
"github.com/docker/docker/runconfig"
|
|
||||||
"github.com/docker/docker/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) postCommit(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cname := r.Form.Get("container")
|
|
||||||
|
|
||||||
pause := boolValue(r, "pause")
|
|
||||||
if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") {
|
|
||||||
pause = true
|
|
||||||
}
|
|
||||||
|
|
||||||
c, _, err := runconfig.DecodeContainerConfig(r.Body)
|
|
||||||
if err != nil && err != io.EOF { //Do not fail if body is empty.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
commitCfg := &builder.CommitConfig{
|
|
||||||
Pause: pause,
|
|
||||||
Repo: r.Form.Get("repo"),
|
|
||||||
Tag: r.Form.Get("tag"),
|
|
||||||
Author: r.Form.Get("author"),
|
|
||||||
Comment: r.Form.Get("comment"),
|
|
||||||
Changes: r.Form["changes"],
|
|
||||||
Config: c,
|
|
||||||
}
|
|
||||||
|
|
||||||
imgID, err := builder.Commit(cname, s.daemon, commitCfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusCreated, &types.ContainerCommitResponse{
|
|
||||||
ID: imgID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates an image from Pull or from Import
|
|
||||||
func (s *Server) postImagesCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
image = r.Form.Get("fromImage")
|
|
||||||
repo = r.Form.Get("repo")
|
|
||||||
tag = r.Form.Get("tag")
|
|
||||||
message = r.Form.Get("message")
|
|
||||||
)
|
|
||||||
authEncoded := r.Header.Get("X-Registry-Auth")
|
|
||||||
authConfig := &cliconfig.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 = &cliconfig.AuthConfig{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
output = ioutils.NewWriteFlusher(w)
|
|
||||||
)
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
if image != "" { //pull
|
|
||||||
if tag == "" {
|
|
||||||
image, tag = parsers.ParseRepositoryTag(image)
|
|
||||||
}
|
|
||||||
metaHeaders := map[string][]string{}
|
|
||||||
for k, v := range r.Header {
|
|
||||||
if strings.HasPrefix(k, "X-Meta-") {
|
|
||||||
metaHeaders[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imagePullConfig := &graph.ImagePullConfig{
|
|
||||||
MetaHeaders: metaHeaders,
|
|
||||||
AuthConfig: authConfig,
|
|
||||||
OutStream: output,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.daemon.Repositories().Pull(image, tag, imagePullConfig)
|
|
||||||
} else { //import
|
|
||||||
if tag == "" {
|
|
||||||
repo, tag = parsers.ParseRepositoryTag(repo)
|
|
||||||
}
|
|
||||||
|
|
||||||
src := r.Form.Get("fromSrc")
|
|
||||||
|
|
||||||
// 'err' MUST NOT be defined within this block, we need any error
|
|
||||||
// generated from the download to be available to the output
|
|
||||||
// stream processing below
|
|
||||||
var newConfig *runconfig.Config
|
|
||||||
newConfig, err = builder.BuildFromConfig(s.daemon, &runconfig.Config{}, r.Form["changes"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.daemon.Repositories().Import(src, repo, tag, message, r.Body, output, newConfig)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if !output.Flushed() {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sf := streamformatter.NewJSONStreamFormatter()
|
|
||||||
output.Write(sf.FormatError(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postImagesPush(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
metaHeaders := map[string][]string{}
|
|
||||||
for k, v := range r.Header {
|
|
||||||
if strings.HasPrefix(k, "X-Meta-") {
|
|
||||||
metaHeaders[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
authConfig := &cliconfig.AuthConfig{}
|
|
||||||
|
|
||||||
authEncoded := r.Header.Get("X-Registry-Auth")
|
|
||||||
if authEncoded != "" {
|
|
||||||
// the new format is to handle the authConfig as a header
|
|
||||||
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 = &cliconfig.AuthConfig{}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// the old format is supported for compatibility if there was no authConfig header
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
|
|
||||||
return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
name := vars["name"]
|
|
||||||
output := ioutils.NewWriteFlusher(w)
|
|
||||||
imagePushConfig := &graph.ImagePushConfig{
|
|
||||||
MetaHeaders: metaHeaders,
|
|
||||||
AuthConfig: authConfig,
|
|
||||||
Tag: r.Form.Get("tag"),
|
|
||||||
OutStream: output,
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
if err := s.daemon.Repositories().Push(name, imagePushConfig); err != nil {
|
|
||||||
if !output.Flushed() {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sf := streamformatter.NewJSONStreamFormatter()
|
|
||||||
output.Write(sf.FormatError(err))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getImagesGet(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
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/x-tar")
|
|
||||||
|
|
||||||
output := ioutils.NewWriteFlusher(w)
|
|
||||||
var names []string
|
|
||||||
if name, ok := vars["name"]; ok {
|
|
||||||
names = []string{name}
|
|
||||||
} else {
|
|
||||||
names = r.Form["names"]
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.Repositories().ImageExport(names, output); err != nil {
|
|
||||||
if !output.Flushed() {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sf := streamformatter.NewJSONStreamFormatter()
|
|
||||||
output.Write(sf.FormatError(err))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postImagesLoad(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
return s.daemon.Repositories().Load(r.Body, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) deleteImages(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")
|
|
||||||
}
|
|
||||||
|
|
||||||
name := vars["name"]
|
|
||||||
|
|
||||||
if name == "" {
|
|
||||||
return fmt.Errorf("image name cannot be blank")
|
|
||||||
}
|
|
||||||
|
|
||||||
force := boolValue(r, "force")
|
|
||||||
prune := !boolValue(r, "noprune")
|
|
||||||
|
|
||||||
list, err := s.daemon.ImageDelete(name, force, prune)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, list)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getImagesByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
imageInspect, err := s.daemon.Repositories().Lookup(vars["name"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, imageInspect)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
var (
|
|
||||||
authConfigs = map[string]cliconfig.AuthConfig{}
|
|
||||||
authConfigsEncoded = r.Header.Get("X-Registry-Config")
|
|
||||||
buildConfig = builder.NewBuildConfig()
|
|
||||||
)
|
|
||||||
|
|
||||||
if authConfigsEncoded != "" {
|
|
||||||
authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
|
|
||||||
if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); 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.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
if boolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
|
|
||||||
buildConfig.Remove = true
|
|
||||||
} else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
|
|
||||||
buildConfig.Remove = true
|
|
||||||
} else {
|
|
||||||
buildConfig.Remove = boolValue(r, "rm")
|
|
||||||
}
|
|
||||||
if boolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
|
|
||||||
buildConfig.Pull = true
|
|
||||||
}
|
|
||||||
|
|
||||||
output := ioutils.NewWriteFlusher(w)
|
|
||||||
buildConfig.Stdout = output
|
|
||||||
buildConfig.Context = r.Body
|
|
||||||
|
|
||||||
buildConfig.RemoteURL = r.FormValue("remote")
|
|
||||||
buildConfig.DockerfileName = r.FormValue("dockerfile")
|
|
||||||
buildConfig.RepoName = r.FormValue("t")
|
|
||||||
buildConfig.SuppressOutput = boolValue(r, "q")
|
|
||||||
buildConfig.NoCache = boolValue(r, "nocache")
|
|
||||||
buildConfig.ForceRemove = boolValue(r, "forcerm")
|
|
||||||
buildConfig.AuthConfigs = authConfigs
|
|
||||||
buildConfig.MemorySwap = int64ValueOrZero(r, "memswap")
|
|
||||||
buildConfig.Memory = int64ValueOrZero(r, "memory")
|
|
||||||
buildConfig.CPUShares = int64ValueOrZero(r, "cpushares")
|
|
||||||
buildConfig.CPUPeriod = int64ValueOrZero(r, "cpuperiod")
|
|
||||||
buildConfig.CPUQuota = int64ValueOrZero(r, "cpuquota")
|
|
||||||
buildConfig.CPUSetCpus = r.FormValue("cpusetcpus")
|
|
||||||
buildConfig.CPUSetMems = r.FormValue("cpusetmems")
|
|
||||||
buildConfig.CgroupParent = r.FormValue("cgroupparent")
|
|
||||||
|
|
||||||
var buildUlimits = []*ulimit.Ulimit{}
|
|
||||||
ulimitsJSON := r.FormValue("ulimits")
|
|
||||||
if ulimitsJSON != "" {
|
|
||||||
if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buildConfig.Ulimits = buildUlimits
|
|
||||||
}
|
|
||||||
|
|
||||||
// Job cancellation. Note: not all job types support this.
|
|
||||||
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
|
||||||
finished := make(chan struct{})
|
|
||||||
defer close(finished)
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-finished:
|
|
||||||
case <-closeNotifier.CloseNotify():
|
|
||||||
logrus.Infof("Client disconnected, cancelling job: build")
|
|
||||||
buildConfig.Cancel()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := builder.Build(s.daemon, buildConfig); err != nil {
|
|
||||||
// Do not write the error in the http output if it's still empty.
|
|
||||||
// This prevents from writing a 200(OK) when there is an interal error.
|
|
||||||
if !output.Flushed() {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sf := streamformatter.NewJSONStreamFormatter()
|
|
||||||
w.Write(sf.FormatError(err))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getImagesJSON(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: The filter parameter could just be a match filter
|
|
||||||
images, err := s.daemon.Repositories().Images(r.Form.Get("filters"), r.Form.Get("filter"), boolValue(r, "all"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, images)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getImagesHistory(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if vars == nil {
|
|
||||||
return fmt.Errorf("Missing parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
name := vars["name"]
|
|
||||||
history, err := s.daemon.Repositories().History(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return writeJSON(w, http.StatusOK, history)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postImagesTag(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")
|
|
||||||
}
|
|
||||||
|
|
||||||
repo := r.Form.Get("repo")
|
|
||||||
tag := r.Form.Get("tag")
|
|
||||||
force := boolValue(r, "force")
|
|
||||||
name := vars["name"]
|
|
||||||
if err := s.daemon.Repositories().Tag(repo, tag, name, force); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.daemon.EventsService.Log("tag", utils.ImageReference(repo, tag), "")
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getImagesSearch(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
config *cliconfig.AuthConfig
|
|
||||||
authEncoded = r.Header.Get("X-Registry-Auth")
|
|
||||||
headers = map[string][]string{}
|
|
||||||
)
|
|
||||||
|
|
||||||
if authEncoded != "" {
|
|
||||||
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
|
||||||
if err := json.NewDecoder(authJSON).Decode(&config); 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
|
|
||||||
config = &cliconfig.AuthConfig{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for k, v := range r.Header {
|
|
||||||
if strings.HasPrefix(k, "X-Meta-") {
|
|
||||||
headers[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
query, err := s.daemon.RegistryService.Search(r.Form.Get("term"), config, headers)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeJSON(w, http.StatusOK, query.Results)
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
func profilerSetup(mainRouter *mux.Router, path string) {
|
func ProfilerSetup(mainRouter *mux.Router, path string) {
|
||||||
var r = mainRouter.PathPrefix(path).Subrouter()
|
var r = mainRouter.PathPrefix(path).Subrouter()
|
||||||
r.HandleFunc("/vars", expVars)
|
r.HandleFunc("/vars", expVars)
|
||||||
r.HandleFunc("/pprof/", pprof.Index)
|
r.HandleFunc("/pprof/", pprof.Index)
|
||||||
|
|||||||
+1247
-60
File diff suppressed because it is too large
Load Diff
@@ -1,17 +0,0 @@
|
|||||||
// +build experimental,!windows
|
|
||||||
|
|
||||||
package server
|
|
||||||
|
|
||||||
func (s *Server) registerSubRouter() {
|
|
||||||
httpHandler := s.daemon.NetworkAPIRouter()
|
|
||||||
|
|
||||||
subrouter := s.router.PathPrefix("/v{version:[0-9.]+}/networks").Subrouter()
|
|
||||||
subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
|
|
||||||
subrouter = s.router.PathPrefix("/networks").Subrouter()
|
|
||||||
subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
|
|
||||||
|
|
||||||
subrouter = s.router.PathPrefix("/v{version:[0-9.]+}/services").Subrouter()
|
|
||||||
subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
|
|
||||||
subrouter = s.router.PathPrefix("/services").Subrouter()
|
|
||||||
subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build freebsd linux
|
// +build linux
|
||||||
|
|
||||||
package server
|
package server
|
||||||
|
|
||||||
@@ -8,12 +8,19 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/daemon"
|
"github.com/docker/docker/daemon"
|
||||||
"github.com/docker/docker/pkg/sockets"
|
"github.com/docker/docker/pkg/sockets"
|
||||||
|
"github.com/docker/docker/pkg/systemd"
|
||||||
|
"github.com/docker/docker/pkg/version"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/libnetwork/portallocator"
|
"github.com/docker/libnetwork/portallocator"
|
||||||
|
)
|
||||||
|
|
||||||
systemdActivation "github.com/coreos/go-systemd/activation"
|
const (
|
||||||
systemdDaemon "github.com/coreos/go-systemd/daemon"
|
// See http://git.kernel.org/cgit/linux/kernel/git/tip/tip.git/tree/kernel/sched/sched.h?id=8cd9234c64c584432f6992fe944ca9e46ca8ea76#n269
|
||||||
|
linuxMinCpuShares = 2
|
||||||
|
linuxMaxCpuShares = 262144
|
||||||
)
|
)
|
||||||
|
|
||||||
// newServer sets up the required serverClosers and does protocol specific checking.
|
// newServer sets up the required serverClosers and does protocol specific checking.
|
||||||
@@ -24,7 +31,7 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
|||||||
)
|
)
|
||||||
switch proto {
|
switch proto {
|
||||||
case "fd":
|
case "fd":
|
||||||
ls, err = listenFD(addr)
|
ls, err = systemd.ListenFD(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -33,7 +40,7 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
|||||||
// won't be ready.
|
// won't be ready.
|
||||||
<-s.start
|
<-s.start
|
||||||
case "tcp":
|
case "tcp":
|
||||||
l, err := s.initTCPSocket(addr)
|
l, err := s.initTcpSocket(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -49,7 +56,7 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
|||||||
}
|
}
|
||||||
var res []serverCloser
|
var res []serverCloser
|
||||||
for _, l := range ls {
|
for _, l := range ls {
|
||||||
res = append(res, &HTTPServer{
|
res = append(res, &HttpServer{
|
||||||
&http.Server{
|
&http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: s.router,
|
Handler: s.router,
|
||||||
@@ -60,14 +67,10 @@ func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptConnections allows clients to connect to the API server.
|
|
||||||
// Referenced Daemon is notified about this server, and waits for the
|
|
||||||
// daemon acknowledgement before the incoming connections are accepted.
|
|
||||||
func (s *Server) AcceptConnections(d *daemon.Daemon) {
|
func (s *Server) AcceptConnections(d *daemon.Daemon) {
|
||||||
// Tell the init daemon we are accepting requests
|
// Tell the init daemon we are accepting requests
|
||||||
s.daemon = d
|
s.daemon = d
|
||||||
s.registerSubRouter()
|
go systemd.SdNotify("READY=1")
|
||||||
go systemdDaemon.SdNotify("READY=1")
|
|
||||||
// close the lock so the listeners start accepting connections
|
// close the lock so the listeners start accepting connections
|
||||||
select {
|
select {
|
||||||
case <-s.start:
|
case <-s.start:
|
||||||
@@ -103,43 +106,17 @@ func allocateDaemonPort(addr string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getContainersByNameDownlevel performs processing for pre 1.20 APIs. This
|
func adjustCpuShares(version version.Version, hostConfig *runconfig.HostConfig) {
|
||||||
// is only relevant on non-Windows daemons.
|
if version.LessThan("1.19") {
|
||||||
func getContainersByNameDownlevel(w http.ResponseWriter, s *Server, namevar string) error {
|
if hostConfig.CpuShares > 0 {
|
||||||
containerJSONRaw, err := s.daemon.ContainerInspectPre120(namevar)
|
// Handle unsupported CpuShares
|
||||||
if err != nil {
|
if hostConfig.CpuShares < linuxMinCpuShares {
|
||||||
return err
|
logrus.Warnf("Changing requested CpuShares of %d to minimum allowed of %d", hostConfig.CpuShares, linuxMinCpuShares)
|
||||||
|
hostConfig.CpuShares = linuxMinCpuShares
|
||||||
|
} else if hostConfig.CpuShares > linuxMaxCpuShares {
|
||||||
|
logrus.Warnf("Changing requested CpuShares of %d to maximum allowed of %d", hostConfig.CpuShares, linuxMaxCpuShares)
|
||||||
|
hostConfig.CpuShares = linuxMaxCpuShares
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return writeJSON(w, http.StatusOK, containerJSONRaw)
|
|
||||||
}
|
|
||||||
|
|
||||||
// listenFD returns the specified socket activated files as a slice of
|
|
||||||
// net.Listeners or all of the activated files if "*" is given.
|
|
||||||
func listenFD(addr string) ([]net.Listener, error) {
|
|
||||||
// socket activation
|
|
||||||
listeners, err := systemdActivation.Listeners(false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if listeners == nil || len(listeners) == 0 {
|
|
||||||
return nil, fmt.Errorf("No sockets found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// default to all fds just like unix:// and tcp://
|
|
||||||
if addr == "" {
|
|
||||||
addr = "*"
|
|
||||||
}
|
|
||||||
|
|
||||||
fdNum, _ := strconv.Atoi(addr)
|
|
||||||
fdOffset := fdNum - 3
|
|
||||||
if (addr != "*") && (len(listeners) < int(fdOffset)+1) {
|
|
||||||
return nil, fmt.Errorf("Too few socket activated files passed in")
|
|
||||||
}
|
|
||||||
|
|
||||||
if addr == "*" {
|
|
||||||
return listeners, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return []net.Listener{listeners[fdOffset]}, nil
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/version"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdjustCpuSharesOldApi(t *testing.T) {
|
||||||
|
apiVersion := version.Version("1.18")
|
||||||
|
hostConfig := &runconfig.HostConfig{
|
||||||
|
CpuShares: linuxMinCpuShares - 1,
|
||||||
|
}
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != linuxMinCpuShares {
|
||||||
|
t.Errorf("Expected CpuShares to be %d", linuxMinCpuShares)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = linuxMaxCpuShares + 1
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != linuxMaxCpuShares {
|
||||||
|
t.Errorf("Expected CpuShares to be %d", linuxMaxCpuShares)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = 0
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != 0 {
|
||||||
|
t.Error("Expected CpuShares to be unchanged")
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = 1024
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != 1024 {
|
||||||
|
t.Error("Expected CpuShares to be unchanged")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdjustCpuSharesNoAdjustment(t *testing.T) {
|
||||||
|
apiVersion := version.Version("1.19")
|
||||||
|
hostConfig := &runconfig.HostConfig{
|
||||||
|
CpuShares: linuxMinCpuShares - 1,
|
||||||
|
}
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != linuxMinCpuShares-1 {
|
||||||
|
t.Errorf("Expected CpuShares to be %d", linuxMinCpuShares-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = linuxMaxCpuShares + 1
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != linuxMaxCpuShares+1 {
|
||||||
|
t.Errorf("Expected CpuShares to be %d", linuxMaxCpuShares+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = 0
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != 0 {
|
||||||
|
t.Error("Expected CpuShares to be unchanged")
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = 1024
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != 1024 {
|
||||||
|
t.Error("Expected CpuShares to be unchanged")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// +build !experimental windows
|
|
||||||
|
|
||||||
package server
|
|
||||||
|
|
||||||
func (s *Server) registerSubRouter() {
|
|
||||||
}
|
|
||||||
@@ -11,40 +11,32 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewServer sets up the required Server and does protocol specific checking.
|
// NewServer sets up the required Server and does protocol specific checking.
|
||||||
func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
func (s *Server) newServer(proto, addr string) (serverCloser, error) {
|
||||||
var (
|
var (
|
||||||
ls []net.Listener
|
err error
|
||||||
|
l net.Listener
|
||||||
)
|
)
|
||||||
switch proto {
|
switch proto {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
l, err := s.initTCPSocket(addr)
|
l, err = s.initTcpSocket(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ls = append(ls, l)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("Invalid protocol format. Windows only supports tcp.")
|
return nil, errors.New("Invalid protocol format. Windows only supports tcp.")
|
||||||
}
|
}
|
||||||
|
return &HttpServer{
|
||||||
var res []serverCloser
|
&http.Server{
|
||||||
for _, l := range ls {
|
Addr: addr,
|
||||||
res = append(res, &HTTPServer{
|
Handler: s.router,
|
||||||
&http.Server{
|
},
|
||||||
Addr: addr,
|
l,
|
||||||
Handler: s.router,
|
}, nil
|
||||||
},
|
|
||||||
l,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptConnections allows router to start listening for the incoming requests.
|
|
||||||
func (s *Server) AcceptConnections(d *daemon.Daemon) {
|
func (s *Server) AcceptConnections(d *daemon.Daemon) {
|
||||||
s.daemon = d
|
s.daemon = d
|
||||||
s.registerSubRouter()
|
|
||||||
// close the lock so the listeners start accepting connections
|
// close the lock so the listeners start accepting connections
|
||||||
select {
|
select {
|
||||||
case <-s.start:
|
case <-s.start:
|
||||||
@@ -57,8 +49,5 @@ func allocateDaemonPort(addr string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getContainersByNameDownlevel performs processing for pre 1.20 APIs. This
|
func adjustCpuShares(version version.Version, hostConfig *runconfig.HostConfig) {
|
||||||
// is only relevant on non-Windows daemons.
|
|
||||||
func getContainersByNameDownlevel(w http.ResponseWriter, s *Server, namevar string) error {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/pkg/version"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Server) getVolumesList(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
volumes, err := s.daemon.Volumes(r.Form.Get("filters"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeJSON(w, http.StatusOK, &types.VolumesListResponse{Volumes: volumes})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) getVolumeByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := s.daemon.VolumeInspect(vars["name"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeJSON(w, http.StatusOK, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) postVolumesCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var req types.VolumeCreateRequest
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
volume, err := s.daemon.VolumeCreate(req.Name, req.Driver, req.DriverOpts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writeJSON(w, http.StatusCreated, volume)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) deleteVolumes(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := parseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := s.daemon.VolumeRm(vars["name"]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTimeout takes an error returned from (generally) the http package and determines if it is a timeout error.
|
|
||||||
func IsTimeout(err error) bool {
|
|
||||||
switch e := err.(type) {
|
|
||||||
case net.Error:
|
|
||||||
return e.Timeout()
|
|
||||||
case *url.Error:
|
|
||||||
if t, ok := e.Err.(net.Error); ok {
|
|
||||||
return t.Timeout()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+12
-22
@@ -1,10 +1,9 @@
|
|||||||
// Package types is used for API stability in the types and response to the
|
// This package is used for API stability in the types and response to the
|
||||||
// consumers of the API stats endpoint.
|
// consumers of the API stats endpoint.
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// ThrottlingData stores CPU throttling stats of one running container
|
|
||||||
type ThrottlingData struct {
|
type ThrottlingData struct {
|
||||||
// Number of periods with throttling active
|
// Number of periods with throttling active
|
||||||
Periods uint64 `json:"periods"`
|
Periods uint64 `json:"periods"`
|
||||||
@@ -14,8 +13,8 @@ type ThrottlingData struct {
|
|||||||
ThrottledTime uint64 `json:"throttled_time"`
|
ThrottledTime uint64 `json:"throttled_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CPUUsage stores All CPU stats aggregated since container inception.
|
// All CPU stats are aggregated since container inception.
|
||||||
type CPUUsage struct {
|
type CpuUsage struct {
|
||||||
// Total CPU time consumed.
|
// Total CPU time consumed.
|
||||||
// Units: nanoseconds.
|
// Units: nanoseconds.
|
||||||
TotalUsage uint64 `json:"total_usage"`
|
TotalUsage uint64 `json:"total_usage"`
|
||||||
@@ -30,14 +29,12 @@ type CPUUsage struct {
|
|||||||
UsageInUsermode uint64 `json:"usage_in_usermode"`
|
UsageInUsermode uint64 `json:"usage_in_usermode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CPUStats aggregates and wraps all CPU related info of container
|
type CpuStats struct {
|
||||||
type CPUStats struct {
|
CpuUsage CpuUsage `json:"cpu_usage"`
|
||||||
CPUUsage CPUUsage `json:"cpu_usage"`
|
|
||||||
SystemUsage uint64 `json:"system_cpu_usage"`
|
SystemUsage uint64 `json:"system_cpu_usage"`
|
||||||
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
|
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MemoryStats aggregates All memory stats since container inception
|
|
||||||
type MemoryStats struct {
|
type MemoryStats struct {
|
||||||
// current res_counter usage for memory
|
// current res_counter usage for memory
|
||||||
Usage uint64 `json:"usage"`
|
Usage uint64 `json:"usage"`
|
||||||
@@ -51,8 +48,6 @@ type MemoryStats struct {
|
|||||||
Limit uint64 `json:"limit"`
|
Limit uint64 `json:"limit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlkioStatEntry is one small entity to store a piece of Blkio stats
|
|
||||||
// TODO Windows: This can be factored out
|
|
||||||
type BlkioStatEntry struct {
|
type BlkioStatEntry struct {
|
||||||
Major uint64 `json:"major"`
|
Major uint64 `json:"major"`
|
||||||
Minor uint64 `json:"minor"`
|
Minor uint64 `json:"minor"`
|
||||||
@@ -60,8 +55,6 @@ type BlkioStatEntry struct {
|
|||||||
Value uint64 `json:"value"`
|
Value uint64 `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlkioStats stores All IO service stats for data read and write
|
|
||||||
// TODO Windows: This can be factored out
|
|
||||||
type BlkioStats struct {
|
type BlkioStats struct {
|
||||||
// number of bytes tranferred to and from the block device
|
// number of bytes tranferred to and from the block device
|
||||||
IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive"`
|
IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive"`
|
||||||
@@ -74,9 +67,7 @@ type BlkioStats struct {
|
|||||||
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"`
|
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkStats aggregates All network stats of one container
|
type Network struct {
|
||||||
// TODO Windows: This will require refactoring
|
|
||||||
type NetworkStats struct {
|
|
||||||
RxBytes uint64 `json:"rx_bytes"`
|
RxBytes uint64 `json:"rx_bytes"`
|
||||||
RxPackets uint64 `json:"rx_packets"`
|
RxPackets uint64 `json:"rx_packets"`
|
||||||
RxErrors uint64 `json:"rx_errors"`
|
RxErrors uint64 `json:"rx_errors"`
|
||||||
@@ -87,12 +78,11 @@ type NetworkStats struct {
|
|||||||
TxDropped uint64 `json:"tx_dropped"`
|
TxDropped uint64 `json:"tx_dropped"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stats is Ultimate struct aggregating all types of stats of one container
|
|
||||||
type Stats struct {
|
type Stats struct {
|
||||||
Read time.Time `json:"read"`
|
Read time.Time `json:"read"`
|
||||||
Network NetworkStats `json:"network,omitempty"`
|
Network Network `json:"network,omitempty"`
|
||||||
PreCPUStats CPUStats `json:"precpu_stats,omitempty"`
|
PreCpuStats CpuStats `json:"precpu_stats,omitempty"`
|
||||||
CPUStats CPUStats `json:"cpu_stats,omitempty"`
|
CpuStats CpuStats `json:"cpu_stats,omitempty"`
|
||||||
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
|
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
|
||||||
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-112
@@ -1,7 +1,6 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/daemon/network"
|
"github.com/docker/docker/daemon/network"
|
||||||
@@ -19,41 +18,35 @@ type ContainerCreateResponse struct {
|
|||||||
Warnings []string `json:"Warnings"`
|
Warnings []string `json:"Warnings"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerExecCreateResponse contains response of Remote API:
|
// POST /containers/{name:.*}/exec
|
||||||
// POST "/containers/{name:.*}/exec"
|
|
||||||
type ContainerExecCreateResponse struct {
|
type ContainerExecCreateResponse struct {
|
||||||
// ID is the exec ID.
|
// ID is the exec ID.
|
||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthResponse contains response of Remote API:
|
// POST /auth
|
||||||
// POST "/auth"
|
|
||||||
type AuthResponse struct {
|
type AuthResponse struct {
|
||||||
// Status is the authentication status
|
// Status is the authentication status
|
||||||
Status string `json:"Status"`
|
Status string `json:"Status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerWaitResponse contains response of Remote API:
|
|
||||||
// POST "/containers/"+containerID+"/wait"
|
// POST "/containers/"+containerID+"/wait"
|
||||||
type ContainerWaitResponse struct {
|
type ContainerWaitResponse struct {
|
||||||
// StatusCode is the status code of the wait job
|
// StatusCode is the status code of the wait job
|
||||||
StatusCode int `json:"StatusCode"`
|
StatusCode int `json:"StatusCode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerCommitResponse contains response of Remote API:
|
|
||||||
// POST "/commit?container="+containerID
|
// POST "/commit?container="+containerID
|
||||||
type ContainerCommitResponse struct {
|
type ContainerCommitResponse struct {
|
||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerChange contains response of Remote API:
|
|
||||||
// GET "/containers/{name:.*}/changes"
|
// GET "/containers/{name:.*}/changes"
|
||||||
type ContainerChange struct {
|
type ContainerChange struct {
|
||||||
Kind int
|
Kind int
|
||||||
Path string
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImageHistory contains response of Remote API:
|
|
||||||
// GET "/images/{name:.*}/history"
|
// GET "/images/{name:.*}/history"
|
||||||
type ImageHistory struct {
|
type ImageHistory struct {
|
||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
@@ -64,40 +57,30 @@ type ImageHistory struct {
|
|||||||
Comment string
|
Comment string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImageDelete contains response of Remote API:
|
|
||||||
// DELETE "/images/{name:.*}"
|
// DELETE "/images/{name:.*}"
|
||||||
type ImageDelete struct {
|
type ImageDelete struct {
|
||||||
Untagged string `json:",omitempty"`
|
Untagged string `json:",omitempty"`
|
||||||
Deleted string `json:",omitempty"`
|
Deleted string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Image contains response of Remote API:
|
|
||||||
// GET "/images/json"
|
// GET "/images/json"
|
||||||
type Image struct {
|
type Image struct {
|
||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
ParentID string `json:"ParentId"`
|
ParentId string
|
||||||
RepoTags []string
|
RepoTags []string
|
||||||
RepoDigests []string
|
RepoDigests []string
|
||||||
Created int64
|
Created int
|
||||||
Size int64
|
Size int
|
||||||
VirtualSize int64
|
VirtualSize int
|
||||||
Labels map[string]string
|
Labels map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphDriverData returns Image's graph driver config info
|
|
||||||
// when calling inspect command
|
|
||||||
type GraphDriverData struct {
|
|
||||||
Name string
|
|
||||||
Data map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImageInspect contains response of Remote API:
|
|
||||||
// GET "/images/{name:.*}/json"
|
// GET "/images/{name:.*}/json"
|
||||||
type ImageInspect struct {
|
type ImageInspect struct {
|
||||||
ID string `json:"Id"`
|
Id string
|
||||||
Parent string
|
Parent string
|
||||||
Comment string
|
Comment string
|
||||||
Created string
|
Created time.Time
|
||||||
Container string
|
Container string
|
||||||
ContainerConfig *runconfig.Config
|
ContainerConfig *runconfig.Config
|
||||||
DockerVersion string
|
DockerVersion string
|
||||||
@@ -107,11 +90,9 @@ type ImageInspect struct {
|
|||||||
Os string
|
Os string
|
||||||
Size int64
|
Size int64
|
||||||
VirtualSize int64
|
VirtualSize int64
|
||||||
GraphDriver GraphDriverData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Port stores open ports info of container
|
// GET "/containers/json"
|
||||||
// e.g. {"PrivatePort": 8080, "PublicPort": 80, "Type": "tcp"}
|
|
||||||
type Port struct {
|
type Port struct {
|
||||||
IP string `json:",omitempty"`
|
IP string `json:",omitempty"`
|
||||||
PrivatePort int
|
PrivatePort int
|
||||||
@@ -119,63 +100,41 @@ type Port struct {
|
|||||||
Type string
|
Type string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Container contains response of Remote API:
|
|
||||||
// GET "/containers/json"
|
|
||||||
type Container struct {
|
type Container struct {
|
||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
Names []string
|
Names []string
|
||||||
Image string
|
Image string
|
||||||
Command string
|
Command string
|
||||||
Created int64
|
Created int
|
||||||
Ports []Port
|
Ports []Port
|
||||||
SizeRw int64 `json:",omitempty"`
|
SizeRw int `json:",omitempty"`
|
||||||
SizeRootFs int64 `json:",omitempty"`
|
SizeRootFs int `json:",omitempty"`
|
||||||
Labels map[string]string
|
Labels map[string]string
|
||||||
Status string
|
Status string
|
||||||
HostConfig struct {
|
|
||||||
NetworkMode string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyConfig contains request body of Remote API:
|
|
||||||
// POST "/containers/"+containerID+"/copy"
|
// POST "/containers/"+containerID+"/copy"
|
||||||
type CopyConfig struct {
|
type CopyConfig struct {
|
||||||
Resource string
|
Resource string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerPathStat is used to encode the header from
|
|
||||||
// GET "/containers/{name:.*}/archive"
|
|
||||||
// "Name" is the file or directory name.
|
|
||||||
type ContainerPathStat struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
Mode os.FileMode `json:"mode"`
|
|
||||||
Mtime time.Time `json:"mtime"`
|
|
||||||
LinkTarget string `json:"linkTarget"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerProcessList contains response of Remote API:
|
|
||||||
// GET "/containers/{name:.*}/top"
|
// GET "/containers/{name:.*}/top"
|
||||||
type ContainerProcessList struct {
|
type ContainerProcessList struct {
|
||||||
Processes [][]string
|
Processes [][]string
|
||||||
Titles []string
|
Titles []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Version contains response of Remote API:
|
|
||||||
// GET "/version"
|
|
||||||
type Version struct {
|
type Version struct {
|
||||||
Version string
|
Version string
|
||||||
APIVersion version.Version `json:"ApiVersion"`
|
ApiVersion version.Version
|
||||||
GitCommit string
|
GitCommit string
|
||||||
GoVersion string
|
GoVersion string
|
||||||
Os string
|
Os string
|
||||||
Arch string
|
Arch string
|
||||||
KernelVersion string `json:",omitempty"`
|
KernelVersion string `json:",omitempty"`
|
||||||
Experimental bool `json:",omitempty"`
|
Experimental bool `json:",omitempty"`
|
||||||
BuildTime string `json:",omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info contains response of Remote API:
|
|
||||||
// GET "/info"
|
// GET "/info"
|
||||||
type Info struct {
|
type Info struct {
|
||||||
ID string
|
ID string
|
||||||
@@ -185,11 +144,9 @@ type Info struct {
|
|||||||
DriverStatus [][2]string
|
DriverStatus [][2]string
|
||||||
MemoryLimit bool
|
MemoryLimit bool
|
||||||
SwapLimit bool
|
SwapLimit bool
|
||||||
CPUCfsPeriod bool `json:"CpuCfsPeriod"`
|
CpuCfsPeriod bool
|
||||||
CPUCfsQuota bool `json:"CpuCfsQuota"`
|
CpuCfsQuota bool
|
||||||
IPv4Forwarding bool
|
IPv4Forwarding bool
|
||||||
BridgeNfIptables bool
|
|
||||||
BridgeNfIP6tables bool `json:"BridgeNfIp6tables"`
|
|
||||||
Debug bool
|
Debug bool
|
||||||
NFd int
|
NFd int
|
||||||
OomKillDisable bool
|
OomKillDisable bool
|
||||||
@@ -207,15 +164,15 @@ type Info struct {
|
|||||||
NCPU int
|
NCPU int
|
||||||
MemTotal int64
|
MemTotal int64
|
||||||
DockerRootDir string
|
DockerRootDir string
|
||||||
HTTPProxy string `json:"HttpProxy"`
|
HttpProxy string
|
||||||
HTTPSProxy string `json:"HttpsProxy"`
|
HttpsProxy string
|
||||||
NoProxy string
|
NoProxy string
|
||||||
Name string
|
Name string
|
||||||
Labels []string
|
Labels []string
|
||||||
ExperimentalBuild bool
|
ExperimentalBuild bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecStartCheck is a temp struct used by execStart
|
// This struct is a temp struct used by execStart
|
||||||
// Config fields is part of ExecConfig in runconfig package
|
// Config fields is part of ExecConfig in runconfig package
|
||||||
type ExecStartCheck struct {
|
type ExecStartCheck struct {
|
||||||
// ExecStart will first check if it's detached
|
// ExecStart will first check if it's detached
|
||||||
@@ -224,10 +181,7 @@ type ExecStartCheck struct {
|
|||||||
Tty bool
|
Tty bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerState stores container's running state
|
|
||||||
// it's part of ContainerJSONBase and will return by "inspect" command
|
|
||||||
type ContainerState struct {
|
type ContainerState struct {
|
||||||
Status string
|
|
||||||
Running bool
|
Running bool
|
||||||
Paused bool
|
Paused bool
|
||||||
Restarting bool
|
Restarting bool
|
||||||
@@ -236,15 +190,14 @@ type ContainerState struct {
|
|||||||
Pid int
|
Pid int
|
||||||
ExitCode int
|
ExitCode int
|
||||||
Error string
|
Error string
|
||||||
StartedAt string
|
StartedAt time.Time
|
||||||
FinishedAt string
|
FinishedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerJSONBase contains response of Remote API:
|
|
||||||
// GET "/containers/{name:.*}/json"
|
// GET "/containers/{name:.*}/json"
|
||||||
type ContainerJSONBase struct {
|
type ContainerJSONBase struct {
|
||||||
ID string `json:"Id"`
|
Id string
|
||||||
Created string
|
Created time.Time
|
||||||
Path string
|
Path string
|
||||||
Args []string
|
Args []string
|
||||||
State *ContainerState
|
State *ContainerState
|
||||||
@@ -260,66 +213,30 @@ type ContainerJSONBase struct {
|
|||||||
ExecDriver string
|
ExecDriver string
|
||||||
MountLabel string
|
MountLabel string
|
||||||
ProcessLabel string
|
ProcessLabel string
|
||||||
|
Volumes map[string]string
|
||||||
|
VolumesRW map[string]bool
|
||||||
AppArmorProfile string
|
AppArmorProfile string
|
||||||
ExecIDs []string
|
ExecIDs []string
|
||||||
HostConfig *runconfig.HostConfig
|
HostConfig *runconfig.HostConfig
|
||||||
GraphDriver GraphDriverData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerJSON is newly used struct along with MountPoint
|
|
||||||
type ContainerJSON struct {
|
type ContainerJSON struct {
|
||||||
*ContainerJSONBase
|
*ContainerJSONBase
|
||||||
Mounts []MountPoint
|
|
||||||
Config *runconfig.Config
|
Config *runconfig.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerJSONPre120 is a backcompatibility struct along with ContainerConfig.
|
// backcompatibility struct along with ContainerConfig
|
||||||
// Note this is not used by the Windows daemon.
|
type ContainerJSONRaw struct {
|
||||||
type ContainerJSONPre120 struct {
|
|
||||||
*ContainerJSONBase
|
*ContainerJSONBase
|
||||||
Volumes map[string]string
|
Config *ContainerConfig
|
||||||
VolumesRW map[string]bool
|
|
||||||
Config *ContainerConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerConfig is a backcompatibility struct used in ContainerJSONPre120
|
|
||||||
type ContainerConfig struct {
|
type ContainerConfig struct {
|
||||||
*runconfig.Config
|
*runconfig.Config
|
||||||
|
|
||||||
// backward compatibility, they now live in HostConfig
|
// backward compatibility, they now live in HostConfig
|
||||||
Memory int64
|
Memory int64
|
||||||
MemorySwap int64
|
MemorySwap int64
|
||||||
CPUShares int64 `json:"CpuShares"`
|
CpuShares int64
|
||||||
CPUSet string `json:"CpuSet"`
|
Cpuset string
|
||||||
}
|
|
||||||
|
|
||||||
// MountPoint represents a mount point configuration inside the container.
|
|
||||||
type MountPoint struct {
|
|
||||||
Name string `json:",omitempty"`
|
|
||||||
Source string
|
|
||||||
Destination string
|
|
||||||
Driver string `json:",omitempty"`
|
|
||||||
Mode string
|
|
||||||
RW bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Volume represents the configuration of a volume for the remote API
|
|
||||||
type Volume struct {
|
|
||||||
Name string // Name is the name of the volume
|
|
||||||
Driver string // Driver is the Driver name used to create the volume
|
|
||||||
Mountpoint string // Mountpoint is the location on disk of the volume
|
|
||||||
}
|
|
||||||
|
|
||||||
// VolumesListResponse contains the response for the remote API:
|
|
||||||
// GET "/volumes"
|
|
||||||
type VolumesListResponse struct {
|
|
||||||
Volumes []*Volume // Volumes is the list of volumes being returned
|
|
||||||
}
|
|
||||||
|
|
||||||
// VolumeCreateRequest contains the response for the remote API:
|
|
||||||
// POST "/volumes"
|
|
||||||
type VolumeCreateRequest struct {
|
|
||||||
Name string // Name is the requested name of the volume
|
|
||||||
Driver string // Driver is the name of the driver that should be used to create the volume
|
|
||||||
DriverOpts map[string]string // DriverOpts holds the driver specific options to use for when creating the volume.
|
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-29
@@ -5,7 +5,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FlagType is the type of the build flag
|
|
||||||
type FlagType int
|
type FlagType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -13,33 +12,28 @@ const (
|
|||||||
stringType
|
stringType
|
||||||
)
|
)
|
||||||
|
|
||||||
// BFlags contains all flags information for the builder
|
type BuilderFlags struct {
|
||||||
type BFlags struct {
|
|
||||||
Args []string // actual flags/args from cmd line
|
Args []string // actual flags/args from cmd line
|
||||||
flags map[string]*Flag
|
flags map[string]*Flag
|
||||||
used map[string]*Flag
|
used map[string]*Flag
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flag contains all information for a flag
|
|
||||||
type Flag struct {
|
type Flag struct {
|
||||||
bf *BFlags
|
bf *BuilderFlags
|
||||||
name string
|
name string
|
||||||
flagType FlagType
|
flagType FlagType
|
||||||
Value string
|
Value string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBFlags return the new BFlags struct
|
func NewBuilderFlags() *BuilderFlags {
|
||||||
func NewBFlags() *BFlags {
|
return &BuilderFlags{
|
||||||
return &BFlags{
|
|
||||||
flags: make(map[string]*Flag),
|
flags: make(map[string]*Flag),
|
||||||
used: make(map[string]*Flag),
|
used: make(map[string]*Flag),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddBool adds a bool flag to BFlags
|
func (bf *BuilderFlags) AddBool(name string, def bool) *Flag {
|
||||||
// Note, any error will be generated when Parse() is called (see Parse).
|
|
||||||
func (bf *BFlags) AddBool(name string, def bool) *Flag {
|
|
||||||
flag := bf.addFlag(name, boolType)
|
flag := bf.addFlag(name, boolType)
|
||||||
if flag == nil {
|
if flag == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -52,9 +46,7 @@ func (bf *BFlags) AddBool(name string, def bool) *Flag {
|
|||||||
return flag
|
return flag
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddString adds a string flag to BFlags
|
func (bf *BuilderFlags) AddString(name string, def string) *Flag {
|
||||||
// Note, any error will be generated when Parse() is called (see Parse).
|
|
||||||
func (bf *BFlags) AddString(name string, def string) *Flag {
|
|
||||||
flag := bf.addFlag(name, stringType)
|
flag := bf.addFlag(name, stringType)
|
||||||
if flag == nil {
|
if flag == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -63,10 +55,7 @@ func (bf *BFlags) AddString(name string, def string) *Flag {
|
|||||||
return flag
|
return flag
|
||||||
}
|
}
|
||||||
|
|
||||||
// addFlag is a generic func used by the other AddXXX() func
|
func (bf *BuilderFlags) addFlag(name string, flagType FlagType) *Flag {
|
||||||
// to add a new flag to the BFlags struct.
|
|
||||||
// Note, any error will be generated when Parse() is called (see Parse).
|
|
||||||
func (bf *BFlags) addFlag(name string, flagType FlagType) *Flag {
|
|
||||||
if _, ok := bf.flags[name]; ok {
|
if _, ok := bf.flags[name]; ok {
|
||||||
bf.Err = fmt.Errorf("Duplicate flag defined: %s", name)
|
bf.Err = fmt.Errorf("Duplicate flag defined: %s", name)
|
||||||
return nil
|
return nil
|
||||||
@@ -82,7 +71,6 @@ func (bf *BFlags) addFlag(name string, flagType FlagType) *Flag {
|
|||||||
return newFlag
|
return newFlag
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUsed checks if the flag is used
|
|
||||||
func (fl *Flag) IsUsed() bool {
|
func (fl *Flag) IsUsed() bool {
|
||||||
if _, ok := fl.bf.used[fl.name]; ok {
|
if _, ok := fl.bf.used[fl.name]; ok {
|
||||||
return true
|
return true
|
||||||
@@ -90,7 +78,6 @@ func (fl *Flag) IsUsed() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTrue checks if a bool flag is true
|
|
||||||
func (fl *Flag) IsTrue() bool {
|
func (fl *Flag) IsTrue() bool {
|
||||||
if fl.flagType != boolType {
|
if fl.flagType != boolType {
|
||||||
// Should never get here
|
// Should never get here
|
||||||
@@ -99,15 +86,7 @@ func (fl *Flag) IsTrue() bool {
|
|||||||
return fl.Value == "true"
|
return fl.Value == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses and checks if the BFlags is valid.
|
func (bf *BuilderFlags) Parse() error {
|
||||||
// Any error noticed during the AddXXX() funcs will be generated/returned
|
|
||||||
// here. We do this because an error during AddXXX() is more like a
|
|
||||||
// compile time error so it doesn't matter too much when we stop our
|
|
||||||
// processing as long as we do stop it, so this allows the code
|
|
||||||
// around AddXXX() to be just:
|
|
||||||
// defFlag := AddString("desription", "")
|
|
||||||
// w/o needing to add an if-statement around each one.
|
|
||||||
func (bf *BFlags) Parse() error {
|
|
||||||
// If there was an error while defining the possible flags
|
// If there was an error while defining the possible flags
|
||||||
// go ahead and bubble it back up here since we didn't do it
|
// go ahead and bubble it back up here since we didn't do it
|
||||||
// earlier in the processing
|
// earlier in the processing
|
||||||
|
|||||||
+13
-13
@@ -10,7 +10,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf := NewBFlags()
|
bf := NewBuilderFlags()
|
||||||
bf.Args = []string{}
|
bf.Args = []string{}
|
||||||
if err := bf.Parse(); err != nil {
|
if err := bf.Parse(); err != nil {
|
||||||
t.Fatalf("Test1 of %q was supposed to work: %s", bf.Args, err)
|
t.Fatalf("Test1 of %q was supposed to work: %s", bf.Args, err)
|
||||||
@@ -18,7 +18,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBFlags()
|
bf = NewBuilderFlags()
|
||||||
bf.Args = []string{"--"}
|
bf.Args = []string{"--"}
|
||||||
if err := bf.Parse(); err != nil {
|
if err := bf.Parse(); err != nil {
|
||||||
t.Fatalf("Test2 of %q was supposed to work: %s", bf.Args, err)
|
t.Fatalf("Test2 of %q was supposed to work: %s", bf.Args, err)
|
||||||
@@ -26,7 +26,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBFlags()
|
bf = NewBuilderFlags()
|
||||||
flStr1 := bf.AddString("str1", "")
|
flStr1 := bf.AddString("str1", "")
|
||||||
flBool1 := bf.AddBool("bool1", false)
|
flBool1 := bf.AddBool("bool1", false)
|
||||||
bf.Args = []string{}
|
bf.Args = []string{}
|
||||||
@@ -43,7 +43,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBFlags()
|
bf = NewBuilderFlags()
|
||||||
flStr1 = bf.AddString("str1", "HI")
|
flStr1 = bf.AddString("str1", "HI")
|
||||||
flBool1 = bf.AddBool("bool1", false)
|
flBool1 = bf.AddBool("bool1", false)
|
||||||
bf.Args = []string{}
|
bf.Args = []string{}
|
||||||
@@ -67,7 +67,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBFlags()
|
bf = NewBuilderFlags()
|
||||||
flStr1 = bf.AddString("str1", "HI")
|
flStr1 = bf.AddString("str1", "HI")
|
||||||
bf.Args = []string{"--str1"}
|
bf.Args = []string{"--str1"}
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBFlags()
|
bf = NewBuilderFlags()
|
||||||
flStr1 = bf.AddString("str1", "HI")
|
flStr1 = bf.AddString("str1", "HI")
|
||||||
bf.Args = []string{"--str1="}
|
bf.Args = []string{"--str1="}
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBFlags()
|
bf = NewBuilderFlags()
|
||||||
flStr1 = bf.AddString("str1", "HI")
|
flStr1 = bf.AddString("str1", "HI")
|
||||||
bf.Args = []string{"--str1=BYE"}
|
bf.Args = []string{"--str1=BYE"}
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBFlags()
|
bf = NewBuilderFlags()
|
||||||
flBool1 = bf.AddBool("bool1", false)
|
flBool1 = bf.AddBool("bool1", false)
|
||||||
bf.Args = []string{"--bool1"}
|
bf.Args = []string{"--bool1"}
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBFlags()
|
bf = NewBuilderFlags()
|
||||||
flBool1 = bf.AddBool("bool1", false)
|
flBool1 = bf.AddBool("bool1", false)
|
||||||
bf.Args = []string{"--bool1=true"}
|
bf.Args = []string{"--bool1=true"}
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBFlags()
|
bf = NewBuilderFlags()
|
||||||
flBool1 = bf.AddBool("bool1", false)
|
flBool1 = bf.AddBool("bool1", false)
|
||||||
bf.Args = []string{"--bool1=false"}
|
bf.Args = []string{"--bool1=false"}
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBFlags()
|
bf = NewBuilderFlags()
|
||||||
flBool1 = bf.AddBool("bool1", false)
|
flBool1 = bf.AddBool("bool1", false)
|
||||||
bf.Args = []string{"--bool1=false1"}
|
bf.Args = []string{"--bool1=false1"}
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBFlags()
|
bf = NewBuilderFlags()
|
||||||
flBool1 = bf.AddBool("bool1", false)
|
flBool1 = bf.AddBool("bool1", false)
|
||||||
bf.Args = []string{"--bool2"}
|
bf.Args = []string{"--bool2"}
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ func TestBuilderFlags(t *testing.T) {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
bf = NewBFlags()
|
bf = NewBuilderFlags()
|
||||||
flStr1 = bf.AddString("str1", "HI")
|
flStr1 = bf.AddString("str1", "HI")
|
||||||
flBool1 = bf.AddBool("bool1", false)
|
flBool1 = bf.AddBool("bool1", false)
|
||||||
bf.Args = []string{"--bool1", "--str1=BYE"}
|
bf.Args = []string{"--bool1", "--str1=BYE"}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// Package command contains the set of Dockerfile commands.
|
// This package contains the set of Dockerfile commands.
|
||||||
package command
|
package command
|
||||||
|
|
||||||
// Define constants for the command strings
|
|
||||||
const (
|
const (
|
||||||
Env = "env"
|
Env = "env"
|
||||||
Label = "label"
|
Label = "label"
|
||||||
|
|||||||
+42
-40
@@ -10,7 +10,6 @@ package builder
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -18,10 +17,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/nat"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/nat"
|
|
||||||
"github.com/docker/docker/pkg/stringutils"
|
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,7 +29,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// dispatch with no layer / parsing. This is effectively not a command.
|
// dispatch with no layer / parsing. This is effectively not a command.
|
||||||
func nullDispatch(b *builder, args []string, attributes map[string]bool, original string) error {
|
func nullDispatch(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +38,10 @@ func nullDispatch(b *builder, args []string, attributes map[string]bool, origina
|
|||||||
// Sets the environment variable foo to bar, also makes interpolation
|
// Sets the environment variable foo to bar, also makes interpolation
|
||||||
// in the dockerfile available from the next statement on via ${foo}.
|
// in the dockerfile available from the next statement on via ${foo}.
|
||||||
//
|
//
|
||||||
func env(b *builder, args []string, attributes map[string]bool, original string) error {
|
func env(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return fmt.Errorf("ENV is not supported on Windows.")
|
||||||
|
}
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("ENV requires at least one argument")
|
return fmt.Errorf("ENV requires at least one argument")
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ func env(b *builder, args []string, attributes map[string]bool, original string)
|
|||||||
// MAINTAINER some text <maybe@an.email.address>
|
// MAINTAINER some text <maybe@an.email.address>
|
||||||
//
|
//
|
||||||
// Sets the maintainer metadata.
|
// Sets the maintainer metadata.
|
||||||
func maintainer(b *builder, args []string, attributes map[string]bool, original string) error {
|
func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return fmt.Errorf("MAINTAINER requires exactly one argument")
|
return fmt.Errorf("MAINTAINER requires exactly one argument")
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,7 @@ func maintainer(b *builder, args []string, attributes map[string]bool, original
|
|||||||
//
|
//
|
||||||
// Sets the Label variable foo to bar,
|
// Sets the Label variable foo to bar,
|
||||||
//
|
//
|
||||||
func label(b *builder, args []string, attributes map[string]bool, original string) error {
|
func label(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("LABEL requires at least one argument")
|
return fmt.Errorf("LABEL requires at least one argument")
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ func label(b *builder, args []string, attributes map[string]bool, original strin
|
|||||||
// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
|
// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
|
||||||
// exist here. If you do not wish to have this automatic handling, use COPY.
|
// exist here. If you do not wish to have this automatic handling, use COPY.
|
||||||
//
|
//
|
||||||
func add(b *builder, args []string, attributes map[string]bool, original string) error {
|
func add(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return fmt.Errorf("ADD requires at least two arguments")
|
return fmt.Errorf("ADD requires at least two arguments")
|
||||||
}
|
}
|
||||||
@@ -169,7 +169,7 @@ func add(b *builder, args []string, attributes map[string]bool, original string)
|
|||||||
//
|
//
|
||||||
// Same as 'ADD' but without the tar and remote url handling.
|
// Same as 'ADD' but without the tar and remote url handling.
|
||||||
//
|
//
|
||||||
func dispatchCopy(b *builder, args []string, attributes map[string]bool, original string) error {
|
func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return fmt.Errorf("COPY requires at least two arguments")
|
return fmt.Errorf("COPY requires at least two arguments")
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@ func dispatchCopy(b *builder, args []string, attributes map[string]bool, origina
|
|||||||
//
|
//
|
||||||
// This sets the image the dockerfile will build on top of.
|
// This sets the image the dockerfile will build on top of.
|
||||||
//
|
//
|
||||||
func from(b *builder, args []string, attributes map[string]bool, original string) error {
|
func from(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return fmt.Errorf("FROM requires one argument")
|
return fmt.Errorf("FROM requires one argument")
|
||||||
}
|
}
|
||||||
@@ -196,11 +196,7 @@ func from(b *builder, args []string, attributes map[string]bool, original string
|
|||||||
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
// Windows cannot support a container with no base image.
|
|
||||||
if name == NoBaseImageSpecifier {
|
if name == NoBaseImageSpecifier {
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return fmt.Errorf("Windows does not support FROM scratch")
|
|
||||||
}
|
|
||||||
b.image = ""
|
b.image = ""
|
||||||
b.noBaseImage = true
|
b.noBaseImage = true
|
||||||
return nil
|
return nil
|
||||||
@@ -237,7 +233,7 @@ func from(b *builder, args []string, attributes map[string]bool, original string
|
|||||||
// special cases. search for 'OnBuild' in internals.go for additional special
|
// special cases. search for 'OnBuild' in internals.go for additional special
|
||||||
// cases.
|
// cases.
|
||||||
//
|
//
|
||||||
func onbuild(b *builder, args []string, attributes map[string]bool, original string) error {
|
func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("ONBUILD requires at least one argument")
|
return fmt.Errorf("ONBUILD requires at least one argument")
|
||||||
}
|
}
|
||||||
@@ -264,7 +260,7 @@ func onbuild(b *builder, args []string, attributes map[string]bool, original str
|
|||||||
//
|
//
|
||||||
// Set the working directory for future RUN/CMD/etc statements.
|
// Set the working directory for future RUN/CMD/etc statements.
|
||||||
//
|
//
|
||||||
func workdir(b *builder, args []string, attributes map[string]bool, original string) error {
|
func workdir(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return fmt.Errorf("WORKDIR requires exactly one argument")
|
return fmt.Errorf("WORKDIR requires exactly one argument")
|
||||||
}
|
}
|
||||||
@@ -273,13 +269,10 @@ func workdir(b *builder, args []string, attributes map[string]bool, original str
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is from the Dockerfile and will not necessarily be in platform
|
workdir := args[0]
|
||||||
// specific semantics, hence ensure it is converted.
|
|
||||||
workdir := filepath.FromSlash(args[0])
|
|
||||||
|
|
||||||
if !system.IsAbs(workdir) {
|
if !filepath.IsAbs(workdir) {
|
||||||
current := filepath.FromSlash(b.Config.WorkingDir)
|
workdir = filepath.Join("/", b.Config.WorkingDir, workdir)
|
||||||
workdir = filepath.Join(string(os.PathSeparator), current, workdir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Config.WorkingDir = workdir
|
b.Config.WorkingDir = workdir
|
||||||
@@ -297,7 +290,7 @@ func workdir(b *builder, args []string, attributes map[string]bool, original str
|
|||||||
// RUN echo hi # cmd /S /C echo hi (Windows)
|
// RUN echo hi # cmd /S /C echo hi (Windows)
|
||||||
// RUN [ "echo", "hi" ] # echo hi
|
// RUN [ "echo", "hi" ] # echo hi
|
||||||
//
|
//
|
||||||
func run(b *builder, args []string, attributes map[string]bool, original string) error {
|
func run(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if b.image == "" && !b.noBaseImage {
|
if b.image == "" && !b.noBaseImage {
|
||||||
return fmt.Errorf("Please provide a source image with `from` prior to run")
|
return fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||||
}
|
}
|
||||||
@@ -306,7 +299,7 @@ func run(b *builder, args []string, attributes map[string]bool, original string)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
args = handleJSONArgs(args, attributes)
|
args = handleJsonArgs(args, attributes)
|
||||||
|
|
||||||
if !attributes["json"] {
|
if !attributes["json"] {
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
@@ -330,7 +323,7 @@ func run(b *builder, args []string, attributes map[string]bool, original string)
|
|||||||
b.Config.Cmd = config.Cmd
|
b.Config.Cmd = config.Cmd
|
||||||
runconfig.Merge(b.Config, config)
|
runconfig.Merge(b.Config, config)
|
||||||
|
|
||||||
defer func(cmd *stringutils.StrSlice) { b.Config.Cmd = cmd }(cmd)
|
defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
|
||||||
|
|
||||||
logrus.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd)
|
logrus.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd)
|
||||||
|
|
||||||
@@ -368,12 +361,12 @@ func run(b *builder, args []string, attributes map[string]bool, original string)
|
|||||||
// Set the default command to run in the container (which may be empty).
|
// Set the default command to run in the container (which may be empty).
|
||||||
// Argument handling is the same as RUN.
|
// Argument handling is the same as RUN.
|
||||||
//
|
//
|
||||||
func cmd(b *builder, args []string, attributes map[string]bool, original string) error {
|
func cmd(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
if err := b.BuilderFlags.Parse(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdSlice := handleJSONArgs(args, attributes)
|
cmdSlice := handleJsonArgs(args, attributes)
|
||||||
|
|
||||||
if !attributes["json"] {
|
if !attributes["json"] {
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
@@ -383,7 +376,7 @@ func cmd(b *builder, args []string, attributes map[string]bool, original string)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Config.Cmd = stringutils.NewStrSlice(cmdSlice...)
|
b.Config.Cmd = runconfig.NewCommand(cmdSlice...)
|
||||||
|
|
||||||
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
|
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -404,26 +397,26 @@ func cmd(b *builder, args []string, attributes map[string]bool, original string)
|
|||||||
// Handles command processing similar to CMD and RUN, only b.Config.Entrypoint
|
// Handles command processing similar to CMD and RUN, only b.Config.Entrypoint
|
||||||
// is initialized at NewBuilder time instead of through argument parsing.
|
// is initialized at NewBuilder time instead of through argument parsing.
|
||||||
//
|
//
|
||||||
func entrypoint(b *builder, args []string, attributes map[string]bool, original string) error {
|
func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if err := b.BuilderFlags.Parse(); err != nil {
|
if err := b.BuilderFlags.Parse(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed := handleJSONArgs(args, attributes)
|
parsed := handleJsonArgs(args, attributes)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case attributes["json"]:
|
case attributes["json"]:
|
||||||
// ENTRYPOINT ["echo", "hi"]
|
// ENTRYPOINT ["echo", "hi"]
|
||||||
b.Config.Entrypoint = stringutils.NewStrSlice(parsed...)
|
b.Config.Entrypoint = runconfig.NewEntrypoint(parsed...)
|
||||||
case len(parsed) == 0:
|
case len(parsed) == 0:
|
||||||
// ENTRYPOINT []
|
// ENTRYPOINT []
|
||||||
b.Config.Entrypoint = nil
|
b.Config.Entrypoint = nil
|
||||||
default:
|
default:
|
||||||
// ENTRYPOINT echo hi
|
// ENTRYPOINT echo hi
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
b.Config.Entrypoint = stringutils.NewStrSlice("/bin/sh", "-c", parsed[0])
|
b.Config.Entrypoint = runconfig.NewEntrypoint("/bin/sh", "-c", parsed[0])
|
||||||
} else {
|
} else {
|
||||||
b.Config.Entrypoint = stringutils.NewStrSlice("cmd", "/S /C", parsed[0])
|
b.Config.Entrypoint = runconfig.NewEntrypoint("cmd", "/S /C", parsed[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,7 +438,7 @@ func entrypoint(b *builder, args []string, attributes map[string]bool, original
|
|||||||
// Expose ports for links and port mappings. This all ends up in
|
// Expose ports for links and port mappings. This all ends up in
|
||||||
// b.Config.ExposedPorts for runconfig.
|
// b.Config.ExposedPorts for runconfig.
|
||||||
//
|
//
|
||||||
func expose(b *builder, args []string, attributes map[string]bool, original string) error {
|
func expose(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
portsTab := args
|
portsTab := args
|
||||||
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
@@ -460,11 +453,19 @@ func expose(b *builder, args []string, attributes map[string]bool, original stri
|
|||||||
b.Config.ExposedPorts = make(nat.PortSet)
|
b.Config.ExposedPorts = make(nat.PortSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
ports, _, err := nat.ParsePortSpecs(portsTab)
|
ports, bindingMap, err := nat.ParsePortSpecs(append(portsTab, b.Config.PortSpecs...))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, bindings := range bindingMap {
|
||||||
|
if bindings[0].HostIp != "" || bindings[0].HostPort != "" {
|
||||||
|
fmt.Fprintf(b.ErrStream, " ---> Using Dockerfile's EXPOSE instruction"+
|
||||||
|
" to map host ports to container ports (ip:hostPort:containerPort) is deprecated.\n"+
|
||||||
|
" Please use -p to publish the ports.\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// instead of using ports directly, we build a list of ports and sort it so
|
// instead of using ports directly, we build a list of ports and sort it so
|
||||||
// the order is consistent. This prevents cache burst where map ordering
|
// the order is consistent. This prevents cache burst where map ordering
|
||||||
// changes between builds
|
// changes between builds
|
||||||
@@ -478,6 +479,7 @@ func expose(b *builder, args []string, attributes map[string]bool, original stri
|
|||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
sort.Strings(portList)
|
sort.Strings(portList)
|
||||||
|
b.Config.PortSpecs = nil
|
||||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " ")))
|
return b.commit("", b.Config.Cmd, fmt.Sprintf("EXPOSE %s", strings.Join(portList, " ")))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,9 +488,9 @@ func expose(b *builder, args []string, attributes map[string]bool, original stri
|
|||||||
// Set the user to 'foo' for future commands and when running the
|
// Set the user to 'foo' for future commands and when running the
|
||||||
// ENTRYPOINT/CMD at container run time.
|
// ENTRYPOINT/CMD at container run time.
|
||||||
//
|
//
|
||||||
func user(b *builder, args []string, attributes map[string]bool, original string) error {
|
func user(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return fmt.Errorf("USER is not supported on Windows")
|
return fmt.Errorf("USER is not supported on Windows.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
@@ -507,9 +509,9 @@ func user(b *builder, args []string, attributes map[string]bool, original string
|
|||||||
//
|
//
|
||||||
// Expose the volume /foo for use. Will also accept the JSON array form.
|
// Expose the volume /foo for use. Will also accept the JSON array form.
|
||||||
//
|
//
|
||||||
func volume(b *builder, args []string, attributes map[string]bool, original string) error {
|
func volume(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return fmt.Errorf("VOLUME is not supported on Windows")
|
return fmt.Errorf("VOLUME is not supported on Windows.")
|
||||||
}
|
}
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("VOLUME requires at least one argument")
|
return fmt.Errorf("VOLUME requires at least one argument")
|
||||||
|
|||||||
+12
-38
@@ -24,7 +24,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
@@ -38,7 +37,6 @@ import (
|
|||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
"github.com/docker/docker/pkg/symlink"
|
"github.com/docker/docker/pkg/symlink"
|
||||||
"github.com/docker/docker/pkg/tarsum"
|
"github.com/docker/docker/pkg/tarsum"
|
||||||
"github.com/docker/docker/pkg/ulimit"
|
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
@@ -55,10 +53,10 @@ var replaceEnvAllowed = map[string]struct{}{
|
|||||||
command.User: {},
|
command.User: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
var evaluateTable map[string]func(*builder, []string, map[string]bool, string) error
|
var evaluateTable map[string]func(*Builder, []string, map[string]bool, string) error
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
evaluateTable = map[string]func(*builder, []string, map[string]bool, string) error{
|
evaluateTable = map[string]func(*Builder, []string, map[string]bool, string) error{
|
||||||
command.Env: env,
|
command.Env: env,
|
||||||
command.Label: label,
|
command.Label: label,
|
||||||
command.Maintainer: maintainer,
|
command.Maintainer: maintainer,
|
||||||
@@ -76,9 +74,9 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// builder is an internal struct, used to maintain configuration of the Dockerfile's
|
// internal struct, used to maintain configuration of the Dockerfile's
|
||||||
// processing as it evaluates the parsing result.
|
// processing as it evaluates the parsing result.
|
||||||
type builder struct {
|
type Builder struct {
|
||||||
Daemon *daemon.Daemon
|
Daemon *daemon.Daemon
|
||||||
|
|
||||||
// effectively stdio for the run. Because it is not stdio, I said
|
// effectively stdio for the run. Because it is not stdio, I said
|
||||||
@@ -100,8 +98,8 @@ type builder struct {
|
|||||||
// the final configs of the Dockerfile but dont want the layers
|
// the final configs of the Dockerfile but dont want the layers
|
||||||
disableCommit bool
|
disableCommit bool
|
||||||
|
|
||||||
// Registry server auth configs used to pull images when handling `FROM`.
|
AuthConfig *cliconfig.AuthConfig
|
||||||
AuthConfigs map[string]cliconfig.AuthConfig
|
ConfigFile *cliconfig.ConfigFile
|
||||||
|
|
||||||
// Deprecated, original writer used for ImagePull. To be removed.
|
// Deprecated, original writer used for ImagePull. To be removed.
|
||||||
OutOld io.Writer
|
OutOld io.Writer
|
||||||
@@ -117,7 +115,7 @@ type builder struct {
|
|||||||
image string // image name for commit processing
|
image string // image name for commit processing
|
||||||
maintainer string // maintainer name. could probably be removed.
|
maintainer string // maintainer name. could probably be removed.
|
||||||
cmdSet bool // indicates is CMD was set in current Dockerfile
|
cmdSet bool // indicates is CMD was set in current Dockerfile
|
||||||
BuilderFlags *BFlags // current cmd's BuilderFlags - temporary
|
BuilderFlags *BuilderFlags // current cmd's BuilderFlags - temporary
|
||||||
context tarsum.TarSum // the context is a tarball that is uploaded by the client
|
context tarsum.TarSum // the context is a tarball that is uploaded by the client
|
||||||
contextPath string // the path of the temporary directory the local context is unpacked to (server side)
|
contextPath string // the path of the temporary directory the local context is unpacked to (server side)
|
||||||
noBaseImage bool // indicates that this build does not start from any base image, but is being built from an empty file system.
|
noBaseImage bool // indicates that this build does not start from any base image, but is being built from an empty file system.
|
||||||
@@ -131,12 +129,8 @@ type builder struct {
|
|||||||
cgroupParent string
|
cgroupParent string
|
||||||
memory int64
|
memory int64
|
||||||
memorySwap int64
|
memorySwap int64
|
||||||
ulimits []*ulimit.Ulimit
|
|
||||||
|
|
||||||
cancelled <-chan struct{} // When closed, job was cancelled.
|
cancelled <-chan struct{} // When closed, job was cancelled.
|
||||||
|
|
||||||
activeImages []string
|
|
||||||
id string // Used to hold reference images
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the builder with the context. This is the lynchpin of this package. This
|
// Run the builder with the context. This is the lynchpin of this package. This
|
||||||
@@ -151,7 +145,7 @@ type builder struct {
|
|||||||
// processing.
|
// processing.
|
||||||
// * Print a happy message and return the image ID.
|
// * Print a happy message and return the image ID.
|
||||||
//
|
//
|
||||||
func (b *builder) Run(context io.Reader) (string, error) {
|
func (b *Builder) Run(context io.Reader) (string, error) {
|
||||||
if err := b.readContext(context); err != nil {
|
if err := b.readContext(context); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -202,7 +196,7 @@ func (b *builder) Run(context io.Reader) (string, error) {
|
|||||||
|
|
||||||
// Reads a Dockerfile from the current context. It assumes that the
|
// Reads a Dockerfile from the current context. It assumes that the
|
||||||
// 'filename' is a relative path from the root of the context
|
// 'filename' is a relative path from the root of the context
|
||||||
func (b *builder) readDockerfile() error {
|
func (b *Builder) readDockerfile() error {
|
||||||
// If no -f was specified then look for 'Dockerfile'. If we can't find
|
// If no -f was specified then look for 'Dockerfile'. If we can't find
|
||||||
// that then look for 'dockerfile'. If neither are found then default
|
// that then look for 'dockerfile'. If neither are found then default
|
||||||
// back to 'Dockerfile' and use that in the error message.
|
// back to 'Dockerfile' and use that in the error message.
|
||||||
@@ -280,20 +274,13 @@ func (b *builder) readDockerfile() error {
|
|||||||
// such as `RUN` in ONBUILD RUN foo. There is special case logic in here to
|
// such as `RUN` in ONBUILD RUN foo. There is special case logic in here to
|
||||||
// deal with that, at least until it becomes more of a general concern with new
|
// deal with that, at least until it becomes more of a general concern with new
|
||||||
// features.
|
// features.
|
||||||
func (b *builder) dispatch(stepN int, ast *parser.Node) error {
|
func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
|
||||||
cmd := ast.Value
|
cmd := ast.Value
|
||||||
|
|
||||||
// To ensure the user is give a decent error message if the platform
|
|
||||||
// on which the daemon is running does not support a builder command.
|
|
||||||
if err := platformSupports(strings.ToLower(cmd)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs := ast.Attributes
|
attrs := ast.Attributes
|
||||||
original := ast.Original
|
original := ast.Original
|
||||||
flags := ast.Flags
|
flags := ast.Flags
|
||||||
strs := []string{}
|
strs := []string{}
|
||||||
msg := fmt.Sprintf("Step %d : %s", stepN+1, strings.ToUpper(cmd))
|
msg := fmt.Sprintf("Step %d : %s", stepN, strings.ToUpper(cmd))
|
||||||
|
|
||||||
if len(ast.Flags) > 0 {
|
if len(ast.Flags) > 0 {
|
||||||
msg += " " + strings.Join(ast.Flags, " ")
|
msg += " " + strings.Join(ast.Flags, " ")
|
||||||
@@ -350,23 +337,10 @@ func (b *builder) dispatch(stepN int, ast *parser.Node) error {
|
|||||||
// XXX yes, we skip any cmds that are not valid; the parser should have
|
// XXX yes, we skip any cmds that are not valid; the parser should have
|
||||||
// picked these out already.
|
// picked these out already.
|
||||||
if f, ok := evaluateTable[cmd]; ok {
|
if f, ok := evaluateTable[cmd]; ok {
|
||||||
b.BuilderFlags = NewBFlags()
|
b.BuilderFlags = NewBuilderFlags()
|
||||||
b.BuilderFlags.Args = flags
|
b.BuilderFlags.Args = flags
|
||||||
return f(b, strList, attrs, original)
|
return f(b, strList, attrs, original)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("Unknown instruction: %s", strings.ToUpper(cmd))
|
return fmt.Errorf("Unknown instruction: %s", strings.ToUpper(cmd))
|
||||||
}
|
}
|
||||||
|
|
||||||
// platformSupports is a short-term function to give users a quality error
|
|
||||||
// message if a Dockerfile uses a command not supported on the platform.
|
|
||||||
func platformSupports(command string) error {
|
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch command {
|
|
||||||
case "expose", "volume", "user":
|
|
||||||
return fmt.Errorf("The daemon on this platform does not support the command '%s'", command)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
+113
-141
@@ -12,8 +12,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -21,10 +21,9 @@ import (
|
|||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/builder/parser"
|
"github.com/docker/docker/builder/parser"
|
||||||
"github.com/docker/docker/cliconfig"
|
|
||||||
"github.com/docker/docker/daemon"
|
"github.com/docker/docker/daemon"
|
||||||
"github.com/docker/docker/graph"
|
"github.com/docker/docker/graph"
|
||||||
"github.com/docker/docker/image"
|
imagepkg "github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/chrootarchive"
|
"github.com/docker/docker/pkg/chrootarchive"
|
||||||
"github.com/docker/docker/pkg/httputils"
|
"github.com/docker/docker/pkg/httputils"
|
||||||
@@ -33,7 +32,6 @@ import (
|
|||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/pkg/progressreader"
|
"github.com/docker/docker/pkg/progressreader"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
"github.com/docker/docker/pkg/stringutils"
|
|
||||||
"github.com/docker/docker/pkg/system"
|
"github.com/docker/docker/pkg/system"
|
||||||
"github.com/docker/docker/pkg/tarsum"
|
"github.com/docker/docker/pkg/tarsum"
|
||||||
"github.com/docker/docker/pkg/urlutil"
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
@@ -41,40 +39,30 @@ import (
|
|||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *builder) readContext(context io.Reader) (err error) {
|
func (b *Builder) readContext(context io.Reader) error {
|
||||||
tmpdirPath, err := ioutil.TempDir("", "docker-build")
|
tmpdirPath, err := ioutil.TempDir("", "docker-build")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we clean-up upon error. In the happy case the caller
|
|
||||||
// is expected to manage the clean-up
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
if e := os.RemoveAll(tmpdirPath); e != nil {
|
|
||||||
logrus.Debugf("[BUILDER] failed to remove temporary context: %s", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
decompressedStream, err := archive.DecompressStream(context)
|
decompressedStream, err := archive.DecompressStream(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.context, err = tarsum.NewTarSum(decompressedStream, true, tarsum.Version1); err != nil {
|
if b.context, err = tarsum.NewTarSum(decompressedStream, true, tarsum.Version0); err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = chrootarchive.Untar(b.context, tmpdirPath, nil); err != nil {
|
if err := chrootarchive.Untar(b.context, tmpdirPath, nil); err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
b.contextPath = tmpdirPath
|
b.contextPath = tmpdirPath
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *builder) commit(id string, autoCmd *stringutils.StrSlice, comment string) error {
|
func (b *Builder) commit(id string, autoCmd *runconfig.Command, comment string) error {
|
||||||
if b.disableCommit {
|
if b.disableCommit {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -84,12 +72,8 @@ func (b *builder) commit(id string, autoCmd *stringutils.StrSlice, comment strin
|
|||||||
b.Config.Image = b.image
|
b.Config.Image = b.image
|
||||||
if id == "" {
|
if id == "" {
|
||||||
cmd := b.Config.Cmd
|
cmd := b.Config.Cmd
|
||||||
if runtime.GOOS != "windows" {
|
b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", "#(nop) "+comment)
|
||||||
b.Config.Cmd = stringutils.NewStrSlice("/bin/sh", "-c", "#(nop) "+comment)
|
defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
|
||||||
} else {
|
|
||||||
b.Config.Cmd = stringutils.NewStrSlice("cmd", "/S /C", "REM (nop) "+comment)
|
|
||||||
}
|
|
||||||
defer func(cmd *stringutils.StrSlice) { b.Config.Cmd = cmd }(cmd)
|
|
||||||
|
|
||||||
hit, err := b.probeCache()
|
hit, err := b.probeCache()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -119,19 +103,11 @@ func (b *builder) commit(id string, autoCmd *stringutils.StrSlice, comment strin
|
|||||||
autoConfig := *b.Config
|
autoConfig := *b.Config
|
||||||
autoConfig.Cmd = autoCmd
|
autoConfig.Cmd = autoCmd
|
||||||
|
|
||||||
commitCfg := &daemon.ContainerCommitConfig{
|
|
||||||
Author: b.maintainer,
|
|
||||||
Pause: true,
|
|
||||||
Config: &autoConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit the container
|
// Commit the container
|
||||||
image, err := b.Daemon.Commit(container, commitCfg)
|
image, err := b.Daemon.Commit(container, "", "", "", b.maintainer, true, &autoConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b.Daemon.Graph().Retain(b.id, image.ID)
|
|
||||||
b.activeImages = append(b.activeImages, image.ID)
|
|
||||||
b.image = image.ID
|
b.image = image.ID
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -144,7 +120,7 @@ type copyInfo struct {
|
|||||||
tmpDir string
|
tmpDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *builder) runContextCommand(args []string, allowRemote bool, allowDecompression bool, cmdName string) error {
|
func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecompression bool, cmdName string) error {
|
||||||
if b.context == nil {
|
if b.context == nil {
|
||||||
return fmt.Errorf("No context given. Impossible to use %s", cmdName)
|
return fmt.Errorf("No context given. Impossible to use %s", cmdName)
|
||||||
}
|
}
|
||||||
@@ -153,8 +129,7 @@ func (b *builder) runContextCommand(args []string, allowRemote bool, allowDecomp
|
|||||||
return fmt.Errorf("Invalid %s format - at least two arguments required", cmdName)
|
return fmt.Errorf("Invalid %s format - at least two arguments required", cmdName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Work in daemon-specific filepath semantics
|
dest := args[len(args)-1] // last one is always the dest
|
||||||
dest := filepath.FromSlash(args[len(args)-1]) // last one is always the dest
|
|
||||||
|
|
||||||
copyInfos := []*copyInfo{}
|
copyInfos := []*copyInfo{}
|
||||||
|
|
||||||
@@ -189,7 +164,8 @@ func (b *builder) runContextCommand(args []string, allowRemote bool, allowDecomp
|
|||||||
if len(copyInfos) == 0 {
|
if len(copyInfos) == 0 {
|
||||||
return fmt.Errorf("No source files were specified")
|
return fmt.Errorf("No source files were specified")
|
||||||
}
|
}
|
||||||
if len(copyInfos) > 1 && !strings.HasSuffix(dest, string(os.PathSeparator)) {
|
|
||||||
|
if len(copyInfos) > 1 && !strings.HasSuffix(dest, "/") {
|
||||||
return fmt.Errorf("When using %s with more than one source file, the destination must be a directory and end with a /", cmdName)
|
return fmt.Errorf("When using %s with more than one source file, the destination must be a directory and end with a /", cmdName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,12 +191,8 @@ func (b *builder) runContextCommand(args []string, allowRemote bool, allowDecomp
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd := b.Config.Cmd
|
cmd := b.Config.Cmd
|
||||||
if runtime.GOOS != "windows" {
|
b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest))
|
||||||
b.Config.Cmd = stringutils.NewStrSlice("/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest))
|
defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
|
||||||
} else {
|
|
||||||
b.Config.Cmd = stringutils.NewStrSlice("cmd", "/S /C", fmt.Sprintf("REM (nop) %s %s in %s", cmdName, srcHash, dest))
|
|
||||||
}
|
|
||||||
defer func(cmd *stringutils.StrSlice) { b.Config.Cmd = cmd }(cmd)
|
|
||||||
|
|
||||||
hit, err := b.probeCache()
|
hit, err := b.probeCache()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -231,7 +203,7 @@ func (b *builder) runContextCommand(args []string, allowRemote bool, allowDecomp
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
container, _, err := b.Daemon.ContainerCreate("", b.Config, nil, true)
|
container, _, err := b.Daemon.Create(b.Config, nil, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -254,39 +226,27 @@ func (b *builder) runContextCommand(args []string, allowRemote bool, allowDecomp
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath string, destPath string, allowRemote bool, allowDecompression bool, allowWildcards bool) error {
|
func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath string, destPath string, allowRemote bool, allowDecompression bool, allowWildcards bool) error {
|
||||||
|
|
||||||
// Work in daemon-specific OS filepath semantics. However, we save
|
if origPath != "" && origPath[0] == '/' && len(origPath) > 1 {
|
||||||
// the the origPath passed in here, as it might also be a URL which
|
|
||||||
// we need to check for in this function.
|
|
||||||
passedInOrigPath := origPath
|
|
||||||
origPath = filepath.FromSlash(origPath)
|
|
||||||
destPath = filepath.FromSlash(destPath)
|
|
||||||
|
|
||||||
if origPath != "" && origPath[0] == os.PathSeparator && len(origPath) > 1 {
|
|
||||||
origPath = origPath[1:]
|
origPath = origPath[1:]
|
||||||
}
|
}
|
||||||
origPath = strings.TrimPrefix(origPath, "."+string(os.PathSeparator))
|
origPath = strings.TrimPrefix(origPath, "./")
|
||||||
|
|
||||||
// Twiddle the destPath when its a relative path - meaning, make it
|
// Twiddle the destPath when its a relative path - meaning, make it
|
||||||
// relative to the WORKINGDIR
|
// relative to the WORKINGDIR
|
||||||
if !system.IsAbs(destPath) {
|
if !filepath.IsAbs(destPath) {
|
||||||
hasSlash := strings.HasSuffix(destPath, string(os.PathSeparator))
|
hasSlash := strings.HasSuffix(destPath, "/")
|
||||||
destPath = filepath.Join(string(os.PathSeparator), filepath.FromSlash(b.Config.WorkingDir), destPath)
|
destPath = filepath.Join("/", b.Config.WorkingDir, destPath)
|
||||||
|
|
||||||
// Make sure we preserve any trailing slash
|
// Make sure we preserve any trailing slash
|
||||||
if hasSlash {
|
if hasSlash {
|
||||||
destPath += string(os.PathSeparator)
|
destPath += "/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the remote/URL case, download it and gen its hashcode
|
// In the remote/URL case, download it and gen its hashcode
|
||||||
if urlutil.IsURL(passedInOrigPath) {
|
if urlutil.IsURL(origPath) {
|
||||||
|
|
||||||
// As it's a URL, we go back to processing on what was passed in
|
|
||||||
// to this function
|
|
||||||
origPath = passedInOrigPath
|
|
||||||
|
|
||||||
if !allowRemote {
|
if !allowRemote {
|
||||||
return fmt.Errorf("Source can't be a URL for %s", cmdName)
|
return fmt.Errorf("Source can't be a URL for %s", cmdName)
|
||||||
}
|
}
|
||||||
@@ -312,7 +272,7 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
|||||||
ci.tmpDir = tmpDirName
|
ci.tmpDir = tmpDirName
|
||||||
|
|
||||||
// Create a tmp file within our tmp dir
|
// Create a tmp file within our tmp dir
|
||||||
tmpFileName := filepath.Join(tmpDirName, "tmp")
|
tmpFileName := path.Join(tmpDirName, "tmp")
|
||||||
tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -323,7 +283,7 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
|||||||
In: resp.Body,
|
In: resp.Body,
|
||||||
Out: b.OutOld,
|
Out: b.OutOld,
|
||||||
Formatter: b.StreamFormatter,
|
Formatter: b.StreamFormatter,
|
||||||
Size: resp.ContentLength,
|
Size: int(resp.ContentLength),
|
||||||
NewLines: true,
|
NewLines: true,
|
||||||
ID: "",
|
ID: "",
|
||||||
Action: "Downloading",
|
Action: "Downloading",
|
||||||
@@ -348,26 +308,23 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Windows does not support UtimesNano.
|
if err := system.UtimesNano(tmpFileName, times); err != nil {
|
||||||
if runtime.GOOS != "windows" {
|
return err
|
||||||
if err := system.UtimesNano(tmpFileName, times); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ci.origPath = filepath.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
|
ci.origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
|
||||||
|
|
||||||
// If the destination is a directory, figure out the filename.
|
// If the destination is a directory, figure out the filename.
|
||||||
if strings.HasSuffix(ci.destPath, string(os.PathSeparator)) {
|
if strings.HasSuffix(ci.destPath, "/") {
|
||||||
u, err := url.Parse(origPath)
|
u, err := url.Parse(origPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
path := filepath.FromSlash(u.Path) // Ensure in platform semantics
|
path := u.Path
|
||||||
if strings.HasSuffix(path, string(os.PathSeparator)) {
|
if strings.HasSuffix(path, "/") {
|
||||||
path = path[:len(path)-1]
|
path = path[:len(path)-1]
|
||||||
}
|
}
|
||||||
parts := strings.Split(path, string(os.PathSeparator))
|
parts := strings.Split(path, "/")
|
||||||
filename := parts[len(parts)-1]
|
filename := parts[len(parts)-1]
|
||||||
if filename == "" {
|
if filename == "" {
|
||||||
return fmt.Errorf("cannot determine filename from url: %s", u)
|
return fmt.Errorf("cannot determine filename from url: %s", u)
|
||||||
@@ -380,7 +337,7 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tarSum, err := tarsum.NewTarSum(r, true, tarsum.Version1)
|
tarSum, err := tarsum.NewTarSum(r, true, tarsum.Version0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -394,12 +351,12 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Deal with wildcards
|
// Deal with wildcards
|
||||||
if allowWildcards && containsWildcards(origPath) {
|
if allowWildcards && ContainsWildcards(origPath) {
|
||||||
for _, fileInfo := range b.context.GetSums() {
|
for _, fileInfo := range b.context.GetSums() {
|
||||||
if fileInfo.Name() == "" {
|
if fileInfo.Name() == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
match, _ := filepath.Match(origPath, fileInfo.Name())
|
match, _ := path.Match(origPath, fileInfo.Name())
|
||||||
if !match {
|
if !match {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -416,7 +373,7 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
|||||||
if err := b.checkPathForAddition(origPath); err != nil {
|
if err := b.checkPathForAddition(origPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fi, _ := os.Stat(filepath.Join(b.contextPath, origPath))
|
fi, _ := os.Stat(path.Join(b.contextPath, origPath))
|
||||||
|
|
||||||
ci := copyInfo{}
|
ci := copyInfo{}
|
||||||
ci.origPath = origPath
|
ci.origPath = origPath
|
||||||
@@ -437,20 +394,20 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
|||||||
|
|
||||||
// Must be a dir
|
// Must be a dir
|
||||||
var subfiles []string
|
var subfiles []string
|
||||||
absOrigPath := filepath.Join(b.contextPath, ci.origPath)
|
absOrigPath := path.Join(b.contextPath, ci.origPath)
|
||||||
|
|
||||||
// Add a trailing / to make sure we only pick up nested files under
|
// Add a trailing / to make sure we only pick up nested files under
|
||||||
// the dir and not sibling files of the dir that just happen to
|
// the dir and not sibling files of the dir that just happen to
|
||||||
// start with the same chars
|
// start with the same chars
|
||||||
if !strings.HasSuffix(absOrigPath, string(os.PathSeparator)) {
|
if !strings.HasSuffix(absOrigPath, "/") {
|
||||||
absOrigPath += string(os.PathSeparator)
|
absOrigPath += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need path w/o slash too to find matching dir w/o trailing slash
|
// Need path w/o / too to find matching dir w/o trailing /
|
||||||
absOrigPathNoSlash := absOrigPath[:len(absOrigPath)-1]
|
absOrigPathNoSlash := absOrigPath[:len(absOrigPath)-1]
|
||||||
|
|
||||||
for _, fileInfo := range b.context.GetSums() {
|
for _, fileInfo := range b.context.GetSums() {
|
||||||
absFile := filepath.Join(b.contextPath, fileInfo.Name())
|
absFile := path.Join(b.contextPath, fileInfo.Name())
|
||||||
// Any file in the context that starts with the given path will be
|
// Any file in the context that starts with the given path will be
|
||||||
// picked up and its hashcode used. However, we'll exclude the
|
// picked up and its hashcode used. However, we'll exclude the
|
||||||
// root dir itself. We do this for a coupel of reasons:
|
// root dir itself. We do this for a coupel of reasons:
|
||||||
@@ -471,7 +428,7 @@ func calcCopyInfo(b *builder, cmdName string, cInfos *[]*copyInfo, origPath stri
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func containsWildcards(name string) bool {
|
func ContainsWildcards(name string) bool {
|
||||||
for i := 0; i < len(name); i++ {
|
for i := 0; i < len(name); i++ {
|
||||||
ch := name[i]
|
ch := name[i]
|
||||||
if ch == '\\' {
|
if ch == '\\' {
|
||||||
@@ -483,25 +440,21 @@ func containsWildcards(name string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *builder) pullImage(name string) (*image.Image, error) {
|
func (b *Builder) pullImage(name string) (*imagepkg.Image, error) {
|
||||||
remote, tag := parsers.ParseRepositoryTag(name)
|
remote, tag := parsers.ParseRepositoryTag(name)
|
||||||
if tag == "" {
|
if tag == "" {
|
||||||
tag = "latest"
|
tag = "latest"
|
||||||
}
|
}
|
||||||
|
|
||||||
pullRegistryAuth := &cliconfig.AuthConfig{}
|
pullRegistryAuth := b.AuthConfig
|
||||||
if len(b.AuthConfigs) > 0 {
|
if len(b.ConfigFile.AuthConfigs) > 0 {
|
||||||
// The request came with a full auth config file, we prefer to use that
|
// The request came with a full auth config file, we prefer to use that
|
||||||
repoInfo, err := b.Daemon.RegistryService.ResolveRepository(remote)
|
repoInfo, err := b.Daemon.RegistryService.ResolveRepository(remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
resolvedAuth := registry.ResolveAuthConfig(b.ConfigFile, repoInfo.Index)
|
||||||
resolvedConfig := registry.ResolveAuthConfig(
|
pullRegistryAuth = &resolvedAuth
|
||||||
&cliconfig.ConfigFile{AuthConfigs: b.AuthConfigs},
|
|
||||||
repoInfo.Index,
|
|
||||||
)
|
|
||||||
pullRegistryAuth = &resolvedConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
imagePullConfig := &graph.ImagePullConfig{
|
imagePullConfig := &graph.ImagePullConfig{
|
||||||
@@ -521,25 +474,20 @@ func (b *builder) pullImage(name string) (*image.Image, error) {
|
|||||||
return image, nil
|
return image, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *builder) processImageFrom(img *image.Image) error {
|
func (b *Builder) processImageFrom(img *imagepkg.Image) error {
|
||||||
b.image = img.ID
|
b.image = img.ID
|
||||||
|
|
||||||
if img.Config != nil {
|
if img.Config != nil {
|
||||||
b.Config = img.Config
|
b.Config = img.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// The default path will be blank on Windows (set by HCS)
|
if len(b.Config.Env) == 0 {
|
||||||
if len(b.Config.Env) == 0 && daemon.DefaultPathEnv != "" {
|
|
||||||
b.Config.Env = append(b.Config.Env, "PATH="+daemon.DefaultPathEnv)
|
b.Config.Env = append(b.Config.Env, "PATH="+daemon.DefaultPathEnv)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process ONBUILD triggers if they exist
|
// Process ONBUILD triggers if they exist
|
||||||
if nTriggers := len(b.Config.OnBuild); nTriggers != 0 {
|
if nTriggers := len(b.Config.OnBuild); nTriggers != 0 {
|
||||||
word := "trigger"
|
fmt.Fprintf(b.ErrStream, "# Executing %d build triggers\n", nTriggers)
|
||||||
if nTriggers > 1 {
|
|
||||||
word = "triggers"
|
|
||||||
}
|
|
||||||
fmt.Fprintf(b.ErrStream, "# Executing %d build %s...\n", nTriggers, word)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the ONBUILD triggers, and remove them from the config, since the config will be committed.
|
// Copy the ONBUILD triggers, and remove them from the config, since the config will be committed.
|
||||||
@@ -547,7 +495,7 @@ func (b *builder) processImageFrom(img *image.Image) error {
|
|||||||
b.Config.OnBuild = []string{}
|
b.Config.OnBuild = []string{}
|
||||||
|
|
||||||
// parse the ONBUILD triggers by invoking the parser
|
// parse the ONBUILD triggers by invoking the parser
|
||||||
for _, step := range onBuildTriggers {
|
for stepN, step := range onBuildTriggers {
|
||||||
ast, err := parser.Parse(strings.NewReader(step))
|
ast, err := parser.Parse(strings.NewReader(step))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -561,6 +509,8 @@ func (b *builder) processImageFrom(img *image.Image) error {
|
|||||||
return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", n.Value)
|
return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", n.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(b.OutStream, "Trigger %d, %s\n", stepN, step)
|
||||||
|
|
||||||
if err := b.dispatch(i, n); err != nil {
|
if err := b.dispatch(i, n); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -575,7 +525,7 @@ func (b *builder) processImageFrom(img *image.Image) error {
|
|||||||
// in the current server `b.Daemon`. If an image is found, probeCache returns
|
// in the current server `b.Daemon`. If an image is found, probeCache returns
|
||||||
// `(true, nil)`. If no image is found, it returns `(false, nil)`. If there
|
// `(true, nil)`. If no image is found, it returns `(false, nil)`. If there
|
||||||
// is any error, it returns `(false, err)`.
|
// is any error, it returns `(false, err)`.
|
||||||
func (b *builder) probeCache() (bool, error) {
|
func (b *Builder) probeCache() (bool, error) {
|
||||||
if !b.UtilizeCache || b.cacheBusted {
|
if !b.UtilizeCache || b.cacheBusted {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@@ -593,33 +543,31 @@ func (b *builder) probeCache() (bool, error) {
|
|||||||
fmt.Fprintf(b.OutStream, " ---> Using cache\n")
|
fmt.Fprintf(b.OutStream, " ---> Using cache\n")
|
||||||
logrus.Debugf("[BUILDER] Use cached version")
|
logrus.Debugf("[BUILDER] Use cached version")
|
||||||
b.image = cache.ID
|
b.image = cache.ID
|
||||||
b.Daemon.Graph().Retain(b.id, cache.ID)
|
|
||||||
b.activeImages = append(b.activeImages, cache.ID)
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *builder) create() (*daemon.Container, error) {
|
func (b *Builder) create() (*daemon.Container, error) {
|
||||||
if b.image == "" && !b.noBaseImage {
|
if b.image == "" && !b.noBaseImage {
|
||||||
return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
|
return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||||
}
|
}
|
||||||
b.Config.Image = b.image
|
b.Config.Image = b.image
|
||||||
|
|
||||||
hostConfig := &runconfig.HostConfig{
|
hostConfig := &runconfig.HostConfig{
|
||||||
CPUShares: b.cpuShares,
|
CpuShares: b.cpuShares,
|
||||||
CPUPeriod: b.cpuPeriod,
|
CpuPeriod: b.cpuPeriod,
|
||||||
CPUQuota: b.cpuQuota,
|
CpuQuota: b.cpuQuota,
|
||||||
CpusetCpus: b.cpuSetCpus,
|
CpusetCpus: b.cpuSetCpus,
|
||||||
CpusetMems: b.cpuSetMems,
|
CpusetMems: b.cpuSetMems,
|
||||||
CgroupParent: b.cgroupParent,
|
CgroupParent: b.cgroupParent,
|
||||||
Memory: b.memory,
|
Memory: b.memory,
|
||||||
MemorySwap: b.memorySwap,
|
MemorySwap: b.memorySwap,
|
||||||
Ulimits: b.ulimits,
|
NetworkMode: "bridge",
|
||||||
}
|
}
|
||||||
|
|
||||||
config := *b.Config
|
config := *b.Config
|
||||||
|
|
||||||
// Create the container
|
// Create the container
|
||||||
c, warnings, err := b.Daemon.ContainerCreate("", b.Config, hostConfig, true)
|
c, warnings, err := b.Daemon.Create(b.Config, hostConfig, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -636,13 +584,13 @@ func (b *builder) create() (*daemon.Container, error) {
|
|||||||
c.Path = s[0]
|
c.Path = s[0]
|
||||||
c.Args = s[1:]
|
c.Args = s[1:]
|
||||||
} else {
|
} else {
|
||||||
config.Cmd = stringutils.NewStrSlice()
|
config.Cmd = runconfig.NewCommand()
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *builder) run(c *daemon.Container) error {
|
func (b *Builder) run(c *daemon.Container) error {
|
||||||
var errCh chan error
|
var errCh chan error
|
||||||
if b.Verbose {
|
if b.Verbose {
|
||||||
errCh = c.Attach(nil, b.OutStream, b.ErrStream)
|
errCh = c.Attach(nil, b.OutStream, b.ErrStream)
|
||||||
@@ -682,8 +630,8 @@ func (b *builder) run(c *daemon.Container) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *builder) checkPathForAddition(orig string) error {
|
func (b *Builder) checkPathForAddition(orig string) error {
|
||||||
origPath := filepath.Join(b.contextPath, orig)
|
origPath := path.Join(b.contextPath, orig)
|
||||||
origPath, err := filepath.EvalSymlinks(origPath)
|
origPath, err := filepath.EvalSymlinks(origPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
@@ -691,11 +639,7 @@ func (b *builder) checkPathForAddition(orig string) error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
contextPath, err := filepath.EvalSymlinks(b.contextPath)
|
if !strings.HasPrefix(origPath, b.contextPath) {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(origPath, contextPath) {
|
|
||||||
return fmt.Errorf("Forbidden path outside the build context: %s (%s)", orig, origPath)
|
return fmt.Errorf("Forbidden path outside the build context: %s (%s)", orig, origPath)
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(origPath); err != nil {
|
if _, err := os.Stat(origPath); err != nil {
|
||||||
@@ -707,31 +651,27 @@ func (b *builder) checkPathForAddition(orig string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *builder) addContext(container *daemon.Container, orig, dest string, decompress bool) error {
|
func (b *Builder) addContext(container *daemon.Container, orig, dest string, decompress bool) error {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
destExists = true
|
destExists = true
|
||||||
origPath = filepath.Join(b.contextPath, orig)
|
origPath = path.Join(b.contextPath, orig)
|
||||||
destPath string
|
destPath string
|
||||||
)
|
)
|
||||||
|
|
||||||
// Work in daemon-local OS specific file paths
|
|
||||||
dest = filepath.FromSlash(dest)
|
|
||||||
|
|
||||||
destPath, err = container.GetResourcePath(dest)
|
destPath, err = container.GetResourcePath(dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preserve the trailing slash
|
// Preserve the trailing '/'
|
||||||
if strings.HasSuffix(dest, string(os.PathSeparator)) || dest == "." {
|
if strings.HasSuffix(dest, "/") || dest == "." {
|
||||||
destPath = destPath + string(os.PathSeparator)
|
destPath = destPath + "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
destStat, err := os.Stat(destPath)
|
destStat, err := os.Stat(destPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
logrus.Errorf("Error performing os.Stat on %s. %s", destPath, err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
destExists = false
|
destExists = false
|
||||||
@@ -754,9 +694,9 @@ func (b *builder) addContext(container *daemon.Container, orig, dest string, dec
|
|||||||
// First try to unpack the source as an archive
|
// First try to unpack the source as an archive
|
||||||
// to support the untar feature we need to clean up the path a little bit
|
// 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
|
// 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 slash
|
// filename from the path but this is only added if it does not end in / .
|
||||||
tarDest := destPath
|
tarDest := destPath
|
||||||
if strings.HasSuffix(tarDest, string(os.PathSeparator)) {
|
if strings.HasSuffix(tarDest, "/") {
|
||||||
tarDest = filepath.Dir(destPath)
|
tarDest = filepath.Dir(destPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -768,7 +708,7 @@ func (b *builder) addContext(container *daemon.Container, orig, dest string, dec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := system.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
|
if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := chrootarchive.CopyWithTar(origPath, destPath); err != nil {
|
if err := chrootarchive.CopyWithTar(origPath, destPath); err != nil {
|
||||||
@@ -777,7 +717,7 @@ func (b *builder) addContext(container *daemon.Container, orig, dest string, dec
|
|||||||
|
|
||||||
resPath := destPath
|
resPath := destPath
|
||||||
if destExists && destStat.IsDir() {
|
if destExists && destStat.IsDir() {
|
||||||
resPath = filepath.Join(destPath, filepath.Base(origPath))
|
resPath = path.Join(destPath, path.Base(origPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
return fixPermissions(origPath, resPath, 0, 0, destExists)
|
return fixPermissions(origPath, resPath, 0, 0, destExists)
|
||||||
@@ -790,7 +730,39 @@ func copyAsDirectory(source, destination string, destExisted bool) error {
|
|||||||
return fixPermissions(source, destination, 0, 0, destExisted)
|
return fixPermissions(source, destination, 0, 0, destExisted)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *builder) clearTmp() {
|
func fixPermissions(source, destination string, uid, gid int, destExisted bool) error {
|
||||||
|
// If the destination didn't already exist, or the destination isn't a
|
||||||
|
// directory, then we should Lchown the destination. Otherwise, we shouldn't
|
||||||
|
// Lchown the destination.
|
||||||
|
destStat, err := os.Stat(destination)
|
||||||
|
if err != nil {
|
||||||
|
// This should *never* be reached, because the destination must've already
|
||||||
|
// been created while untar-ing the context.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
doChownDestination := !destExisted || !destStat.IsDir()
|
||||||
|
|
||||||
|
// We Walk on the source rather than on the destination because we don't
|
||||||
|
// want to change permissions on things we haven't created or modified.
|
||||||
|
return filepath.Walk(source, func(fullpath string, info os.FileInfo, err error) error {
|
||||||
|
// Do not alter the walk root iff. it existed before, as it doesn't fall under
|
||||||
|
// the domain of "things we should chown".
|
||||||
|
if !doChownDestination && (source == fullpath) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path is prefixed by source: substitute with destination instead.
|
||||||
|
cleaned, err := filepath.Rel(source, fullpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fullpath = path.Join(destination, cleaned)
|
||||||
|
return os.Lchown(fullpath, uid, gid)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) clearTmp() {
|
||||||
for c := range b.TmpContainers {
|
for c := range b.TmpContainers {
|
||||||
rmConfig := &daemon.ContainerRmConfig{
|
rmConfig := &daemon.ContainerRmConfig{
|
||||||
ForceRemove: true,
|
ForceRemove: true,
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
// +build freebsd linux
|
|
||||||
|
|
||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
func fixPermissions(source, destination string, uid, gid int, destExisted bool) error {
|
|
||||||
// If the destination didn't already exist, or the destination isn't a
|
|
||||||
// directory, then we should Lchown the destination. Otherwise, we shouldn't
|
|
||||||
// Lchown the destination.
|
|
||||||
destStat, err := os.Stat(destination)
|
|
||||||
if err != nil {
|
|
||||||
// This should *never* be reached, because the destination must've already
|
|
||||||
// been created while untar-ing the context.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
doChownDestination := !destExisted || !destStat.IsDir()
|
|
||||||
|
|
||||||
// We Walk on the source rather than on the destination because we don't
|
|
||||||
// want to change permissions on things we haven't created or modified.
|
|
||||||
return filepath.Walk(source, func(fullpath string, info os.FileInfo, err error) error {
|
|
||||||
// Do not alter the walk root iff. it existed before, as it doesn't fall under
|
|
||||||
// the domain of "things we should chown".
|
|
||||||
if !doChownDestination && (source == fullpath) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path is prefixed by source: substitute with destination instead.
|
|
||||||
cleaned, err := filepath.Rel(source, fullpath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fullpath = filepath.Join(destination, cleaned)
|
|
||||||
return os.Lchown(fullpath, uid, gid)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package builder
|
|
||||||
|
|
||||||
func fixPermissions(source, destination string, uid, gid int, destExisted bool) error {
|
|
||||||
// chown is not supported on Windows
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
+43
-161
@@ -2,12 +2,10 @@ package builder
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -19,34 +17,25 @@ import (
|
|||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/httputils"
|
"github.com/docker/docker/pkg/httputils"
|
||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/pkg/progressreader"
|
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
|
||||||
"github.com/docker/docker/pkg/ulimit"
|
|
||||||
"github.com/docker/docker/pkg/urlutil"
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// When downloading remote contexts, limit the amount (in bytes)
|
|
||||||
// to be read from the response body in order to detect its Content-Type
|
|
||||||
const maxPreambleLength = 100
|
|
||||||
|
|
||||||
// whitelist of commands allowed for a commit/import
|
// whitelist of commands allowed for a commit/import
|
||||||
var validCommitCommands = map[string]bool{
|
var validCommitCommands = map[string]bool{
|
||||||
"cmd": true,
|
|
||||||
"entrypoint": true,
|
"entrypoint": true,
|
||||||
"env": true,
|
"cmd": true,
|
||||||
"expose": true,
|
|
||||||
"label": true,
|
|
||||||
"onbuild": true,
|
|
||||||
"user": true,
|
"user": true,
|
||||||
"volume": true,
|
|
||||||
"workdir": true,
|
"workdir": true,
|
||||||
|
"env": true,
|
||||||
|
"volume": true,
|
||||||
|
"expose": true,
|
||||||
|
"onbuild": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config contains all configs for a build job
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
DockerfileName string
|
DockerfileName string
|
||||||
RemoteURL string
|
RemoteURL string
|
||||||
@@ -58,14 +47,14 @@ type Config struct {
|
|||||||
Pull bool
|
Pull bool
|
||||||
Memory int64
|
Memory int64
|
||||||
MemorySwap int64
|
MemorySwap int64
|
||||||
CPUShares int64
|
CpuShares int64
|
||||||
CPUPeriod int64
|
CpuPeriod int64
|
||||||
CPUQuota int64
|
CpuQuota int64
|
||||||
CPUSetCpus string
|
CpuSetCpus string
|
||||||
CPUSetMems string
|
CpuSetMems string
|
||||||
CgroupParent string
|
CgroupParent string
|
||||||
Ulimits []*ulimit.Ulimit
|
AuthConfig *cliconfig.AuthConfig
|
||||||
AuthConfigs map[string]cliconfig.AuthConfig
|
ConfigFile *cliconfig.ConfigFile
|
||||||
|
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
Context io.ReadCloser
|
Context io.ReadCloser
|
||||||
@@ -76,36 +65,32 @@ type Config struct {
|
|||||||
cancelOnce sync.Once
|
cancelOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel signals the build job to cancel
|
// When called, causes the Job.WaitCancelled channel to unblock.
|
||||||
func (b *Config) Cancel() {
|
func (b *Config) Cancel() {
|
||||||
b.cancelOnce.Do(func() {
|
b.cancelOnce.Do(func() {
|
||||||
close(b.cancelled)
|
close(b.cancelled)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitCancelled returns a channel which is closed ("never blocks") when
|
// Returns a channel which is closed ("never blocks") when the job is cancelled.
|
||||||
// the job is cancelled.
|
|
||||||
func (b *Config) WaitCancelled() <-chan struct{} {
|
func (b *Config) WaitCancelled() <-chan struct{} {
|
||||||
return b.cancelled
|
return b.cancelled
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBuildConfig returns a new Config struct
|
|
||||||
func NewBuildConfig() *Config {
|
func NewBuildConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
AuthConfigs: map[string]cliconfig.AuthConfig{},
|
AuthConfig: &cliconfig.AuthConfig{},
|
||||||
cancelled: make(chan struct{}),
|
ConfigFile: &cliconfig.ConfigFile{},
|
||||||
|
cancelled: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build is the main interface of the package, it gathers the Builder
|
|
||||||
// struct and calls builder.Run() to do all the real build job.
|
|
||||||
func Build(d *daemon.Daemon, buildConfig *Config) error {
|
func Build(d *daemon.Daemon, buildConfig *Config) error {
|
||||||
var (
|
var (
|
||||||
repoName string
|
repoName string
|
||||||
tag string
|
tag string
|
||||||
context io.ReadCloser
|
context io.ReadCloser
|
||||||
)
|
)
|
||||||
sf := streamformatter.NewJSONStreamFormatter()
|
|
||||||
|
|
||||||
repoName, tag = parsers.ParseRepositoryTag(buildConfig.RepoName)
|
repoName, tag = parsers.ParseRepositoryTag(buildConfig.RepoName)
|
||||||
if repoName != "" {
|
if repoName != "" {
|
||||||
@@ -136,58 +121,35 @@ func Build(d *daemon.Daemon, buildConfig *Config) error {
|
|||||||
} else if urlutil.IsURL(buildConfig.RemoteURL) {
|
} else if urlutil.IsURL(buildConfig.RemoteURL) {
|
||||||
f, err := httputils.Download(buildConfig.RemoteURL)
|
f, err := httputils.Download(buildConfig.RemoteURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error downloading remote context %s: %v", buildConfig.RemoteURL, err)
|
return err
|
||||||
}
|
}
|
||||||
defer f.Body.Close()
|
defer f.Body.Close()
|
||||||
ct := f.Header.Get("Content-Type")
|
dockerFile, err := ioutil.ReadAll(f.Body)
|
||||||
clen := f.ContentLength
|
|
||||||
contentType, bodyReader, err := inspectResponse(ct, f.Body, clen)
|
|
||||||
|
|
||||||
defer bodyReader.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error detecting content type for remote %s: %v", buildConfig.RemoteURL, err)
|
return err
|
||||||
}
|
}
|
||||||
if contentType == httputils.MimeTypes.TextPlain {
|
|
||||||
dockerFile, err := ioutil.ReadAll(bodyReader)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// When we're downloading just a Dockerfile put it in
|
// When we're downloading just a Dockerfile put it in
|
||||||
// the default name - don't allow the client to move/specify it
|
// the default name - don't allow the client to move/specify it
|
||||||
buildConfig.DockerfileName = api.DefaultDockerfileName
|
buildConfig.DockerfileName = api.DefaultDockerfileName
|
||||||
|
|
||||||
c, err := archive.Generate(buildConfig.DockerfileName, string(dockerFile))
|
c, err := archive.Generate(buildConfig.DockerfileName, string(dockerFile))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
context = c
|
|
||||||
} else {
|
|
||||||
// Pass through - this is a pre-packaged context, presumably
|
|
||||||
// with a Dockerfile with the right name inside it.
|
|
||||||
prCfg := progressreader.Config{
|
|
||||||
In: bodyReader,
|
|
||||||
Out: buildConfig.Stdout,
|
|
||||||
Formatter: sf,
|
|
||||||
Size: clen,
|
|
||||||
NewLines: true,
|
|
||||||
ID: "Downloading context",
|
|
||||||
Action: buildConfig.RemoteURL,
|
|
||||||
}
|
|
||||||
context = progressreader.New(prCfg)
|
|
||||||
}
|
}
|
||||||
|
context = c
|
||||||
}
|
}
|
||||||
|
|
||||||
defer context.Close()
|
defer context.Close()
|
||||||
|
|
||||||
builder := &builder{
|
sf := streamformatter.NewJSONStreamFormatter()
|
||||||
|
|
||||||
|
builder := &Builder{
|
||||||
Daemon: d,
|
Daemon: d,
|
||||||
OutStream: &streamformatter.StdoutFormatter{
|
OutStream: &streamformatter.StdoutFormater{
|
||||||
Writer: buildConfig.Stdout,
|
Writer: buildConfig.Stdout,
|
||||||
StreamFormatter: sf,
|
StreamFormatter: sf,
|
||||||
},
|
},
|
||||||
ErrStream: &streamformatter.StderrFormatter{
|
ErrStream: &streamformatter.StderrFormater{
|
||||||
Writer: buildConfig.Stdout,
|
Writer: buildConfig.Stdout,
|
||||||
StreamFormatter: sf,
|
StreamFormatter: sf,
|
||||||
},
|
},
|
||||||
@@ -198,40 +160,31 @@ func Build(d *daemon.Daemon, buildConfig *Config) error {
|
|||||||
Pull: buildConfig.Pull,
|
Pull: buildConfig.Pull,
|
||||||
OutOld: buildConfig.Stdout,
|
OutOld: buildConfig.Stdout,
|
||||||
StreamFormatter: sf,
|
StreamFormatter: sf,
|
||||||
AuthConfigs: buildConfig.AuthConfigs,
|
AuthConfig: buildConfig.AuthConfig,
|
||||||
|
ConfigFile: buildConfig.ConfigFile,
|
||||||
dockerfileName: buildConfig.DockerfileName,
|
dockerfileName: buildConfig.DockerfileName,
|
||||||
cpuShares: buildConfig.CPUShares,
|
cpuShares: buildConfig.CpuShares,
|
||||||
cpuPeriod: buildConfig.CPUPeriod,
|
cpuPeriod: buildConfig.CpuPeriod,
|
||||||
cpuQuota: buildConfig.CPUQuota,
|
cpuQuota: buildConfig.CpuQuota,
|
||||||
cpuSetCpus: buildConfig.CPUSetCpus,
|
cpuSetCpus: buildConfig.CpuSetCpus,
|
||||||
cpuSetMems: buildConfig.CPUSetMems,
|
cpuSetMems: buildConfig.CpuSetMems,
|
||||||
cgroupParent: buildConfig.CgroupParent,
|
cgroupParent: buildConfig.CgroupParent,
|
||||||
memory: buildConfig.Memory,
|
memory: buildConfig.Memory,
|
||||||
memorySwap: buildConfig.MemorySwap,
|
memorySwap: buildConfig.MemorySwap,
|
||||||
ulimits: buildConfig.Ulimits,
|
|
||||||
cancelled: buildConfig.WaitCancelled(),
|
cancelled: buildConfig.WaitCancelled(),
|
||||||
id: stringid.GenerateRandomID(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
|
||||||
builder.Daemon.Graph().Release(builder.id, builder.activeImages...)
|
|
||||||
}()
|
|
||||||
|
|
||||||
id, err := builder.Run(context)
|
id, err := builder.Run(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if repoName != "" {
|
if repoName != "" {
|
||||||
return d.Repositories().Tag(repoName, tag, id, true)
|
return d.Repositories().Tag(repoName, tag, id, true)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildFromConfig will do build directly from parameter 'changes', which comes
|
|
||||||
// from Dockerfile entries, it will:
|
|
||||||
//
|
|
||||||
// - call parse.Parse() to get AST root from Dockerfile entries
|
|
||||||
// - do build by calling builder.dispatch() to call all entries' handling routines
|
|
||||||
func BuildFromConfig(d *daemon.Daemon, c *runconfig.Config, changes []string) (*runconfig.Config, error) {
|
func BuildFromConfig(d *daemon.Daemon, c *runconfig.Config, changes []string) (*runconfig.Config, error) {
|
||||||
ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
|
ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -245,7 +198,7 @@ func BuildFromConfig(d *daemon.Daemon, c *runconfig.Config, changes []string) (*
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
builder := &builder{
|
builder := &Builder{
|
||||||
Daemon: d,
|
Daemon: d,
|
||||||
Config: c,
|
Config: c,
|
||||||
OutStream: ioutil.Discard,
|
OutStream: ioutil.Discard,
|
||||||
@@ -262,29 +215,12 @@ func BuildFromConfig(d *daemon.Daemon, c *runconfig.Config, changes []string) (*
|
|||||||
return builder.Config, nil
|
return builder.Config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitConfig contains build configs for commit operation
|
func Commit(d *daemon.Daemon, name string, c *daemon.ContainerCommitConfig) (string, error) {
|
||||||
type CommitConfig struct {
|
|
||||||
Pause bool
|
|
||||||
Repo string
|
|
||||||
Tag string
|
|
||||||
Author string
|
|
||||||
Comment string
|
|
||||||
Changes []string
|
|
||||||
Config *runconfig.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit will create a new image from a container's changes
|
|
||||||
func Commit(name string, d *daemon.Daemon, c *CommitConfig) (string, error) {
|
|
||||||
container, err := d.Get(name)
|
container, err := d.Get(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// It is not possible to commit a running container on Windows
|
|
||||||
if runtime.GOOS == "windows" && container.IsRunning() {
|
|
||||||
return "", fmt.Errorf("Windows does not support commit of a running container")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Config == nil {
|
if c.Config == nil {
|
||||||
c.Config = &runconfig.Config{}
|
c.Config = &runconfig.Config{}
|
||||||
}
|
}
|
||||||
@@ -298,64 +234,10 @@ func Commit(name string, d *daemon.Daemon, c *CommitConfig) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
commitCfg := &daemon.ContainerCommitConfig{
|
img, err := d.Commit(container, c.Repo, c.Tag, c.Comment, c.Author, c.Pause, newConfig)
|
||||||
Pause: c.Pause,
|
|
||||||
Repo: c.Repo,
|
|
||||||
Tag: c.Tag,
|
|
||||||
Author: c.Author,
|
|
||||||
Comment: c.Comment,
|
|
||||||
Config: newConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
img, err := d.Commit(container, commitCfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return img.ID, nil
|
return img.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// inspectResponse looks into the http response data at r to determine whether its
|
|
||||||
// content-type is on the list of acceptable content types for remote build contexts.
|
|
||||||
// This function returns:
|
|
||||||
// - a string representation of the detected content-type
|
|
||||||
// - an io.Reader for the response body
|
|
||||||
// - an error value which will be non-nil either when something goes wrong while
|
|
||||||
// reading bytes from r or when the detected content-type is not acceptable.
|
|
||||||
func inspectResponse(ct string, r io.ReadCloser, clen int64) (string, io.ReadCloser, error) {
|
|
||||||
plen := clen
|
|
||||||
if plen <= 0 || plen > maxPreambleLength {
|
|
||||||
plen = maxPreambleLength
|
|
||||||
}
|
|
||||||
|
|
||||||
preamble := make([]byte, plen, plen)
|
|
||||||
rlen, err := r.Read(preamble)
|
|
||||||
if rlen == 0 {
|
|
||||||
return ct, r, errors.New("Empty response")
|
|
||||||
}
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
return ct, r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
preambleR := bytes.NewReader(preamble)
|
|
||||||
bodyReader := ioutil.NopCloser(io.MultiReader(preambleR, r))
|
|
||||||
// Some web servers will use application/octet-stream as the default
|
|
||||||
// content type for files without an extension (e.g. 'Dockerfile')
|
|
||||||
// so if we receive this value we better check for text content
|
|
||||||
contentType := ct
|
|
||||||
if len(ct) == 0 || ct == httputils.MimeTypes.OctetStream {
|
|
||||||
contentType, _, err = httputils.DetectContentType(preamble)
|
|
||||||
if err != nil {
|
|
||||||
return contentType, bodyReader, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
contentType = selectAcceptableMIME(contentType)
|
|
||||||
var cterr error
|
|
||||||
if len(contentType) == 0 {
|
|
||||||
cterr = fmt.Errorf("unsupported Content-Type %q", ct)
|
|
||||||
contentType = ct
|
|
||||||
}
|
|
||||||
|
|
||||||
return contentType, bodyReader, cterr
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io/ioutil"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var textPlainDockerfile = "FROM busybox"
|
|
||||||
var binaryContext = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00} //xz magic
|
|
||||||
|
|
||||||
func TestInspectEmptyResponse(t *testing.T) {
|
|
||||||
ct := "application/octet-stream"
|
|
||||||
br := ioutil.NopCloser(bytes.NewReader([]byte("")))
|
|
||||||
contentType, bReader, err := inspectResponse(ct, br, 0)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Should have generated an error for an empty response")
|
|
||||||
}
|
|
||||||
if contentType != "application/octet-stream" {
|
|
||||||
t.Fatalf("Content type should be 'application/octet-stream' but is %q", contentType)
|
|
||||||
}
|
|
||||||
body, err := ioutil.ReadAll(bReader)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(body) != 0 {
|
|
||||||
t.Fatal("response body should remain empty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInspectResponseBinary(t *testing.T) {
|
|
||||||
ct := "application/octet-stream"
|
|
||||||
br := ioutil.NopCloser(bytes.NewReader(binaryContext))
|
|
||||||
contentType, bReader, err := inspectResponse(ct, br, int64(len(binaryContext)))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if contentType != "application/octet-stream" {
|
|
||||||
t.Fatalf("Content type should be 'application/octet-stream' but is %q", contentType)
|
|
||||||
}
|
|
||||||
body, err := ioutil.ReadAll(bReader)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(body) != len(binaryContext) {
|
|
||||||
t.Fatalf("Wrong response size %d, should be == len(binaryContext)", len(body))
|
|
||||||
}
|
|
||||||
for i := range body {
|
|
||||||
if body[i] != binaryContext[i] {
|
|
||||||
t.Fatalf("Corrupted response body at byte index %d", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResponseUnsupportedContentType(t *testing.T) {
|
|
||||||
content := []byte(textPlainDockerfile)
|
|
||||||
ct := "application/json"
|
|
||||||
br := ioutil.NopCloser(bytes.NewReader(content))
|
|
||||||
contentType, bReader, err := inspectResponse(ct, br, int64(len(textPlainDockerfile)))
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("Should have returned an error on content-type 'application/json'")
|
|
||||||
}
|
|
||||||
if contentType != ct {
|
|
||||||
t.Fatalf("Should not have altered content-type: orig: %s, altered: %s", ct, contentType)
|
|
||||||
}
|
|
||||||
body, err := ioutil.ReadAll(bReader)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if string(body) != textPlainDockerfile {
|
|
||||||
t.Fatalf("Corrupted response body %s", body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInspectResponseTextSimple(t *testing.T) {
|
|
||||||
content := []byte(textPlainDockerfile)
|
|
||||||
ct := "text/plain"
|
|
||||||
br := ioutil.NopCloser(bytes.NewReader(content))
|
|
||||||
contentType, bReader, err := inspectResponse(ct, br, int64(len(content)))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if contentType != "text/plain" {
|
|
||||||
t.Fatalf("Content type should be 'text/plain' but is %q", contentType)
|
|
||||||
}
|
|
||||||
body, err := ioutil.ReadAll(bReader)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if string(body) != textPlainDockerfile {
|
|
||||||
t.Fatalf("Corrupted response body %s", body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInspectResponseEmptyContentType(t *testing.T) {
|
|
||||||
content := []byte(textPlainDockerfile)
|
|
||||||
br := ioutil.NopCloser(bytes.NewReader(content))
|
|
||||||
contentType, bodyReader, err := inspectResponse("", br, int64(len(content)))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if contentType != "text/plain" {
|
|
||||||
t.Fatalf("Content type should be 'text/plain' but is %q", contentType)
|
|
||||||
}
|
|
||||||
body, err := ioutil.ReadAll(bodyReader)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if string(body) != textPlainDockerfile {
|
|
||||||
t.Fatalf("Corrupted response body %s", body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -151,7 +151,7 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) {
|
|||||||
if !strings.Contains(words[0], "=") {
|
if !strings.Contains(words[0], "=") {
|
||||||
node := &Node{}
|
node := &Node{}
|
||||||
rootnode = node
|
rootnode = node
|
||||||
strs := tokenWhitespace.Split(rest, 2)
|
strs := TOKEN_WHITESPACE.Split(rest, 2)
|
||||||
|
|
||||||
if len(strs) < 2 {
|
if len(strs) < 2 {
|
||||||
return nil, nil, fmt.Errorf(key + " must have two arguments")
|
return nil, nil, fmt.Errorf(key + " must have two arguments")
|
||||||
@@ -205,7 +205,7 @@ func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error
|
|||||||
node := &Node{}
|
node := &Node{}
|
||||||
rootnode := node
|
rootnode := node
|
||||||
prevnode := node
|
prevnode := node
|
||||||
for _, str := range tokenWhitespace.Split(rest, -1) { // use regexp
|
for _, str := range TOKEN_WHITESPACE.Split(rest, -1) { // use regexp
|
||||||
prevnode = node
|
prevnode = node
|
||||||
node.Value = str
|
node.Value = str
|
||||||
node.Next = &Node{}
|
node.Next = &Node{}
|
||||||
@@ -232,18 +232,13 @@ func parseString(rest string) (*Node, map[string]bool, error) {
|
|||||||
|
|
||||||
// parseJSON converts JSON arrays to an AST.
|
// parseJSON converts JSON arrays to an AST.
|
||||||
func parseJSON(rest string) (*Node, map[string]bool, error) {
|
func parseJSON(rest string) (*Node, map[string]bool, error) {
|
||||||
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
|
var myJson []interface{}
|
||||||
if !strings.HasPrefix(rest, "[") {
|
if err := json.NewDecoder(strings.NewReader(rest)).Decode(&myJson); err != nil {
|
||||||
return nil, nil, fmt.Errorf(`Error parsing "%s" as a JSON array`, rest)
|
|
||||||
}
|
|
||||||
|
|
||||||
var myJSON []interface{}
|
|
||||||
if err := json.NewDecoder(strings.NewReader(rest)).Decode(&myJSON); err != nil {
|
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var top, prev *Node
|
var top, prev *Node
|
||||||
for _, str := range myJSON {
|
for _, str := range myJson {
|
||||||
s, ok := str.(string)
|
s, ok := str.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, errDockerfileNotStringArray
|
return nil, nil, errDockerfileNotStringArray
|
||||||
|
|||||||
+10
-10
@@ -1,4 +1,4 @@
|
|||||||
// Package parser implements a parser and parse tree dumper for Dockerfiles.
|
// This package implements a parser and parse tree dumper for Dockerfiles.
|
||||||
package parser
|
package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -33,16 +33,16 @@ type Node struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dispatch map[string]func(string) (*Node, map[string]bool, error)
|
dispatch map[string]func(string) (*Node, map[string]bool, error)
|
||||||
tokenWhitespace = regexp.MustCompile(`[\t\v\f\r ]+`)
|
TOKEN_WHITESPACE = regexp.MustCompile(`[\t\v\f\r ]+`)
|
||||||
tokenLineContinuation = regexp.MustCompile(`\\[ \t]*$`)
|
TOKEN_LINE_CONTINUATION = regexp.MustCompile(`\\[ \t]*$`)
|
||||||
tokenComment = regexp.MustCompile(`^#.*$`)
|
TOKEN_COMMENT = regexp.MustCompile(`^#.*$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Dispatch Table. see line_parsers.go for the parse functions.
|
// Dispatch Table. see line_parsers.go for the parse functions.
|
||||||
// The command is parsed and mapped to the line parser. The line parser
|
// The command is parsed and mapped to the line parser. The line parser
|
||||||
// receives the arguments but not the command, and returns an AST after
|
// recieves the arguments but not the command, and returns an AST after
|
||||||
// reformulating the arguments according to the rules in the parser
|
// reformulating the arguments according to the rules in the parser
|
||||||
// functions. Errors are propagated up by Parse() and the resulting AST can
|
// functions. Errors are propagated up by Parse() and the resulting AST can
|
||||||
// be incorporated directly into the existing AST as a next.
|
// be incorporated directly into the existing AST as a next.
|
||||||
@@ -70,8 +70,8 @@ func parseLine(line string) (string, *Node, error) {
|
|||||||
return "", nil, nil
|
return "", nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if tokenLineContinuation.MatchString(line) {
|
if TOKEN_LINE_CONTINUATION.MatchString(line) {
|
||||||
line = tokenLineContinuation.ReplaceAllString(line, "")
|
line = TOKEN_LINE_CONTINUATION.ReplaceAllString(line, "")
|
||||||
return line, nil, nil
|
return line, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,8 +96,8 @@ func parseLine(line string) (string, *Node, error) {
|
|||||||
return "", node, nil
|
return "", node, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse is the main parse routine.
|
// The main parse routine. Handles an io.ReadWriteCloser and returns the root
|
||||||
// It handles an io.ReadWriteCloser and returns the root of the AST.
|
// of the AST.
|
||||||
func Parse(rwc io.Reader) (*Node, error) {
|
func Parse(rwc io.Reader) (*Node, error) {
|
||||||
root := &Node{}
|
root := &Node{}
|
||||||
scanner := bufio.NewScanner(rwc)
|
scanner := bufio.NewScanner(rwc)
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ MAINTAINER Seongyeol Lim <seongyeol37@gmail.com>
|
|||||||
|
|
||||||
COPY . /go/src/github.com/docker/docker
|
COPY . /go/src/github.com/docker/docker
|
||||||
ADD . /
|
ADD . /
|
||||||
ADD null /
|
|
||||||
COPY nullfile /tmp
|
|
||||||
ADD [ "vimrc", "/tmp" ]
|
ADD [ "vimrc", "/tmp" ]
|
||||||
COPY [ "bashrc", "/tmp" ]
|
COPY [ "bashrc", "/tmp" ]
|
||||||
COPY [ "test file", "/tmp" ]
|
COPY [ "test file", "/tmp" ]
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
(maintainer "Seongyeol Lim <seongyeol37@gmail.com>")
|
(maintainer "Seongyeol Lim <seongyeol37@gmail.com>")
|
||||||
(copy "." "/go/src/github.com/docker/docker")
|
(copy "." "/go/src/github.com/docker/docker")
|
||||||
(add "." "/")
|
(add "." "/")
|
||||||
(add "null" "/")
|
|
||||||
(copy "nullfile" "/tmp")
|
|
||||||
(add "vimrc" "/tmp")
|
(add "vimrc" "/tmp")
|
||||||
(copy "bashrc" "/tmp")
|
(copy "bashrc" "/tmp")
|
||||||
(copy "test file" "/tmp")
|
(copy "test file" "/tmp")
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
# -e GPG_PASSPHRASE=gloubiboulga \
|
# -e GPG_PASSPHRASE=gloubiboulga \
|
||||||
# docker hack/release.sh
|
# docker hack/release.sh
|
||||||
#
|
#
|
||||||
# Note: AppArmor used to mess with privileged mode, but this is no longer
|
# Note: Apparmor used to mess with privileged mode, but this is no longer
|
||||||
# the case. Therefore, you don't have to disable it anymore.
|
# the case. Therefore, you don't have to disable it anymore.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user