mirror of
https://github.com/moby/moby.git
synced 2026-01-12 11:11:44 +00:00
Compare commits
134 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76d6bc9a9f | ||
|
|
b9c3ba79a6 | ||
|
|
17fb8c3c5a | ||
|
|
0849a71036 | ||
|
|
b53c39f606 | ||
|
|
5d72b7a582 | ||
|
|
c50bd847a7 | ||
|
|
fa2a6b3522 | ||
|
|
f2d0d2e516 | ||
|
|
7be73fe0cb | ||
|
|
75847fb840 | ||
|
|
dfad0c3da7 | ||
|
|
263604f570 | ||
|
|
6cbcdc7a6d | ||
|
|
0d762ef9d2 | ||
|
|
9f7231816f | ||
|
|
0430024bad | ||
|
|
6fa9da0363 | ||
|
|
2ec7433aa4 | ||
|
|
51e5073111 | ||
|
|
c4f0a2aa00 | ||
|
|
581f297644 | ||
|
|
cff248b35f | ||
|
|
12da51944a | ||
|
|
87417387c6 | ||
|
|
b693a04658 | ||
|
|
e50176876c | ||
|
|
22c2f04847 | ||
|
|
75f6674b11 | ||
|
|
ea1862d346 | ||
|
|
9335cf9d29 | ||
|
|
22cf3df4c0 | ||
|
|
c57d1e026d | ||
|
|
ce6e6b18ad | ||
|
|
a09d521221 | ||
|
|
3648ffe722 | ||
|
|
37e04b864e | ||
|
|
160f4e4a3b | ||
|
|
a76b5fd324 | ||
|
|
04cc66b08e | ||
|
|
93f966cdc0 | ||
|
|
6b779ead93 | ||
|
|
7ac3502444 | ||
|
|
963b4d0b22 | ||
|
|
2801a373a8 | ||
|
|
c2c88fec85 | ||
|
|
7825b5a5cd | ||
|
|
3040ad3581 | ||
|
|
84eaf42839 | ||
|
|
581df6aaa1 | ||
|
|
b8410cd2ae | ||
|
|
cc1782b318 | ||
|
|
f8c24afcb4 | ||
|
|
8b5c7d6a60 | ||
|
|
05e3f2f62f | ||
|
|
957fcb10bf | ||
|
|
9c5885135f | ||
|
|
03d7cf1aef | ||
|
|
7ff56c563d | ||
|
|
e6ff99629b | ||
|
|
50a8ce2b28 | ||
|
|
dc5a0118a1 | ||
|
|
f2671e07b3 | ||
|
|
033ce66707 | ||
|
|
6a01c901d3 | ||
|
|
c75fd9b5da | ||
|
|
b83bbbe864 | ||
|
|
f110597a78 | ||
|
|
adfbdc361c | ||
|
|
587a0ffa2d | ||
|
|
7176e68625 | ||
|
|
98af0d7f3c | ||
|
|
fd2633a451 | ||
|
|
b576f54b73 | ||
|
|
d2e25ed81c | ||
|
|
44c74f2109 | ||
|
|
434221d0eb | ||
|
|
620817efba | ||
|
|
f4dc974b2a | ||
|
|
42abfab3a7 | ||
|
|
b63c2ab3c1 | ||
|
|
595fd5226b | ||
|
|
46b742dbb3 | ||
|
|
cd8357e5a6 | ||
|
|
d6a407f9b2 | ||
|
|
392fe9e507 | ||
|
|
2b4c34e756 | ||
|
|
130f92a67f | ||
|
|
d676ebc156 | ||
|
|
7e6a483578 | ||
|
|
304a08ee91 | ||
|
|
38fb64bba3 | ||
|
|
69cfb521bb | ||
|
|
9d8bf95811 | ||
|
|
10b594924a | ||
|
|
1b4f705a2b | ||
|
|
737cddc85d | ||
|
|
1f209fffd0 | ||
|
|
fb6ef9086d | ||
|
|
f2bbcab6c9 | ||
|
|
292d3b2052 | ||
|
|
f04a7bd388 | ||
|
|
08cec812cb | ||
|
|
34fdfc8f67 | ||
|
|
1d5b8348ce | ||
|
|
c0705bc51d | ||
|
|
d88f6c33a3 | ||
|
|
1361eaef7c | ||
|
|
cbec9c9a6b | ||
|
|
4a66841730 | ||
|
|
f679c1a5ed | ||
|
|
69c0989dfb | ||
|
|
10f946d312 | ||
|
|
45cfeed39d | ||
|
|
417386caa6 | ||
|
|
71f5c74a04 | ||
|
|
daa20c724a | ||
|
|
b4207be09d | ||
|
|
02a8baf069 | ||
|
|
447c28b165 | ||
|
|
78afc5876a | ||
|
|
3748494073 | ||
|
|
90d352e765 | ||
|
|
e51ffc7dd3 | ||
|
|
bbd690e2e2 | ||
|
|
14897a0b47 | ||
|
|
ac4349bd30 | ||
|
|
fb525a33e1 | ||
|
|
17def9a2c6 | ||
|
|
d1e3a75934 | ||
|
|
38cd4eb1b2 | ||
|
|
fbe20aa393 | ||
|
|
9b313adb51 | ||
|
|
9419eade34 |
22
.DEREK.yml
22
.DEREK.yml
@@ -1,22 +0,0 @@
|
||||
curators:
|
||||
- aboch
|
||||
- alexellis
|
||||
- andrewhsu
|
||||
- anonymuse
|
||||
- arkodg
|
||||
- chanwit
|
||||
- ehazlett
|
||||
- fntlnz
|
||||
- gianarb
|
||||
- kolyshkin
|
||||
- mgoelzer
|
||||
- olljanat
|
||||
- programmerq
|
||||
- rheinwein
|
||||
- ripcurld0
|
||||
- thajeztah
|
||||
|
||||
features:
|
||||
- comments
|
||||
- pr_description_required
|
||||
|
||||
@@ -1,6 +1,2 @@
|
||||
.git
|
||||
.go-pkg-cache
|
||||
.gopath
|
||||
bundles
|
||||
vendor/pkg
|
||||
|
||||
.gopath
|
||||
|
||||
16
.github/CODEOWNERS
vendored
16
.github/CODEOWNERS
vendored
@@ -1,16 +0,0 @@
|
||||
# GitHub code owners
|
||||
# See https://help.github.com/articles/about-codeowners/
|
||||
#
|
||||
# KEEP THIS FILE SORTED. Order is important. Last match takes precedence.
|
||||
|
||||
builder/** @tonistiigi
|
||||
contrib/mkimage/** @tianon
|
||||
daemon/graphdriver/devmapper/** @rhvgoyal
|
||||
daemon/graphdriver/lcow/** @johnstep
|
||||
daemon/graphdriver/overlay/** @dmcgowan
|
||||
daemon/graphdriver/overlay2/** @dmcgowan
|
||||
daemon/graphdriver/windows/** @johnstep
|
||||
daemon/logger/awslogs/** @samuelkarp
|
||||
hack/** @tianon
|
||||
plugin/** @cpuguy83
|
||||
project/** @thaJeztah
|
||||
70
.github/ISSUE_TEMPLATE.md
vendored
70
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,70 +0,0 @@
|
||||
<!--
|
||||
If you are reporting a new issue, make sure that we do not have any duplicates
|
||||
already open. You can ensure this by searching the issue list for this
|
||||
repository. If there is a duplicate, please close your issue and add a comment
|
||||
to the existing issue instead.
|
||||
|
||||
If you suspect your issue is a bug, please edit your issue description to
|
||||
include the BUG REPORT INFORMATION shown below. If you fail to provide this
|
||||
information within 7 days, we cannot debug your issue and will close it. We
|
||||
will, however, reopen it if you later provide the information.
|
||||
|
||||
For more information about reporting issues, see
|
||||
https://github.com/moby/moby/blob/master/CONTRIBUTING.md#reporting-other-issues
|
||||
|
||||
---------------------------------------------------
|
||||
GENERAL SUPPORT INFORMATION
|
||||
---------------------------------------------------
|
||||
|
||||
The GitHub issue tracker is for bug reports and feature requests.
|
||||
General support for **docker** can be found at the following locations:
|
||||
|
||||
- Docker Support Forums - https://forums.docker.com
|
||||
- Slack - community.docker.com #general channel
|
||||
- Post a question on StackOverflow, using the Docker tag
|
||||
|
||||
General support for **moby** can be found at the following locations:
|
||||
|
||||
- Moby Project Forums - https://forums.mobyproject.org
|
||||
- Slack - community.docker.com #moby-project channel
|
||||
- Post a question on StackOverflow, using the Moby tag
|
||||
|
||||
---------------------------------------------------
|
||||
BUG REPORT INFORMATION
|
||||
---------------------------------------------------
|
||||
Use the commands below to provide key information from your environment:
|
||||
You do NOT have to include this information if this is a FEATURE REQUEST
|
||||
-->
|
||||
|
||||
**Description**
|
||||
|
||||
<!--
|
||||
Briefly describe the problem you are having in a few paragraphs.
|
||||
-->
|
||||
|
||||
**Steps to reproduce the issue:**
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Describe the results you received:**
|
||||
|
||||
|
||||
**Describe the results you expected:**
|
||||
|
||||
|
||||
**Additional information you deem important (e.g. issue happens only occasionally):**
|
||||
|
||||
**Output of `docker version`:**
|
||||
|
||||
```
|
||||
(paste your output here)
|
||||
```
|
||||
|
||||
**Output of `docker info`:**
|
||||
|
||||
```
|
||||
(paste your output here)
|
||||
```
|
||||
|
||||
**Additional environment details (AWS, VirtualBox, physical, etc.):**
|
||||
30
.github/PULL_REQUEST_TEMPLATE.md
vendored
30
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,30 +0,0 @@
|
||||
<!--
|
||||
Please make sure you've read and understood our contributing guidelines;
|
||||
https://github.com/moby/moby/blob/master/CONTRIBUTING.md
|
||||
|
||||
** Make sure all your commits include a signature generated with `git commit -s` **
|
||||
|
||||
For additional information on our contributing process, read our contributing
|
||||
guide https://docs.docker.com/opensource/code/
|
||||
|
||||
If this is a bug fix, make sure your description includes "fixes #xxxx", or
|
||||
"closes #xxxx"
|
||||
|
||||
Please provide the following information:
|
||||
-->
|
||||
|
||||
**- What I did**
|
||||
|
||||
**- How I did it**
|
||||
|
||||
**- How to verify it**
|
||||
|
||||
**- Description for the changelog**
|
||||
<!--
|
||||
Write a short (one line) summary that describes the changes in this
|
||||
pull request for inclusion in the changelog:
|
||||
-->
|
||||
|
||||
|
||||
**- A picture of a cute animal (not mandatory but encouraged)**
|
||||
|
||||
35
.gitignore
vendored
35
.gitignore
vendored
@@ -3,21 +3,36 @@
|
||||
# please consider a global .gitignore https://help.github.com/articles/ignoring-files
|
||||
*.exe
|
||||
*.exe~
|
||||
*.gz
|
||||
*.orig
|
||||
test.main
|
||||
*.rej
|
||||
*.test
|
||||
.*.swp
|
||||
.DS_Store
|
||||
# a .bashrc may be added to customize the build environment
|
||||
.bashrc
|
||||
.editorconfig
|
||||
.dotcloud
|
||||
.flymake*
|
||||
.git/
|
||||
.gopath/
|
||||
.go-pkg-cache/
|
||||
.hg/
|
||||
.vagrant*
|
||||
Vagrantfile
|
||||
a.out
|
||||
autogen/
|
||||
bin
|
||||
build_src
|
||||
bundles/
|
||||
cmd/dockerd/dockerd
|
||||
contrib/builder/rpm/*/changelog
|
||||
docker/docker
|
||||
docs/AWS_S3_BUCKET
|
||||
docs/GITCOMMIT
|
||||
docs/GIT_BRANCH
|
||||
docs/VERSION
|
||||
docs/_build
|
||||
docs/_static
|
||||
docs/_templates
|
||||
docs/changed-files
|
||||
# generated by man/md2man-all.sh
|
||||
man/man1
|
||||
man/man5
|
||||
man/man8
|
||||
pyenv
|
||||
vendor/pkg/
|
||||
go-test-report.json
|
||||
profile.out
|
||||
junit-report.xml
|
||||
|
||||
720
.mailmap
720
.mailmap
@@ -6,576 +6,166 @@
|
||||
#
|
||||
# For explanation on this file format: man git-shortlog
|
||||
|
||||
<21551195@zju.edu.cn> <hsinko@users.noreply.github.com>
|
||||
<mr.wrfly@gmail.com> <wrfly@users.noreply.github.com>
|
||||
Aaron L. Xu <liker.xu@foxmail.com>
|
||||
Abhinandan Prativadi <abhi@docker.com>
|
||||
Adam Dobrawy <naczelnik@jawnosc.tk>
|
||||
Adam Dobrawy <naczelnik@jawnosc.tk> <ad-m@users.noreply.github.com>
|
||||
Adrien Gallouët <adrien@gallouet.fr> <angt@users.noreply.github.com>
|
||||
Ahmed Kamal <email.ahmedkamal@googlemail.com>
|
||||
Ahmet Alp Balkan <ahmetb@microsoft.com> <ahmetalpbalkan@gmail.com>
|
||||
AJ Bowen <aj@soulshake.net>
|
||||
AJ Bowen <aj@soulshake.net> <aj@gandi.net>
|
||||
AJ Bowen <aj@soulshake.net> <amy@gandi.net>
|
||||
Akihiro Matsushima <amatsusbit@gmail.com> <amatsus@users.noreply.github.com>
|
||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
|
||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp> <suda.akihiro@lab.ntt.co.jp>
|
||||
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp> <suda.kyoto@gmail.com>
|
||||
Aleksa Sarai <asarai@suse.de>
|
||||
Aleksa Sarai <asarai@suse.de> <asarai@suse.com>
|
||||
Aleksa Sarai <asarai@suse.de> <cyphar@cyphar.com>
|
||||
Aleksandrs Fadins <aleks@s-ko.net>
|
||||
Alessandro Boch <aboch@tetrationanalytics.com> <aboch@docker.com>
|
||||
Alex Chen <alexchenunix@gmail.com> <root@localhost.localdomain>
|
||||
Alex Ellis <alexellis2@gmail.com>
|
||||
Alex Goodman <wagoodman@gmail.com> <wagoodman@users.noreply.github.com>
|
||||
Alexander Larsson <alexl@redhat.com> <alexander.larsson@gmail.com>
|
||||
Alexander Morozov <lk4d4@docker.com>
|
||||
Alexander Morozov <lk4d4@docker.com> <lk4d4math@gmail.com>
|
||||
Alexandre Beslic <alexandre.beslic@gmail.com> <abronan@docker.com>
|
||||
Alicia Lauerman <alicia@eta.im> <allydevour@me.com>
|
||||
Allen Sun <allensun.shl@alibaba-inc.com> <allen.sun@daocloud.io>
|
||||
Allen Sun <allensun.shl@alibaba-inc.com> <shlallen1990@gmail.com>
|
||||
Andrea Denisse Gómez <crypto.andrea@protonmail.ch>
|
||||
Andrew Weiss <andrew.weiss@docker.com> <andrew.weiss@microsoft.com>
|
||||
Andrew Weiss <andrew.weiss@docker.com> <andrew.weiss@outlook.com>
|
||||
Andrey Kolomentsev <andrey.kolomentsev@docker.com>
|
||||
Andrey Kolomentsev <andrey.kolomentsev@docker.com> <andrey.kolomentsev@gmail.com>
|
||||
André Martins <aanm90@gmail.com> <martins@noironetworks.com>
|
||||
Andy Rothfusz <github@developersupport.net> <github@metaliveblog.com>
|
||||
Andy Smith <github@anarkystic.com>
|
||||
Ankush Agarwal <ankushagarwal11@gmail.com> <ankushagarwal@users.noreply.github.com>
|
||||
Antonio Murdaca <antonio.murdaca@gmail.com> <amurdaca@redhat.com>
|
||||
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@redhat.com>
|
||||
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@users.noreply.github.com>
|
||||
Anuj Bahuguna <anujbahuguna.dev@gmail.com>
|
||||
Anuj Bahuguna <anujbahuguna.dev@gmail.com> <abahuguna@fiberlink.com>
|
||||
Anusha Ragunathan <anusha.ragunathan@docker.com> <anusha@docker.com>
|
||||
Arko Dasgupta <arko.dasgupta@docker.com>
|
||||
Arko Dasgupta <arko.dasgupta@docker.com> <arkodg@users.noreply.github.com>
|
||||
Arnaud Porterie <arnaud.porterie@docker.com>
|
||||
Arnaud Porterie <arnaud.porterie@docker.com> <icecrime@gmail.com>
|
||||
Arthur Gautier <baloo@gandi.net> <superbaloo+registrations.github@superbaloo.net>
|
||||
Avi Miller <avi.miller@oracle.com> <avi.miller@gmail.com>
|
||||
Ben Bonnefoy <frenchben@docker.com>
|
||||
Ben Golub <ben.golub@dotcloud.com>
|
||||
Ben Toews <mastahyeti@gmail.com> <mastahyeti@users.noreply.github.com>
|
||||
Benny Ng <benny.tpng@gmail.com>
|
||||
Benoit Chesneau <bchesneau@gmail.com>
|
||||
Bevisy Zhang <binbin36520@gmail.com>
|
||||
Bhiraj Butala <abhiraj.butala@gmail.com>
|
||||
Bhumika Bayani <bhumikabayani@gmail.com>
|
||||
Bilal Amarni <bilal.amarni@gmail.com> <bamarni@users.noreply.github.com>
|
||||
Bill Wang <ozbillwang@gmail.com> <SydOps@users.noreply.github.com>
|
||||
Bily Zhang <xcoder@tenxcloud.com>
|
||||
Bin Liu <liubin0329@gmail.com>
|
||||
Bin Liu <liubin0329@gmail.com> <liubin0329@users.noreply.github.com>
|
||||
Bingshen Wang <bingshen.wbs@alibaba-inc.com>
|
||||
Boaz Shuster <ripcurld.github@gmail.com>
|
||||
Boqin Qin <bobbqqin@gmail.com>
|
||||
Brandon Philips <brandon.philips@coreos.com> <brandon@ifup.co>
|
||||
Brandon Philips <brandon.philips@coreos.com> <brandon@ifup.org>
|
||||
Brent Salisbury <brent.salisbury@docker.com> <brent@docker.com>
|
||||
Brian Goff <cpuguy83@gmail.com>
|
||||
Brian Goff <cpuguy83@gmail.com> <bgoff@cpuguy83-mbp.home>
|
||||
Brian Goff <cpuguy83@gmail.com> <bgoff@cpuguy83-mbp.local>
|
||||
Carlos de Paula <me@carlosedp.com>
|
||||
Chander Govindarajan <chandergovind@gmail.com>
|
||||
Chao Wang <wangchao.fnst@cn.fujitsu.com> <chaowang@localhost.localdomain>
|
||||
Charles Hooper <charles.hooper@dotcloud.com> <chooper@plumata.com>
|
||||
Chen Chao <cc272309126@gmail.com>
|
||||
Chen Chuanliang <chen.chuanliang@zte.com.cn>
|
||||
Chen Mingjie <chenmingjie0828@163.com>
|
||||
Chen Qiu <cheney-90@hotmail.com>
|
||||
Chen Qiu <cheney-90@hotmail.com> <21321229@zju.edu.cn>
|
||||
Chengfei Shang <cfshang@alauda.io>
|
||||
Chris Dias <cdias@microsoft.com>
|
||||
Chris McKinnel <chris.mckinnel@tangentlabs.co.uk>
|
||||
Chris Price <cprice@mirantis.com>
|
||||
Chris Price <cprice@mirantis.com> <chris.price@docker.com>
|
||||
Christopher Biscardi <biscarch@sketcht.com>
|
||||
Christopher Latham <sudosurootdev@gmail.com>
|
||||
Christy Norman <christy@linux.vnet.ibm.com>
|
||||
Chun Chen <ramichen@tencent.com> <chenchun.feed@gmail.com>
|
||||
Corbin Coleman <corbin.coleman@docker.com>
|
||||
Cristian Ariza <dev@cristianrz.com>
|
||||
Cristian Staretu <cristian.staretu@gmail.com>
|
||||
Cristian Staretu <cristian.staretu@gmail.com> <unclejack@users.noreply.github.com>
|
||||
Cristian Staretu <cristian.staretu@gmail.com> <unclejacksons@gmail.com>
|
||||
CUI Wei <ghostplant@qq.com> cuiwei13 <cuiwei13@pku.edu.cn>
|
||||
Daehyeok Mun <daehyeok@gmail.com>
|
||||
Daehyeok Mun <daehyeok@gmail.com> <daehyeok@daehyeok-ui-MacBook-Air.local>
|
||||
Daehyeok Mun <daehyeok@gmail.com> <daehyeok@daehyeokui-MacBook-Air.local>
|
||||
Dan Feldman <danf@jfrog.com>
|
||||
Daniel Dao <dqminh@cloudflare.com>
|
||||
Daniel Dao <dqminh@cloudflare.com> <dqminh89@gmail.com>
|
||||
Daniel Garcia <daniel@danielgarcia.info>
|
||||
Daniel Gasienica <daniel@gasienica.ch> <dgasienica@zynga.com>
|
||||
Daniel Goosen <daniel.goosen@surveysampling.com> <djgoosen@users.noreply.github.com>
|
||||
Daniel Grunwell <mwgrunny@gmail.com>
|
||||
Daniel Hiltgen <daniel.hiltgen@docker.com> <dhiltgen@users.noreply.github.com>
|
||||
Daniel J Walsh <dwalsh@redhat.com>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com> <daniel@dotcloud.com>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com> <mzdaniel@glidelink.net>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com> <root@vagrant-ubuntu-12.10.vagrantup.com>
|
||||
Daniel Nephin <dnephin@docker.com> <dnephin@gmail.com>
|
||||
Daniel Norberg <dano@spotify.com> <daniel.norberg@gmail.com>
|
||||
Daniel Watkins <daniel@daniel-watkins.co.uk>
|
||||
Daniel Zhang <jmzwcn@gmail.com>
|
||||
Danny Yates <danny@codeaholics.org> <Danny.Yates@mailonline.co.uk>
|
||||
Darren Shepherd <darren.s.shepherd@gmail.com> <darren@rancher.com>
|
||||
Dattatraya Kumbhar <dattatraya.kumbhar@gslab.com>
|
||||
Dave Goodchild <buddhamagnet@gmail.com>
|
||||
Dave Henderson <dhenderson@gmail.com> <Dave.Henderson@ca.ibm.com>
|
||||
Dave Tucker <dt@docker.com> <dave@dtucker.co.uk>
|
||||
David M. Karr <davidmichaelkarr@gmail.com>
|
||||
David Sheets <dsheets@docker.com> <sheets@alum.mit.edu>
|
||||
David Sissitka <me@dsissitka.com>
|
||||
David Williamson <david.williamson@docker.com> <davidwilliamson@users.noreply.github.com>
|
||||
Deshi Xiao <dxiao@redhat.com> <dsxiao@dataman-inc.com>
|
||||
Deshi Xiao <dxiao@redhat.com> <xiaods@gmail.com>
|
||||
Diego Siqueira <dieg0@live.com>
|
||||
Diogo Monica <diogo@docker.com> <diogo.monica@gmail.com>
|
||||
Dmitry Sharshakov <d3dx12.xx@gmail.com>
|
||||
Dmitry Sharshakov <d3dx12.xx@gmail.com> <sh7dm@outlook.com>
|
||||
Dominic Yin <yindongchao@inspur.com>
|
||||
Dominik Honnef <dominik@honnef.co> <dominikh@fork-bomb.org>
|
||||
Doug Davis <dug@us.ibm.com> <duglin@users.noreply.github.com>
|
||||
Doug Tangren <d.tangren@gmail.com>
|
||||
Drew Erny <derny@mirantis.com>
|
||||
Drew Erny <derny@mirantis.com> <drew.erny@docker.com>
|
||||
Elan Ruusamäe <glen@pld-linux.org>
|
||||
Elan Ruusamäe <glen@pld-linux.org> <glen@delfi.ee>
|
||||
Elango Sivanandam <elango.siva@docker.com>
|
||||
Eric G. Noriega <enoriega@vizuri.com> <egnoriega@users.noreply.github.com>
|
||||
Eric Hanchrow <ehanchrow@ine.com> <eric.hanchrow@gmail.com>
|
||||
Eric Rosenberg <ehaydenr@gmail.com> <ehaydenr@users.noreply.github.com>
|
||||
Erica Windisch <erica@windisch.us> <eric@windisch.us>
|
||||
Erica Windisch <erica@windisch.us> <ewindisch@docker.com>
|
||||
Erik Hollensbe <github@hollensbe.org> <erik+github@hollensbe.org>
|
||||
Erwin van der Koogh <info@erronis.nl>
|
||||
Ethan Bell <ebgamer29@gmail.com>
|
||||
Euan Kemp <euan.kemp@coreos.com> <euank@amazon.com>
|
||||
Eugen Krizo <eugen.krizo@gmail.com>
|
||||
Evan Hazlett <ejhazlett@gmail.com> <ehazlett@users.noreply.github.com>
|
||||
Evelyn Xu <evelynhsu21@gmail.com>
|
||||
Evgeny Shmarnev <shmarnev@gmail.com>
|
||||
Faiz Khan <faizkhan00@gmail.com>
|
||||
Fangming Fang <fangming.fang@arm.com>
|
||||
Felix Hupfeld <felix@quobyte.com> <quofelix@users.noreply.github.com>
|
||||
Felix Ruess <felix.ruess@gmail.com> <felix.ruess@roboception.de>
|
||||
Feng Yan <fy2462@gmail.com>
|
||||
Fengtu Wang <wangfengtu@huawei.com> <wangfengtu@huawei.com>
|
||||
Francisco Carriedo <fcarriedo@gmail.com>
|
||||
Frank Rosquin <frank.rosquin+github@gmail.com> <frank.rosquin@gmail.com>
|
||||
Frederick F. Kautz IV <fkautz@redhat.com> <fkautz@alumni.cmu.edu>
|
||||
Fu JinLin <withlin@yeah.net>
|
||||
Gabriel Nicolas Avellaneda <avellaneda.gabriel@gmail.com>
|
||||
Gaetan de Villele <gdevillele@gmail.com>
|
||||
Gang Qiao <qiaohai8866@gmail.com> <1373319223@qq.com>
|
||||
Geon Kim <geon0250@gmail.com>
|
||||
George Kontridze <george@bugsnag.com>
|
||||
Gerwim Feiken <g.feiken@tfe.nl> <gerwim@gmail.com>
|
||||
Giampaolo Mancini <giampaolo@trampolineup.com>
|
||||
Giovan Isa Musthofa <giovanism@outlook.co.id>
|
||||
Gopikannan Venugopalsamy <gopikannan.venugopalsamy@gmail.com>
|
||||
Gou Rao <gou@portworx.com> <gourao@users.noreply.github.com>
|
||||
Grant Millar <rid@cylo.io>
|
||||
Grant Millar <rid@cylo.io> <grant@cylo.io>
|
||||
Grant Millar <rid@cylo.io> <grant@seednet.eu>
|
||||
Greg Stephens <greg@udon.org>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com> <charmes.guillaume@gmail.com>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com> <guillaume.charmes@dotcloud.com>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com> <guillaume@charmes.net>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com> <guillaume@docker.com>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com> <guillaume@dotcloud.com>
|
||||
Guri <odg0318@gmail.com>
|
||||
Gurjeet Singh <gurjeet@singh.im> <singh.gurjeet@gmail.com>
|
||||
Gustav Sinder <gustav.sinder@gmail.com>
|
||||
Günther Jungbluth <gunther@gameslabs.net>
|
||||
Hakan Özler <hakan.ozler@kodcu.com>
|
||||
Hao Shu Wei <haosw@cn.ibm.com>
|
||||
Hao Shu Wei <haosw@cn.ibm.com> <haoshuwei1989@163.com>
|
||||
Harald Albers <github@albersweb.de> <albers@users.noreply.github.com>
|
||||
Harald Niesche <harald@niesche.de>
|
||||
Harold Cooper <hrldcpr@gmail.com>
|
||||
Harry Zhang <harryz@hyper.sh> <harryzhang@zju.edu.cn>
|
||||
Harry Zhang <harryz@hyper.sh> <resouer@163.com>
|
||||
Harry Zhang <harryz@hyper.sh> <resouer@gmail.com>
|
||||
Harry Zhang <resouer@163.com>
|
||||
Harshal Patil <harshal.patil@in.ibm.com> <harche@users.noreply.github.com>
|
||||
Helen Xie <chenjg@harmonycloud.cn>
|
||||
Hiroyuki Sasagawa <hs19870702@gmail.com>
|
||||
Hollie Teal <hollie@docker.com>
|
||||
Hollie Teal <hollie@docker.com> <hollie.teal@docker.com>
|
||||
Hollie Teal <hollie@docker.com> <hollietealok@users.noreply.github.com>
|
||||
Hu Keping <hukeping@huawei.com>
|
||||
Huu Nguyen <huu@prismskylabs.com> <whoshuu@gmail.com>
|
||||
Hyzhou Zhy <hyzhou.zhy@alibaba-inc.com>
|
||||
Hyzhou Zhy <hyzhou.zhy@alibaba-inc.com> <1187766782@qq.com>
|
||||
Ian Campbell <ian.campbell@docker.com>
|
||||
Ian Campbell <ian.campbell@docker.com> <ijc@docker.com>
|
||||
Ilya Khlopotov <ilya.khlopotov@gmail.com>
|
||||
Iskander Sharipov <quasilyte@gmail.com>
|
||||
Ivan Markin <sw@nogoegst.net> <twim@riseup.net>
|
||||
Jack Laxson <jackjrabbit@gmail.com>
|
||||
Jacob Atzen <jacob@jacobatzen.dk> <jatzen@gmail.com>
|
||||
Jacob Tomlinson <jacob@tom.linson.uk> <jacobtomlinson@users.noreply.github.com>
|
||||
Jaivish Kothari <janonymous.codevulture@gmail.com>
|
||||
James Nesbitt <jnesbitt@mirantis.com>
|
||||
James Nesbitt <jnesbitt@mirantis.com> <james.nesbitt@wunderkraut.com>
|
||||
Jamie Hannaford <jamie@limetree.org> <jamie.hannaford@rackspace.com>
|
||||
Jean Rouge <rougej+github@gmail.com> <jer329@cornell.edu>
|
||||
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
||||
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||
Jean-Tiare Le Bigot <jt@yadutaf.fr> <admin@jtlebi.fr>
|
||||
Jeff Anderson <jeff@docker.com> <jefferya@programmerq.net>
|
||||
Jeff Nickoloff <jeff.nickoloff@gmail.com> <jeff@allingeek.com>
|
||||
Jeroen Franse <jeroenfranse@gmail.com>
|
||||
Jessica Frazelle <jess@oxide.computer>
|
||||
Jessica Frazelle <jess@oxide.computer> <acidburn@docker.com>
|
||||
Jessica Frazelle <jess@oxide.computer> <acidburn@google.com>
|
||||
Jessica Frazelle <jess@oxide.computer> <acidburn@microsoft.com>
|
||||
Jessica Frazelle <jess@oxide.computer> <jess@docker.com>
|
||||
Jessica Frazelle <jess@oxide.computer> <jess@mesosphere.com>
|
||||
Jessica Frazelle <jess@oxide.computer> <jessfraz@google.com>
|
||||
Jessica Frazelle <jess@oxide.computer> <jfrazelle@users.noreply.github.com>
|
||||
Jessica Frazelle <jess@oxide.computer> <me@jessfraz.com>
|
||||
Jessica Frazelle <jess@oxide.computer> <princess@docker.com>
|
||||
Jian Liao <jliao@alauda.io>
|
||||
Jiang Jinyang <jjyruby@gmail.com>
|
||||
Jiang Jinyang <jjyruby@gmail.com> <jiangjinyang@outlook.com>
|
||||
Jim Galasyn <jim.galasyn@docker.com>
|
||||
Jiuyue Ma <majiuyue@huawei.com>
|
||||
Joey Geiger <jgeiger@gmail.com>
|
||||
Joffrey F <joffrey@docker.com>
|
||||
Joffrey F <joffrey@docker.com> <f.joffrey@gmail.com>
|
||||
Joffrey F <joffrey@docker.com> <joffrey@dotcloud.com>
|
||||
Johan Euphrosine <proppy@google.com> <proppy@aminche.com>
|
||||
John Harris <john@johnharris.io>
|
||||
John Howard <github@lowenna.com>
|
||||
John Howard <github@lowenna.com> <jhoward@microsoft.com>
|
||||
John Howard <github@lowenna.com> <jhoward@ntdev.microsoft.com>
|
||||
John Howard <github@lowenna.com> <jhowardmsft@users.noreply.github.com>
|
||||
John Howard <github@lowenna.com> <John.Howard@microsoft.com>
|
||||
John Howard <github@lowenna.com> <john.howard@microsoft.com>
|
||||
John Stephens <johnstep@docker.com> <johnstep@users.noreply.github.com>
|
||||
Jon Surrell <jon.surrell@gmail.com> <jon.surrell@automattic.com>
|
||||
Jonathan Choy <jonathan.j.choy@gmail.com>
|
||||
Jonathan Choy <jonathan.j.choy@gmail.com> <oni@tetsujinlabs.com>
|
||||
Jordan Arentsen <blissdev@gmail.com>
|
||||
Jordan Jennings <jjn2009@gmail.com> <jjn2009@users.noreply.github.com>
|
||||
Jorit Kleine-Möllhoff <joppich@bricknet.de> <joppich@users.noreply.github.com>
|
||||
Jose Diaz-Gonzalez <email@josediazgonzalez.com>
|
||||
Jose Diaz-Gonzalez <email@josediazgonzalez.com> <jose@seatgeek.com>
|
||||
Jose Diaz-Gonzalez <email@josediazgonzalez.com> <josegonzalez@users.noreply.github.com>
|
||||
Josh Bonczkowski <josh.bonczkowski@gmail.com>
|
||||
Josh Eveleth <joshe@opendns.com> <jeveleth@users.noreply.github.com>
|
||||
Josh Hawn <josh.hawn@docker.com> <jlhawn@berkeley.edu>
|
||||
Josh Horwitz <horwitz@addthis.com> <horwitzja@gmail.com>
|
||||
Josh Soref <jsoref@gmail.com> <jsoref@users.noreply.github.com>
|
||||
Josh Wilson <josh.wilson@fivestars.com> <jcwilson@users.noreply.github.com>
|
||||
Joyce Jang <mail@joycejang.com>
|
||||
Julien Bordellier <julienbordellier@gmail.com> <git@julienbordellier.com>
|
||||
Julien Bordellier <julienbordellier@gmail.com> <me@julienbordellier.com>
|
||||
Justin Cormack <justin.cormack@docker.com>
|
||||
Justin Cormack <justin.cormack@docker.com> <justin.cormack@unikernel.com>
|
||||
Justin Cormack <justin.cormack@docker.com> <justin@specialbusservice.com>
|
||||
Justin Simonelis <justin.p.simonelis@gmail.com> <justin.simonelis@PTS-JSIMON2.toronto.exclamation.com>
|
||||
Justin Terry <juterry@microsoft.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@docker.com> <jerome.petazzoni@dotcloud.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@docker.com> <jerome.petazzoni@gmail.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@docker.com> <jp@enix.org>
|
||||
K. Heller <pestophagous@gmail.com> <pestophagous@users.noreply.github.com>
|
||||
Kai Qiang Wu (Kennan) <wkq5325@gmail.com>
|
||||
Kai Qiang Wu (Kennan) <wkq5325@gmail.com> <wkqwu@cn.ibm.com>
|
||||
Kamil Domański <kamil@domanski.co>
|
||||
Kamjar Gerami <kami.gerami@gmail.com>
|
||||
Karthik Nayak <karthik.188@gmail.com>
|
||||
Karthik Nayak <karthik.188@gmail.com> <Karthik.188@gmail.com>
|
||||
Ken Cochrane <kencochrane@gmail.com> <KenCochrane@gmail.com>
|
||||
Ken Herner <kherner@progress.com> <chosenken@gmail.com>
|
||||
Ken Reese <krrgithub@gmail.com>
|
||||
Kenfe-Mickaël Laventure <mickael.laventure@gmail.com>
|
||||
Kevin Feyrer <kevin.feyrer@btinternet.com> <kevinfeyrer@users.noreply.github.com>
|
||||
Kevin Kern <kaiwentan@harmonycloud.cn>
|
||||
Kevin Meredith <kevin.m.meredith@gmail.com>
|
||||
Kir Kolyshkin <kolyshkin@gmail.com>
|
||||
Kir Kolyshkin <kolyshkin@gmail.com> <kir@openvz.org>
|
||||
Kir Kolyshkin <kolyshkin@gmail.com> <kolyshkin@users.noreply.github.com>
|
||||
Konrad Kleine <konrad.wilhelm.kleine@gmail.com> <kwk@users.noreply.github.com>
|
||||
Konstantin Gribov <grossws@gmail.com>
|
||||
Konstantin Pelykh <kpelykh@zettaset.com>
|
||||
Kotaro Yoshimatsu <kotaro.yoshimatsu@gmail.com>
|
||||
Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp> <kunal.kushwaha@gmail.com>
|
||||
Lajos Papp <lajos.papp@sequenceiq.com> <lalyos@yahoo.com>
|
||||
Lei Gong <lgong@alauda.io>
|
||||
Lei Jitang <leijitang@huawei.com>
|
||||
Lei Jitang <leijitang@huawei.com> <leijitang@gmail.com>
|
||||
Liang Mingqiang <mqliang.zju@gmail.com>
|
||||
Liang-Chi Hsieh <viirya@gmail.com>
|
||||
Liao Qingwei <liaoqingwei@huawei.com>
|
||||
Linus Heckemann <lheckemann@twig-world.com>
|
||||
Linus Heckemann <lheckemann@twig-world.com> <anonymouse2048@gmail.com>
|
||||
Lokesh Mandvekar <lsm5@fedoraproject.org> <lsm5@redhat.com>
|
||||
Lorenzo Fontana <fontanalorenz@gmail.com> <fontanalorenzo@me.com>
|
||||
Lorenzo Fontana <fontanalorenz@gmail.com> <lo@linux.com>
|
||||
Louis Opter <kalessin@kalessin.fr>
|
||||
Louis Opter <kalessin@kalessin.fr> <louis@dotcloud.com>
|
||||
Luca Favatella <luca.favatella@erlang-solutions.com> <lucafavatella@users.noreply.github.com>
|
||||
Luke Marsden <me@lukemarsden.net> <luke@digital-crocus.com>
|
||||
Lyn <energylyn@zju.edu.cn>
|
||||
Lynda O'Leary <lyndaoleary29@gmail.com>
|
||||
Lynda O'Leary <lyndaoleary29@gmail.com> <lyndaoleary@hotmail.com>
|
||||
Ma Müller <mueller-ma@users.noreply.github.com>
|
||||
Madhan Raj Mookkandy <MadhanRaj.Mookkandy@microsoft.com> <madhanm@microsoft.com>
|
||||
Madhu Venugopal <madhu@socketplane.io> <madhu@docker.com>
|
||||
Mageee <fangpuyi@foxmail.com> <21521230.zju.edu.cn>
|
||||
Mansi Nahar <mmn4185@rit.edu> <mansi.nahar@macbookpro-mansinahar.local>
|
||||
Mansi Nahar <mmn4185@rit.edu> <mansinahar@users.noreply.github.com>
|
||||
Marc Abramowitz <marc@marc-abramowitz.com> <msabramo@gmail.com>
|
||||
Marcelo Horacio Fortino <info@fortinux.com> <fortinux@users.noreply.github.com>
|
||||
Marcus Linke <marcus.linke@gmx.de>
|
||||
Marianna Tessel <mtesselh@gmail.com>
|
||||
Mark Oates <fl0yd@me.com>
|
||||
Markan Patel <mpatel678@gmail.com>
|
||||
Markus Kortlang <hyp3rdino@googlemail.com> <markus.kortlang@lhsystems.com>
|
||||
Martin Redmond <redmond.martin@gmail.com> <martin@tinychat.com>
|
||||
Martin Redmond <redmond.martin@gmail.com> <xgithub@redmond5.com>
|
||||
Mary Anthony <mary.anthony@docker.com> <mary@docker.com>
|
||||
Mary Anthony <mary.anthony@docker.com> <moxieandmore@gmail.com>
|
||||
Mary Anthony <mary.anthony@docker.com> moxiegirl <mary@docker.com>
|
||||
Masato Ohba <over.rye@gmail.com>
|
||||
Matt Bentley <matt.bentley@docker.com> <mbentley@mbentley.net>
|
||||
Matt Schurenko <matt.schurenko@gmail.com>
|
||||
Matt Williams <mattyw@me.com>
|
||||
Matt Williams <mattyw@me.com> <gh@mattyw.net>
|
||||
Matthew Heon <mheon@redhat.com> <mheon@mheonlaptop.redhat.com>
|
||||
Matthew Mosesohn <raytrac3r@gmail.com>
|
||||
Matthew Mueller <mattmuelle@gmail.com>
|
||||
Matthias Kühnle <git.nivoc@neverbox.com> <kuehnle@online.de>
|
||||
Mauricio Garavaglia <mauricio@medallia.com> <mauriciogaravaglia@gmail.com>
|
||||
Maxwell <csuhp007@gmail.com>
|
||||
Maxwell <csuhp007@gmail.com> <csuhqg@foxmail.com>
|
||||
Michael Crosby <michael@docker.com> <crosby.michael@gmail.com>
|
||||
Michael Crosby <michael@docker.com> <crosbymichael@gmail.com>
|
||||
Michael Crosby <michael@docker.com> <michael@crosbymichael.com>
|
||||
Michael Hudson-Doyle <michael.hudson@canonical.com> <michael.hudson@linaro.org>
|
||||
Michael Huettermann <michael@huettermann.net>
|
||||
Michael Käufl <docker@c.michael-kaeufl.de> <michael-k@users.noreply.github.com>
|
||||
Michael Nussbaum <michael.nussbaum@getbraintree.com>
|
||||
Michael Nussbaum <michael.nussbaum@getbraintree.com> <code@getbraintree.com>
|
||||
Michael Spetsiotis <michael_spets@hotmail.com>
|
||||
Michal Minář <miminar@redhat.com>
|
||||
Michał Gryko <github@odkurzacz.org>
|
||||
Michiel de Jong <michiel@unhosted.org>
|
||||
Mickaël Fortunato <morsi.morsicus@gmail.com>
|
||||
Miguel Angel Alvarez Cabrerizo <doncicuto@gmail.com> <30386061+doncicuto@users.noreply.github.com>
|
||||
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
||||
Mihai Borobocea <MihaiBorob@gmail.com> <MihaiBorobocea@gmail.com>
|
||||
Mike Casas <mkcsas0@gmail.com> <mikecasas@users.noreply.github.com>
|
||||
Mike Goelzer <mike.goelzer@docker.com> <mgoelzer@docker.com>
|
||||
Milind Chawre <milindchawre@gmail.com>
|
||||
Misty Stanley-Jones <misty@docker.com> <misty@apache.org>
|
||||
Mohit Soni <mosoni@ebay.com> <mohitsoni1989@gmail.com>
|
||||
Moorthy RS <rsmoorthy@gmail.com> <rsmoorthy@users.noreply.github.com>
|
||||
Moysés Borges <moysesb@gmail.com>
|
||||
Moysés Borges <moysesb@gmail.com> <moyses.furtado@wplex.com.br>
|
||||
Nace Oroz <orkica@gmail.com>
|
||||
Natasha Jarus <linuxmercedes@gmail.com>
|
||||
Nathan LeClaire <nathan.leclaire@docker.com> <nathan.leclaire@gmail.com>
|
||||
Nathan LeClaire <nathan.leclaire@docker.com> <nathanleclaire@gmail.com>
|
||||
Neil Horman <nhorman@tuxdriver.com> <nhorman@hmswarspite.think-freely.org>
|
||||
Nick Russo <nicholasjamesrusso@gmail.com> <nicholasrusso@icloud.com>
|
||||
Nicolas Borboën <ponsfrilus@gmail.com> <ponsfrilus@users.noreply.github.com>
|
||||
Nigel Poulton <nigelpoulton@hotmail.com>
|
||||
Nik Nyby <nikolas@gnu.org> <nnyby@columbia.edu>
|
||||
Nolan Darilek <nolan@thewordnerd.info>
|
||||
O.S. Tezer <ostezer@gmail.com>
|
||||
O.S. Tezer <ostezer@gmail.com> <ostezer@users.noreply.github.com>
|
||||
Oh Jinkyun <tintypemolly@gmail.com> <tintypemolly@Ohui-MacBook-Pro.local>
|
||||
Oliver Reason <oli@overrateddev.co>
|
||||
Olli Janatuinen <olli.janatuinen@gmail.com>
|
||||
Olli Janatuinen <olli.janatuinen@gmail.com> <olljanat@users.noreply.github.com>
|
||||
Ouyang Liduo <oyld0210@163.com>
|
||||
Patrick Stapleton <github@gdi2290.com>
|
||||
Paul Liljenberg <liljenberg.paul@gmail.com> <letters@paulnotcom.se>
|
||||
Pavel Tikhomirov <ptikhomirov@virtuozzo.com> <ptikhomirov@parallels.com>
|
||||
Pawel Konczalski <mail@konczalski.de>
|
||||
Peter Choi <phkchoi89@gmail.com> <reikani@Peters-MacBook-Pro.local>
|
||||
Peter Dave Hello <hsu@peterdavehello.org> <PeterDaveHello@users.noreply.github.com>
|
||||
Peter Jaffe <pjaffe@nevo.com>
|
||||
Peter Nagy <xificurC@gmail.com> <pnagy@gratex.com>
|
||||
Peter Waller <p@pwaller.net> <peter@scraperwiki.com>
|
||||
Phil Estes <estesp@linux.vnet.ibm.com> <estesp@gmail.com>
|
||||
Philip Alexander Etling <paetling@gmail.com>
|
||||
Philipp Gillé <philipp.gille@gmail.com> <philippgille@users.noreply.github.com>
|
||||
Prasanna Gautam <prasannagautam@gmail.com>
|
||||
Qiang Huang <h.huangqiang@huawei.com>
|
||||
Qiang Huang <h.huangqiang@huawei.com> <qhuang@10.0.2.15>
|
||||
Ray Tsang <rayt@google.com> <saturnism@users.noreply.github.com>
|
||||
Renaud Gaubert <rgaubert@nvidia.com> <renaud.gaubert@gmail.com>
|
||||
Robert Terhaar <rterhaar@atlanticdynamic.com> <robbyt@users.noreply.github.com>
|
||||
Roberto G. Hashioka <roberto.hashioka@docker.com> <roberto_hashioka@hotmail.com>
|
||||
Roberto Muñoz Fernández <robertomf@gmail.com> <roberto.munoz.fernandez.contractor@bbva.com>
|
||||
Robin Thoni <robin@rthoni.com>
|
||||
Roman Dudin <katrmr@gmail.com> <decadent@users.noreply.github.com>
|
||||
Rong Zhang <rongzhang@alauda.io>
|
||||
Rongxiang Song <tinysong1226@gmail.com>
|
||||
Ross Boucher <rboucher@gmail.com>
|
||||
Rui Cao <ruicao@alauda.io>
|
||||
Runshen Zhu <runshen.zhu@gmail.com>
|
||||
Ryan Stelly <ryan.stelly@live.com>
|
||||
Sakeven Jiang <jc5930@sina.cn>
|
||||
Sandeep Bansal <sabansal@microsoft.com>
|
||||
Sandeep Bansal <sabansal@microsoft.com> <msabansal@microsoft.com>
|
||||
Sargun Dhillon <sargun@netflix.com> <sargun@sargun.me>
|
||||
Sean Lee <seanlee@tw.ibm.com> <scaleoutsean@users.noreply.github.com>
|
||||
Sebastiaan van Stijn <github@gone.nl> <sebastiaan@ws-key-sebas3.dpi1.dpi>
|
||||
Sebastiaan van Stijn <github@gone.nl> <thaJeztah@users.noreply.github.com>
|
||||
Shaun Kaasten <shaunk@gmail.com>
|
||||
Shawn Landden <shawn@churchofgit.com> <shawnlandden@gmail.com>
|
||||
Shengbo Song <thomassong@tencent.com>
|
||||
Shengbo Song <thomassong@tencent.com> <mymneo@163.com>
|
||||
Shih-Yuan Lee <fourdollars@gmail.com>
|
||||
Shishir Mahajan <shishir.mahajan@redhat.com> <smahajan@redhat.com>
|
||||
Shu-Wai Chow <shu-wai.chow@seattlechildrens.org>
|
||||
Shukui Yang <yangshukui@huawei.com>
|
||||
Shuwei Hao <haosw@cn.ibm.com>
|
||||
Shuwei Hao <haosw@cn.ibm.com> <haoshuwei24@gmail.com>
|
||||
Sidhartha Mani <sidharthamn@gmail.com>
|
||||
Sjoerd Langkemper <sjoerd-github@linuxonly.nl> <sjoerd@byte.nl>
|
||||
Solomon Hykes <solomon@docker.com> <s@docker.com>
|
||||
Solomon Hykes <solomon@docker.com> <solomon.hykes@dotcloud.com>
|
||||
Solomon Hykes <solomon@docker.com> <solomon@dotcloud.com>
|
||||
Soshi Katsuta <soshi.katsuta@gmail.com>
|
||||
Soshi Katsuta <soshi.katsuta@gmail.com> <katsuta_soshi@cyberagent.co.jp>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com> <github@srid.name>
|
||||
Srini Brahmaroutu <srbrahma@us.ibm.com> <sbrahma@us.ibm.com>
|
||||
Srinivasan Srivatsan <srinivasan.srivatsan@hpe.com> <srinsriv@users.noreply.github.com>
|
||||
Stefan Berger <stefanb@linux.vnet.ibm.com>
|
||||
Stefan Berger <stefanb@linux.vnet.ibm.com> <stefanb@us.ibm.com>
|
||||
Stefan J. Wernli <swernli@microsoft.com> <swernli@ntdev.microsoft.com>
|
||||
Stefan S. <tronicum@user.github.com>
|
||||
Stefan Scherer <stefan.scherer@docker.com>
|
||||
Stefan Scherer <stefan.scherer@docker.com> <scherer_stefan@icloud.com>
|
||||
Stephan Spindler <shutefan@gmail.com> <shutefan@users.noreply.github.com>
|
||||
Stephen Day <stevvooe@gmail.com>
|
||||
Stephen Day <stevvooe@gmail.com> <stephen.day@docker.com>
|
||||
Stephen Day <stevvooe@gmail.com> <stevvooe@users.noreply.github.com>
|
||||
Steve Desmond <steve@vtsv.ca> <stevedesmond-ca@users.noreply.github.com>
|
||||
Sun Gengze <690388648@qq.com>
|
||||
Sun Jianbo <wonderflow.sun@gmail.com>
|
||||
Sun Jianbo <wonderflow.sun@gmail.com> <wonderflow@zju.edu.cn>
|
||||
Sven Dowideit <SvenDowideit@home.org.au>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <sven@t440s.home.gateway>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@docker.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@fosiki.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@home.org.au>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@users.noreply.github.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <¨SvenDowideit@home.org.au¨>
|
||||
Sylvain Bellemare <sylvain@ascribe.io>
|
||||
Sylvain Bellemare <sylvain@ascribe.io> <sylvain.bellemare@ezeep.com>
|
||||
Tangi Colin <tangicolin@gmail.com>
|
||||
Erwin van der Koogh <info@erronis.nl>
|
||||
Ahmed Kamal <email.ahmedkamal@googlemail.com>
|
||||
Tejesh Mehta <tejesh.mehta@gmail.com> <tj@init.me>
|
||||
Cristian Staretu <cristian.staretu@gmail.com>
|
||||
Cristian Staretu <cristian.staretu@gmail.com> <unclejacksons@gmail.com>
|
||||
Cristian Staretu <cristian.staretu@gmail.com> <unclejack@users.noreply.github.com>
|
||||
Marcus Linke <marcus.linke@gmx.de>
|
||||
Aleksandrs Fadins <aleks@s-ko.net>
|
||||
Christopher Latham <sudosurootdev@gmail.com>
|
||||
Hu Keping <hukeping@huawei.com>
|
||||
Wayne Chang <wayne@neverfear.org>
|
||||
Chen Chao <cc272309126@gmail.com>
|
||||
Daehyeok Mun <daehyeok@gmail.com>
|
||||
<daehyeok@gmail.com> <daehyeok@daehyeokui-MacBook-Air.local>
|
||||
<jt@yadutaf.fr> <admin@jtlebi.fr>
|
||||
<jeff@docker.com> <jefferya@programmerq.net>
|
||||
<charles.hooper@dotcloud.com> <chooper@plumata.com>
|
||||
<daniel.mizyrycki@dotcloud.com> <daniel@dotcloud.com>
|
||||
<daniel.mizyrycki@dotcloud.com> <mzdaniel@glidelink.net>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com> <charmes.guillaume@gmail.com>
|
||||
<guillaume.charmes@docker.com> <guillaume@dotcloud.com>
|
||||
<guillaume.charmes@docker.com> <guillaume@docker.com>
|
||||
<guillaume.charmes@docker.com> <guillaume.charmes@dotcloud.com>
|
||||
<guillaume.charmes@docker.com> <guillaume@charmes.net>
|
||||
<kencochrane@gmail.com> <KenCochrane@gmail.com>
|
||||
Thatcher Peskens <thatcher@docker.com>
|
||||
Thatcher Peskens <thatcher@docker.com> <thatcher@dotcloud.com>
|
||||
Thatcher Peskens <thatcher@docker.com> <thatcher@gmx.net>
|
||||
Thomas Gazagnaire <thomas@gazagnaire.org> <thomas@gazagnaire.com>
|
||||
Thomas Léveil <thomasleveil@gmail.com>
|
||||
Thomas Léveil <thomasleveil@gmail.com> <thomasleveil@users.noreply.github.com>
|
||||
Thatcher Peskens <thatcher@docker.com> dhrp <thatcher@gmx.net>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com> jpetazzo <jerome.petazzoni@dotcloud.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com> <jp@enix.org>
|
||||
Joffrey F <joffrey@docker.com>
|
||||
Joffrey F <joffrey@docker.com> <joffrey@dotcloud.com>
|
||||
Joffrey F <joffrey@docker.com> <f.joffrey@gmail.com>
|
||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||
Andy Smith <github@anarkystic.com>
|
||||
<kalessin@kalessin.fr> <louis@dotcloud.com>
|
||||
<victor.vieux@docker.com> <victor.vieux@dotcloud.com>
|
||||
<victor.vieux@docker.com> <victor@dotcloud.com>
|
||||
<victor.vieux@docker.com> <dev@vvieux.com>
|
||||
<victor.vieux@docker.com> <victor@docker.com>
|
||||
<victor.vieux@docker.com> <vieux@docker.com>
|
||||
<victor.vieux@docker.com> <victorvieux@gmail.com>
|
||||
<dominik@honnef.co> <dominikh@fork-bomb.org>
|
||||
<ehanchrow@ine.com> <eric.hanchrow@gmail.com>
|
||||
Walter Stanish <walter@pratyeka.org>
|
||||
<daniel@gasienica.ch> <dgasienica@zynga.com>
|
||||
Roberto Hashioka <roberto_hashioka@hotmail.com>
|
||||
Konstantin Pelykh <kpelykh@zettaset.com>
|
||||
David Sissitka <me@dsissitka.com>
|
||||
Nolan Darilek <nolan@thewordnerd.info>
|
||||
<mastahyeti@gmail.com> <mastahyeti@users.noreply.github.com>
|
||||
Benoit Chesneau <bchesneau@gmail.com>
|
||||
Jordan Arentsen <blissdev@gmail.com>
|
||||
Daniel Garcia <daniel@danielgarcia.info>
|
||||
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
||||
Bhiraj Butala <abhiraj.butala@gmail.com>
|
||||
Faiz Khan <faizkhan00@gmail.com>
|
||||
Victor Lyuboslavsky <victor@victoreda.com>
|
||||
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
||||
Matthew Mueller <mattmuelle@gmail.com>
|
||||
<mosoni@ebay.com> <mohitsoni1989@gmail.com>
|
||||
Shih-Yuan Lee <fourdollars@gmail.com>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com> root <root@vagrant-ubuntu-12.10.vagrantup.com>
|
||||
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||
<proppy@google.com> <proppy@aminche.com>
|
||||
<michael@docker.com> <michael@crosbymichael.com>
|
||||
<michael@docker.com> <crosby.michael@gmail.com>
|
||||
<michael@docker.com> <crosbymichael@gmail.com>
|
||||
<github@developersupport.net> <github@metaliveblog.com>
|
||||
<brandon@ifup.org> <brandon@ifup.co>
|
||||
<dano@spotify.com> <daniel.norberg@gmail.com>
|
||||
<danny@codeaholics.org> <Danny.Yates@mailonline.co.uk>
|
||||
<gurjeet@singh.im> <singh.gurjeet@gmail.com>
|
||||
<shawn@churchofgit.com> <shawnlandden@gmail.com>
|
||||
<sjoerd-github@linuxonly.nl> <sjoerd@byte.nl>
|
||||
<solomon@docker.com> <solomon.hykes@dotcloud.com>
|
||||
<solomon@docker.com> <solomon@dotcloud.com>
|
||||
<solomon@docker.com> <s@docker.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@fosiki.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@docker.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <¨SvenDowideit@home.org.au¨>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@users.noreply.github.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <sven@t440s.home.gateway>
|
||||
<alexl@redhat.com> <alexander.larsson@gmail.com>
|
||||
Alexandr Morozov <lk4d4math@gmail.com>
|
||||
<git.nivoc@neverbox.com> <kuehnle@online.de>
|
||||
O.S. Tezer <ostezer@gmail.com>
|
||||
<ostezer@gmail.com> <ostezer@users.noreply.github.com>
|
||||
Roberto G. Hashioka <roberto.hashioka@docker.com> <roberto_hashioka@hotmail.com>
|
||||
<justin.p.simonelis@gmail.com> <justin.simonelis@PTS-JSIMON2.toronto.exclamation.com>
|
||||
<taim@bosboot.org> <maztaim@users.noreply.github.com>
|
||||
<viktor.vojnovski@amadeus.com> <vojnovski@gmail.com>
|
||||
<vbatts@redhat.com> <vbatts@hashbangbash.com>
|
||||
<altsysrq@gmail.com> <iamironbob@gmail.com>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com> <github@srid.name>
|
||||
Liang-Chi Hsieh <viirya@gmail.com>
|
||||
Aleksa Sarai <cyphar@cyphar.com>
|
||||
Will Weaver <monkey@buildingbananas.com>
|
||||
Timothy Hobbs <timothyhobbs@seznam.cz>
|
||||
Nathan LeClaire <nathan.leclaire@docker.com> <nathan.leclaire@gmail.com>
|
||||
Nathan LeClaire <nathan.leclaire@docker.com> <nathanleclaire@gmail.com>
|
||||
<github@hollensbe.org> <erik+github@hollensbe.org>
|
||||
<github@albersweb.de> <albers@users.noreply.github.com>
|
||||
<lsm5@fedoraproject.org> <lsm5@redhat.com>
|
||||
<marc@marc-abramowitz.com> <msabramo@gmail.com>
|
||||
Matthew Heon <mheon@redhat.com> <mheon@mheonlaptop.redhat.com>
|
||||
<bernat@luffy.cx> <vincent@bernat.im>
|
||||
<p@pwaller.net> <peter@scraperwiki.com>
|
||||
<andrew.weiss@outlook.com> <andrew.weiss@microsoft.com>
|
||||
Francisco Carriedo <fcarriedo@gmail.com>
|
||||
<julienbordellier@gmail.com> <git@julienbordellier.com>
|
||||
<ahmetb@microsoft.com> <ahmetalpbalkan@gmail.com>
|
||||
<lk4d4@docker.com> <lk4d4math@gmail.com>
|
||||
<arnaud.porterie@docker.com> <icecrime@gmail.com>
|
||||
<baloo@gandi.net> <superbaloo+registrations.github@superbaloo.net>
|
||||
Brian Goff <cpuguy83@gmail.com>
|
||||
<cpuguy83@gmail.com> <bgoff@cpuguy83-mbp.home>
|
||||
<ewindisch@docker.com> <eric@windisch.us>
|
||||
<frank.rosquin+github@gmail.com> <frank.rosquin@gmail.com>
|
||||
Hollie Teal <hollie@docker.com>
|
||||
<hollie@docker.com> <hollie.teal@docker.com>
|
||||
<hollie@docker.com> <hollietealok@users.noreply.github.com>
|
||||
<huu@prismskylabs.com> <whoshuu@gmail.com>
|
||||
Jessica Frazelle <jess@docker.com> Jessie Frazelle <jfrazelle@users.noreply.github.com>
|
||||
<jess@docker.com> <jfrazelle@users.noreply.github.com>
|
||||
<konrad.wilhelm.kleine@gmail.com> <kwk@users.noreply.github.com>
|
||||
<tintypemolly@gmail.com> <tintypemolly@Ohui-MacBook-Pro.local>
|
||||
<estesp@linux.vnet.ibm.com> <estesp@gmail.com>
|
||||
<github@gone.nl> <thaJeztah@users.noreply.github.com>
|
||||
Thomas LEVEIL <thomasleveil@gmail.com> Thomas LÉVEIL <thomasleveil@users.noreply.github.com>
|
||||
<oi@truffles.me.uk> <timruffles@googlemail.com>
|
||||
<Vincent.Bernat@exoscale.ch> <bernat@luffy.cx>
|
||||
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>
|
||||
Tim Bart <tim@fewagainstmany.com>
|
||||
Tim Bosse <taim@bosboot.org> <maztaim@users.noreply.github.com>
|
||||
Tim Ruffles <oi@truffles.me.uk> <timruffles@googlemail.com>
|
||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||
Tim Zju <21651152@zju.edu.cn>
|
||||
Timothy Hobbs <timothyhobbs@seznam.cz>
|
||||
Toli Kuznets <toli@docker.com>
|
||||
Tom Barlow <tomwbarlow@gmail.com>
|
||||
Tom Sweeney <tsweeney@redhat.com>
|
||||
Tõnis Tiigi <tonistiigi@gmail.com>
|
||||
Trace Andreason <tandreason@gmail.com>
|
||||
Trishna Guha <trishnaguha17@gmail.com>
|
||||
Tristan Carel <tristan@cogniteev.com>
|
||||
Tristan Carel <tristan@cogniteev.com> <tristan.carel@gmail.com>
|
||||
Tyler Brown <tylers.pile@gmail.com>
|
||||
Umesh Yadav <umesh4257@gmail.com>
|
||||
Umesh Yadav <umesh4257@gmail.com> <dungeonmaster18@users.noreply.github.com>
|
||||
Victor Lyuboslavsky <victor@victoreda.com>
|
||||
Victor Vieux <victor.vieux@docker.com> <dev@vvieux.com>
|
||||
Victor Vieux <victor.vieux@docker.com> <victor.vieux@dotcloud.com>
|
||||
Victor Vieux <victor.vieux@docker.com> <victor@docker.com>
|
||||
Victor Vieux <victor.vieux@docker.com> <victor@dotcloud.com>
|
||||
Victor Vieux <victor.vieux@docker.com> <victorvieux@gmail.com>
|
||||
Victor Vieux <victor.vieux@docker.com> <vieux@docker.com>
|
||||
Vikram bir Singh <vsingh@mirantis.com>
|
||||
Vikram bir Singh <vsingh@mirantis.com> <vikrambir.singh@docker.com>
|
||||
Viktor Vojnovski <viktor.vojnovski@amadeus.com> <vojnovski@gmail.com>
|
||||
Vincent Batts <vbatts@redhat.com> <vbatts@hashbangbash.com>
|
||||
Vincent Bernat <Vincent.Bernat@exoscale.ch> <bernat@luffy.cx>
|
||||
Vincent Bernat <Vincent.Bernat@exoscale.ch> <vincent@bernat.im>
|
||||
Vincent Boulineau <vincent.boulineau@datadoghq.com>
|
||||
Vincent Demeester <vincent.demeester@docker.com> <vincent+github@demeester.fr>
|
||||
Vincent Demeester <vincent.demeester@docker.com> <vincent@demeester.fr>
|
||||
Vincent Demeester <vincent.demeester@docker.com> <vincent@sbr.pm>
|
||||
Vishnu Kannan <vishnuk@google.com>
|
||||
Vitaly Ostrosablin <vostrosablin@virtuozzo.com>
|
||||
Vitaly Ostrosablin <vostrosablin@virtuozzo.com> <tmp6154@yandex.ru>
|
||||
Vladimir Rutsky <altsysrq@gmail.com> <iamironbob@gmail.com>
|
||||
Walter Stanish <walter@pratyeka.org>
|
||||
Wang Chao <chao.wang@ucloud.cn>
|
||||
Wang Chao <chao.wang@ucloud.cn> <wcwxyz@gmail.com>
|
||||
Wang Guoliang <liangcszzu@163.com>
|
||||
Wang Jie <wangjie5@chinaskycloud.com>
|
||||
Wang Ping <present.wp@icloud.com>
|
||||
Wang Xing <hzwangxing@corp.netease.com> <root@localhost>
|
||||
Wang Yuexiao <wang.yuexiao@zte.com.cn>
|
||||
Wayne Chang <wayne@neverfear.org>
|
||||
Wayne Song <wsong@docker.com> <wsong@users.noreply.github.com>
|
||||
Wei Wu <wuwei4455@gmail.com> cizixs <cizixs@163.com>
|
||||
Wenjun Tang <tangwj2@lenovo.com> <dodia@163.com>
|
||||
Wewang Xiaorenfine <wang.xiaoren@zte.com.cn>
|
||||
Will Weaver <monkey@buildingbananas.com>
|
||||
Wing-Kam Wong <wingkwong.code@gmail.com>
|
||||
Xian Chaobo <xianchaobo@huawei.com>
|
||||
Xian Chaobo <xianchaobo@huawei.com> <jimmyxian2004@yahoo.com.cn>
|
||||
Xianglin Gao <xlgao@zju.edu.cn>
|
||||
Xianlu Bird <xianlubird@gmail.com>
|
||||
Xiao YongBiao <xyb4638@gmail.com>
|
||||
Xiaodong Liu <liuxiaodong@loongson.cn>
|
||||
Xiaodong Zhang <a4012017@sina.com>
|
||||
Xiaoyu Zhang <zhang.xiaoyu33@zte.com.cn>
|
||||
Xuecong Liao <satorulogic@gmail.com>
|
||||
Yamasaki Masahide <masahide.y@gmail.com>
|
||||
Yao Zaiyong <yaozaiyong@hotmail.com>
|
||||
Yassine Tijani <yasstij11@gmail.com>
|
||||
Yazhong Liu <yorkiefixer@gmail.com>
|
||||
Vincent Bernat <bernat@luffy.cx> <Vincent.Bernat@exoscale.ch>
|
||||
Yestin Sun <sunyi0804@gmail.com> <yestin.sun@polyera.com>
|
||||
Yi EungJun <eungjun.yi@navercorp.com> <semtlenori@gmail.com>
|
||||
Ying Li <ying.li@docker.com>
|
||||
Ying Li <ying.li@docker.com> <cyli@twistedmatrix.com>
|
||||
Yong Tang <yong.tang.github@outlook.com> <yongtang@users.noreply.github.com>
|
||||
Yongxin Li <yxli@alauda.io>
|
||||
Yosef Fertel <yfertel@gmail.com> <frosforever@users.noreply.github.com>
|
||||
Yu Changchun <yuchangchun1@huawei.com>
|
||||
Yu Chengxia <yuchengxia@huawei.com>
|
||||
Yu Peng <yu.peng36@zte.com.cn>
|
||||
Yu Peng <yu.peng36@zte.com.cn> <yupeng36@zte.com.cn>
|
||||
Yue Zhang <zy675793960@yeah.net>
|
||||
Zachary Jaffee <zjaffee@us.ibm.com> <zij@case.edu>
|
||||
Zachary Jaffee <zjaffee@us.ibm.com> <zjaffee@apache.org>
|
||||
ZhangHang <stevezhang2014@gmail.com>
|
||||
Zhenkun Bi <bi.zhenkun@zte.com.cn>
|
||||
Zhou Hao <zhouhao@cn.fujitsu.com>
|
||||
Zhoulin Xie <zhoulin.xie@daocloud.io>
|
||||
Zhu Kunjia <zhu.kunjia@zte.com.cn>
|
||||
Ziheng Liu <lzhfromustc@gmail.com>
|
||||
Zou Yu <zouyu7@huawei.com>
|
||||
Zuhayr Elahi <zuhayr.elahi@docker.com>
|
||||
Zuhayr Elahi <zuhayr.elahi@docker.com> <elahi.zuhayr@gmail.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>
|
||||
|
||||
1693
CHANGELOG.md
1693
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
298
CONTRIBUTING.md
298
CONTRIBUTING.md
@@ -1,14 +1,14 @@
|
||||
# Contribute to the Moby Project
|
||||
# Contributing to Docker
|
||||
|
||||
Want to hack on the Moby Project? Awesome! We have a contributor's guide that explains
|
||||
[setting up a development environment and the contribution
|
||||
process](docs/contributing/).
|
||||
Want to hack on Docker? Awesome! We have a contributor's guide that explains
|
||||
[setting up a Docker development environment and the contribution
|
||||
process](https://docs.docker.com/project/who-written-for/).
|
||||
|
||||
[](https://docs.docker.com/opensource/project/who-written-for/)
|
||||

|
||||
|
||||
This page contains information about reporting issues as well as some tips and
|
||||
guidelines useful to experienced open source contributors. Finally, make sure
|
||||
you read our [community guidelines](#moby-community-guidelines) before you
|
||||
you read our [community guidelines](#docker-community-guidelines) before you
|
||||
start participating.
|
||||
|
||||
## Topics
|
||||
@@ -17,20 +17,20 @@ start participating.
|
||||
* [Design and Cleanup Proposals](#design-and-cleanup-proposals)
|
||||
* [Reporting Issues](#reporting-other-issues)
|
||||
* [Quick Contribution Tips and Guidelines](#quick-contribution-tips-and-guidelines)
|
||||
* [Community Guidelines](#moby-community-guidelines)
|
||||
* [Community Guidelines](#docker-community-guidelines)
|
||||
|
||||
## Reporting security issues
|
||||
|
||||
The Moby maintainers take security seriously. If you discover a security
|
||||
The Docker maintainers take security seriously. If you discover a security
|
||||
issue, please bring it to their attention right away!
|
||||
|
||||
Please **DO NOT** file a public issue, instead send your report privately to
|
||||
[security@docker.com](mailto:security@docker.com).
|
||||
|
||||
Security reports are greatly appreciated and we will publicly thank you for it,
|
||||
although we keep your name confidential if you request it. We also like to send
|
||||
gifts—if you're into schwag, make sure to let us know. We currently do not
|
||||
offer a paid security bounty program, but are not ruling it out in the future.
|
||||
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
|
||||
us know. We currently do not offer a paid security bounty program, but are not
|
||||
ruling it out in the future.
|
||||
|
||||
|
||||
## Reporting other issues
|
||||
@@ -39,40 +39,79 @@ A great way to contribute to the project is to send a detailed report when you
|
||||
encounter an issue. We always appreciate a well-written, thorough bug report,
|
||||
and will thank you for it!
|
||||
|
||||
Check that [our issue database](https://github.com/moby/moby/issues)
|
||||
Check that [our issue database](https://github.com/docker/docker/issues)
|
||||
doesn't already include that problem or suggestion before submitting an issue.
|
||||
If you find a match, you can use the "subscribe" button to get notified on
|
||||
updates. Do *not* leave random "+1" or "I have this too" comments, as they
|
||||
only clutter the discussion, and don't help resolving it. However, if you
|
||||
have ways to reproduce the issue or have additional information that may help
|
||||
resolving the issue, please leave a comment.
|
||||
If you find a match, add a quick "+1" or "I have this problem too." Doing this
|
||||
helps prioritize the most common problems and requests. **DO NOT DO THAT** to
|
||||
subscribe to the issue unless you have something meaningful to add to the
|
||||
conversation. The best way to subscribe the issue is by clicking Subscribe
|
||||
button in top right of the page.
|
||||
|
||||
When reporting issues, always include:
|
||||
When reporting issues, please include your host OS (Ubuntu 12.04, Fedora 19,
|
||||
etc). Please include:
|
||||
|
||||
* The output of `uname -a`.
|
||||
* The output of `docker version`.
|
||||
* The output of `docker info`.
|
||||
|
||||
Also include the steps required to reproduce the problem if possible and
|
||||
Please also include the steps required to reproduce the problem if possible and
|
||||
applicable. This information will help us review and fix your issue faster.
|
||||
When sending lengthy log-files, consider posting them as a gist (https://gist.github.com).
|
||||
Don't forget to remove sensitive data from your logfiles before posting (you can
|
||||
replace those parts with "REDACTED").
|
||||
|
||||
## Quick contribution tips and guidelines
|
||||
**Issue Report Template**:
|
||||
|
||||
```
|
||||
Description of problem:
|
||||
|
||||
|
||||
`docker version`:
|
||||
|
||||
|
||||
`docker info`:
|
||||
|
||||
|
||||
`uname -a`:
|
||||
|
||||
|
||||
Environment details (AWS, VirtualBox, physical, etc.):
|
||||
|
||||
|
||||
How reproducible:
|
||||
|
||||
|
||||
Steps to Reproduce:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
|
||||
Actual Results:
|
||||
|
||||
|
||||
Expected Results:
|
||||
|
||||
|
||||
Additional info:
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
##Quick contribution tips and guidelines
|
||||
|
||||
This section gives the experienced contributor some tips and guidelines.
|
||||
|
||||
### Pull requests are always welcome
|
||||
###Pull requests are always welcome
|
||||
|
||||
Not sure if that typo is worth a pull request? Found a bug and know how to fix
|
||||
it? Do it! We will appreciate it. Any significant improvement should be
|
||||
documented as [a GitHub issue](https://github.com/moby/moby/issues) before
|
||||
documented as [a GitHub issue](https://github.com/docker/docker/issues) before
|
||||
anybody starts working on it.
|
||||
|
||||
We are always thrilled to receive pull requests. We do our best to process them
|
||||
quickly. If your pull request is not accepted on the first try,
|
||||
don't get discouraged! Our contributor's guide explains [the review process we
|
||||
use for simple changes](https://docs.docker.com/opensource/workflow/make-a-contribution/).
|
||||
use for simple changes](https://docs.docker.com/project/make-a-contribution/).
|
||||
|
||||
### Design and cleanup proposals
|
||||
|
||||
@@ -80,41 +119,58 @@ You can propose new designs for existing Docker features. You can also design
|
||||
entirely new features. We really appreciate contributors who want to refactor or
|
||||
otherwise cleanup our project. For information on making these types of
|
||||
contributions, see [the advanced contribution
|
||||
section](https://docs.docker.com/opensource/workflow/advanced-contributing/) in
|
||||
the contributors guide.
|
||||
section](https://docs.docker.com/project/advanced-contributing/) in the
|
||||
contributors guide.
|
||||
|
||||
### Connect with other Moby Project contributors
|
||||
We try hard to keep Docker lean and focused. Docker can't do everything for
|
||||
everybody. This means that we might decide against incorporating a new feature.
|
||||
However, there might be a way to implement that feature *on top of* Docker.
|
||||
|
||||
### Talking to other Docker users and contributors
|
||||
|
||||
<table class="tg">
|
||||
<col width="45%">
|
||||
<col width="65%">
|
||||
<tr>
|
||||
<td>Forums</td>
|
||||
<td>Internet Relay Chat (IRC)</td>
|
||||
<td>
|
||||
A public forum for users to discuss questions and explore current design patterns and
|
||||
best practices about all the Moby projects. To participate, log in with your Github
|
||||
account or create an account at <a href="https://forums.mobyproject.org" target="_blank">https://forums.mobyproject.org</a>.
|
||||
<p>
|
||||
IRC a direct line to our most knowledgeable Docker users; we have
|
||||
both the <code>#docker</code> and <code>#docker-dev</code> group on
|
||||
<strong>irc.freenode.net</strong>.
|
||||
IRC is a rich chat protocol but it can overwhelm new users. You can search
|
||||
<a href="https://botbot.me/freenode/docker/#" target="_blank">our chat archives</a>.
|
||||
</p>
|
||||
Read our <a href="https://docs.docker.com/project/get-help/#irc-quickstart" target="_blank">IRC quickstart guide</a> for an easy way to get started.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Slack</td>
|
||||
<td>Google Groups</td>
|
||||
<td>
|
||||
<p>
|
||||
Register for the Docker Community Slack at
|
||||
<a href="https://community.docker.com/registrations/groups/4316" target="_blank">https://community.docker.com/registrations/groups/4316</a>.
|
||||
We use the #moby-project channel for general discussion, and there are separate channels for other Moby projects such as #containerd.
|
||||
Archives are available at <a href="https://dockercommunity.slackarchive.io/" target="_blank">https://dockercommunity.slackarchive.io/</a>.
|
||||
</p>
|
||||
There are two groups.
|
||||
<a href="https://groups.google.com/forum/#!forum/docker-user" target="_blank">Docker-user</a>
|
||||
is for people using Docker containers.
|
||||
The <a href="https://groups.google.com/forum/#!forum/docker-dev" target="_blank">docker-dev</a>
|
||||
group is for contributors and other people contributing to the Docker
|
||||
project.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Twitter</td>
|
||||
<td>
|
||||
You can follow <a href="https://twitter.com/moby/" target="_blank">Moby Project Twitter feed</a>
|
||||
You can follow <a href="https://twitter.com/docker/" target="_blank">Docker's Twitter feed</a>
|
||||
to get updates on our products. You can also tweet us questions or just
|
||||
share blogs or stories.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Stack Overflow</td>
|
||||
<td>
|
||||
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>
|
||||
and so do many other knowledgeable Docker users.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -128,17 +184,16 @@ Fork the repository and make changes on your fork in a feature branch:
|
||||
your intentions, and name it XXXX-something where XXXX is the number of the
|
||||
issue.
|
||||
|
||||
Submit tests for your changes. See [TESTING.md](./TESTING.md) for details.
|
||||
|
||||
If your changes need integration tests, write them against the API. The `cli`
|
||||
integration tests are slowly either migrated to API tests or moved away as unit
|
||||
tests in `docker/cli` and end-to-end tests for Docker.
|
||||
Submit unit tests for your changes. Go has a great test framework built in; use
|
||||
it! Take a look at existing tests for inspiration. [Run the full test
|
||||
suite](https://docs.docker.com/project/test-and-docs/) on your branch before
|
||||
submitting a pull request.
|
||||
|
||||
Update the documentation when creating or modifying features. Test your
|
||||
documentation changes for clarity, concision, and correctness, as well as a
|
||||
clean documentation build. See our contributors guide for [our style
|
||||
guide](https://docs.docker.com/opensource/doc-style) and instructions on [building
|
||||
the documentation](https://docs.docker.com/opensource/project/test-and-docs/#build-and-test-the-documentation).
|
||||
guide](https://docs.docker.com/project/doc-style) and instructions on [building
|
||||
the documentation](https://docs.docker.com/project/test-and-docs/#build-and-test-the-documentation).
|
||||
|
||||
Write clean code. Universally formatted code promotes ease of writing, reading,
|
||||
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
|
||||
@@ -147,64 +202,10 @@ committing your changes. Most editors have plug-ins that do this automatically.
|
||||
Pull request descriptions should be as clear as possible and include a reference
|
||||
to all the issues that they address.
|
||||
|
||||
### Successful Changes
|
||||
|
||||
Before contributing large or high impact changes, make the effort to coordinate
|
||||
with the maintainers of the project before submitting a pull request. This
|
||||
prevents you from doing extra work that may or may not be merged.
|
||||
|
||||
Large PRs that are just submitted without any prior communication are unlikely
|
||||
to be successful.
|
||||
|
||||
While pull requests are the methodology for submitting changes to code, changes
|
||||
are much more likely to be accepted if they are accompanied by additional
|
||||
engineering work. While we don't define this explicitly, most of these goals
|
||||
are accomplished through communication of the design goals and subsequent
|
||||
solutions. Often times, it helps to first state the problem before presenting
|
||||
solutions.
|
||||
|
||||
Typically, the best methods of accomplishing this are to submit an issue,
|
||||
stating the problem. This issue can include a problem statement and a
|
||||
checklist with requirements. If solutions are proposed, alternatives should be
|
||||
listed and eliminated. Even if the criteria for elimination of a solution is
|
||||
frivolous, say so.
|
||||
|
||||
Larger changes typically work best with design documents. These are focused on
|
||||
providing context to the design at the time the feature was conceived and can
|
||||
inform future documentation contributions.
|
||||
|
||||
### Commit Messages
|
||||
|
||||
Commit messages must start with a capitalized and short summary (max. 50 chars)
|
||||
written in the imperative, followed by an optional, more detailed explanatory
|
||||
text which is separated from the summary by an empty line.
|
||||
|
||||
Commit messages should follow best practices, including explaining the context
|
||||
of the problem and how it was solved, including in caveats or follow up changes
|
||||
required. They should tell the story of the change and provide readers
|
||||
understanding of what led to it.
|
||||
|
||||
If you're lost about what this even means, please see [How to Write a Git
|
||||
Commit Message](http://chris.beams.io/posts/git-commit/) for a start.
|
||||
|
||||
In practice, the best approach to maintaining a nice commit message is to
|
||||
leverage a `git add -p` and `git commit --amend` to formulate a solid
|
||||
changeset. This allows one to piece together a change, as information becomes
|
||||
available.
|
||||
|
||||
If you squash a series of commits, don't just submit that. Re-write the commit
|
||||
message, as if the series of commits was a single stroke of brilliance.
|
||||
|
||||
That said, there is no requirement to have a single commit for a PR, as long as
|
||||
each commit tells the story. For example, if there is a feature that requires a
|
||||
package, it might make sense to have the package in a separate commit then have
|
||||
a subsequent commit that uses it.
|
||||
|
||||
Remember, you're telling part of the story with the commit message. Don't make
|
||||
your chapter weird.
|
||||
|
||||
### Review
|
||||
|
||||
Code review comments may be added to your pull request. Discuss, then make the
|
||||
suggested modifications and push additional commits to your feature branch. Post
|
||||
a comment after pushing. New commits show up in the pull request automatically,
|
||||
@@ -225,9 +226,10 @@ 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
|
||||
down to one.
|
||||
|
||||
After every commit, [make sure the test suite passes](./TESTING.md). Include
|
||||
documentation changes in the same pull request so that a revert would remove
|
||||
all traces of the feature or fix.
|
||||
After every commit, [make sure the test suite passes]
|
||||
(https://docs.docker.com/project/test-and-docs/). Include documentation
|
||||
changes in the same pull request so that a revert would remove all traces of
|
||||
the feature or fix.
|
||||
|
||||
Include an issue reference like `Closes #XXXX` or `Fixes #XXXX` in commits that
|
||||
close an issue. Including references automatically closes the issue on a merge.
|
||||
@@ -239,11 +241,15 @@ Please see the [Coding Style](#coding-style) for further guidelines.
|
||||
|
||||
### Merge approval
|
||||
|
||||
Moby maintainers use LGTM (Looks Good To Me) in comments on the code review to
|
||||
indicate acceptance, or use the Github review approval feature.
|
||||
Docker maintainers use LGTM (Looks Good To Me) in comments on the code review to
|
||||
indicate acceptance.
|
||||
|
||||
For an explanation of the review and approval process see the
|
||||
[REVIEWING](project/REVIEWING.md) page.
|
||||
A change requires LGTMs from an absolute majority of the maintainers of each
|
||||
component affected. For example, if a change affects `docs/` and `registry/`, it
|
||||
needs an absolute majority from the maintainers of `docs/` AND, separately, an
|
||||
absolute majority of the maintainers of `registry/`.
|
||||
|
||||
For more details, see the [MAINTAINERS](MAINTAINERS) page.
|
||||
|
||||
### Sign your work
|
||||
|
||||
@@ -257,9 +263,8 @@ Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
1 Letterman Drive
|
||||
Suite D4700
|
||||
San Francisco, CA, 94129
|
||||
660 York Street, Suite 102,
|
||||
San Francisco, CA 94110 USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
@@ -300,49 +305,35 @@ Use your real name (sorry, no pseudonyms or anonymous contributions.)
|
||||
If you set your `user.name` and `user.email` git configs, you can sign your
|
||||
commit automatically with `git commit -s`.
|
||||
|
||||
Note that the old-style `Docker-DCO-1.1-Signed-off-by: ...` format is still
|
||||
accepted, so there is no need to update outstanding pull requests to the new
|
||||
format right away, but please do adjust your processes for future contributions.
|
||||
|
||||
### How can I become a maintainer?
|
||||
|
||||
The procedures for adding new maintainers are explained in the
|
||||
[/project/GOVERNANCE.md](/project/GOVERNANCE.md)
|
||||
file in this repository.
|
||||
* Step 1: Learn the component inside out
|
||||
* Step 2: Make yourself useful by contributing code, bug fixes, support etc.
|
||||
* Step 3: Volunteer on the IRC channel (#docker at Freenode)
|
||||
* Step 4: Propose yourself at a scheduled docker meeting in #docker-dev
|
||||
|
||||
Don't forget: being a maintainer is a time investment. Make sure you
|
||||
will have time to make yourself available. You don't have to be a
|
||||
maintainer to make a difference on the project!
|
||||
|
||||
### Manage issues and pull requests using the Derek bot
|
||||
### IRC meetings
|
||||
|
||||
If you want to help label, assign, close or reopen issues or pull requests
|
||||
without commit rights, ask a maintainer to add your Github handle to the
|
||||
`.DEREK.yml` file. [Derek](https://github.com/alexellis/derek) is a bot that extends
|
||||
Github's user permissions to help non-committers to manage issues and pull requests simply by commenting.
|
||||
There are two monthly meetings taking place on #docker-dev IRC to accommodate all
|
||||
timezones. Anybody can propose a topic for discussion prior to the meeting.
|
||||
|
||||
For example:
|
||||
If you feel the conversation is going off-topic, feel free to point it out.
|
||||
|
||||
* Labels
|
||||
For the exact dates and times, have a look at [the irc-minutes
|
||||
repo](https://github.com/docker/irc-minutes). The minutes also contain all the
|
||||
notes from previous meetings.
|
||||
|
||||
```
|
||||
Derek add label: kind/question
|
||||
Derek remove label: status/claimed
|
||||
```
|
||||
## Docker community guidelines
|
||||
|
||||
* Assign work
|
||||
|
||||
```
|
||||
Derek assign: username
|
||||
Derek unassign: me
|
||||
```
|
||||
|
||||
* Manage issues and PRs
|
||||
|
||||
```
|
||||
Derek close
|
||||
Derek reopen
|
||||
```
|
||||
|
||||
## Moby community guidelines
|
||||
|
||||
We want to keep the Moby community awesome, growing and collaborative. We need
|
||||
We want to keep the Docker community awesome, growing and collaborative. We need
|
||||
your help to keep it that way. To help with this we've come up with some general
|
||||
guidelines for the community as a whole:
|
||||
|
||||
@@ -364,17 +355,6 @@ guidelines for the community as a whole:
|
||||
to an email you are potentially sending to a large number of people. Please
|
||||
consider this before you update. Also remember that nobody likes spam.
|
||||
|
||||
* Don't send email to the maintainers: There's no need to send email to the
|
||||
maintainers to ask them to investigate an issue or to take a look at a
|
||||
pull request. Instead of sending an email, GitHub mentions should be
|
||||
used to ping maintainers to review a pull request, a proposal or an
|
||||
issue.
|
||||
|
||||
The open source governance for this repository is handled via the [Moby Technical Steering Committee (TSC)](https://github.com/moby/tsc)
|
||||
charter. For any concerns with the community process regarding technical contributions,
|
||||
please contact the TSC. More information on project governance is available in
|
||||
our [project/GOVERNANCE.md](/project/GOVERNANCE.md) document.
|
||||
|
||||
### Guideline violations — 3 strikes method
|
||||
|
||||
The point of this section is not to find opportunities to punish people, but we
|
||||
@@ -436,7 +416,7 @@ The rules:
|
||||
5. Document _all_ declarations and methods, even private ones. Declare
|
||||
expectations, caveats and anything else that may be important. If a type
|
||||
gets exported, having the comments already there will ensure it's ready.
|
||||
6. Variable name length should be proportional to its context and no longer.
|
||||
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.
|
||||
@@ -444,7 +424,7 @@ The rules:
|
||||
and re-examine why you need a compound name. If you still think you need a
|
||||
compound name, lose the underscore.
|
||||
8. No utils or helpers packages. If a function is not general enough to
|
||||
warrant its own package, it has not been written generally enough to be a
|
||||
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
|
||||
@@ -453,6 +433,6 @@ The rules:
|
||||
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](https://golang.org/doc/effective_go.html). The
|
||||
[Go Blog](https://blog.golang.org) is also a great resource. Drinking the
|
||||
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.
|
||||
|
||||
617
Dockerfile
617
Dockerfile
@@ -1,408 +1,221 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG CROSS="false"
|
||||
ARG SYSTEMD="false"
|
||||
ARG GO_VERSION=1.20.10
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG VPNKIT_VERSION=0.5.0
|
||||
ARG DOCKER_BUILDTAGS="apparmor seccomp"
|
||||
|
||||
ARG BASE_DEBIAN_DISTRO="bullseye"
|
||||
ARG GOLANG_IMAGE="golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO}"
|
||||
|
||||
FROM ${GOLANG_IMAGE} AS base
|
||||
RUN echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
|
||||
ARG APT_MIRROR
|
||||
RUN test -n "$APT_MIRROR" && sed -ri "s/(httpredir|deb|security).debian.org/${APT_MIRROR}/g" /etc/apt/sources.list || true
|
||||
ENV GO111MODULE=off
|
||||
|
||||
FROM base AS criu
|
||||
ARG DEBIAN_FRONTEND
|
||||
# Install dependency packages specific to criu
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-criu-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-criu-aptcache,target=/var/cache/apt \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
libcap-dev \
|
||||
libnet-dev \
|
||||
libnl-3-dev \
|
||||
libprotobuf-c-dev \
|
||||
libprotobuf-dev \
|
||||
protobuf-c-compiler \
|
||||
protobuf-compiler \
|
||||
python3-protobuf
|
||||
|
||||
# Install CRIU for checkpoint/restore support
|
||||
ARG CRIU_VERSION=3.14
|
||||
RUN mkdir -p /usr/src/criu \
|
||||
&& curl -sSL https://github.com/checkpoint-restore/criu/archive/v${CRIU_VERSION}.tar.gz | tar -C /usr/src/criu/ -xz --strip-components=1 \
|
||||
&& cd /usr/src/criu \
|
||||
&& make \
|
||||
&& make PREFIX=/build/ install-criu
|
||||
|
||||
FROM base AS registry
|
||||
WORKDIR /go/src/github.com/docker/distribution
|
||||
# Install two versions of the registry. The first one is a recent version that
|
||||
# supports both schema 1 and 2 manifests. The second one is an older version that
|
||||
# only supports schema1 manifests. This allows integration-cli tests to cover
|
||||
# push/pull with both schema1 and schema2 manifests.
|
||||
# The old version of the registry is not working on arm64, so installation is
|
||||
# skipped on that architecture.
|
||||
ENV REGISTRY_COMMIT_SCHEMA1 ec87e9b6971d831f0eff752ddb54fb64693e51cd
|
||||
ENV REGISTRY_COMMIT 47a064d4195a9b56133891bbb13620c3ac83a827
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=tmpfs,target=/go/src/ \
|
||||
set -x \
|
||||
&& git clone https://github.com/docker/distribution.git . \
|
||||
&& git checkout -q "$REGISTRY_COMMIT" \
|
||||
&& GOPATH="/go/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
|
||||
go build -buildmode=pie -o /build/registry-v2 github.com/docker/distribution/cmd/registry \
|
||||
&& case $(dpkg --print-architecture) in \
|
||||
amd64|armhf|ppc64*|s390x) \
|
||||
git checkout -q "$REGISTRY_COMMIT_SCHEMA1"; \
|
||||
GOPATH="/go/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH"; \
|
||||
go build -buildmode=pie -o /build/registry-v2-schema1 github.com/docker/distribution/cmd/registry; \
|
||||
;; \
|
||||
esac
|
||||
|
||||
FROM base AS swagger
|
||||
WORKDIR $GOPATH/src/github.com/go-swagger/go-swagger
|
||||
# Install go-swagger for validating swagger.yaml
|
||||
# This is https://github.com/kolyshkin/go-swagger/tree/golang-1.13-fix
|
||||
# TODO: move to under moby/ or fix upstream go-swagger to work for us.
|
||||
ENV GO_SWAGGER_COMMIT c56166c036004ba7a3a321e5951ba472b9ae298c
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=tmpfs,target=/go/src/ \
|
||||
set -x \
|
||||
&& git clone https://github.com/kolyshkin/go-swagger.git . \
|
||||
&& git checkout -q "$GO_SWAGGER_COMMIT" \
|
||||
&& go build -o /build/swagger github.com/go-swagger/go-swagger/cmd/swagger
|
||||
|
||||
FROM debian:${BASE_DEBIAN_DISTRO} AS frozen-images
|
||||
ARG DEBIAN_FRONTEND
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-frozen-images-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-frozen-images-aptcache,target=/var/cache/apt \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
jq
|
||||
# Get useful and necessary Hub images so we can "docker load" locally instead of pulling
|
||||
COPY contrib/download-frozen-image-v2.sh /
|
||||
ARG TARGETARCH
|
||||
RUN /download-frozen-image-v2.sh /build \
|
||||
busybox:latest@sha256:95cf004f559831017cdf4628aaf1bb30133677be8702a8c5f2994629f637a209 \
|
||||
busybox:glibc@sha256:1f81263701cddf6402afe9f33fca0266d9fff379e59b1748f33d3072da71ee85 \
|
||||
debian:bullseye-slim@sha256:dacf278785a4daa9de07596ec739dbc07131e189942772210709c5c0777e8437 \
|
||||
hello-world:latest@sha256:d58e752213a51785838f9eed2b7a498ffa1cb3aa7f946dda11af39286c3db9a9 \
|
||||
arm32v7/hello-world:latest@sha256:50b8560ad574c779908da71f7ce370c0a2471c098d44d1c8f6b513c5a55eeeb1
|
||||
# See also frozenImages in "testutil/environment/protect.go" (which needs to be updated when adding images to this list)
|
||||
|
||||
FROM base AS cross-false
|
||||
|
||||
FROM --platform=linux/amd64 base AS cross-true
|
||||
ARG DEBIAN_FRONTEND
|
||||
RUN dpkg --add-architecture arm64
|
||||
RUN dpkg --add-architecture armel
|
||||
RUN dpkg --add-architecture armhf
|
||||
RUN dpkg --add-architecture ppc64el
|
||||
RUN dpkg --add-architecture s390x
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-cross-true-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-cross-true-aptcache,target=/var/cache/apt \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
crossbuild-essential-arm64 \
|
||||
crossbuild-essential-armel \
|
||||
crossbuild-essential-armhf \
|
||||
crossbuild-essential-ppc64el \
|
||||
crossbuild-essential-s390x
|
||||
|
||||
FROM cross-${CROSS} as dev-base
|
||||
|
||||
FROM dev-base AS runtime-dev-cross-false
|
||||
ARG DEBIAN_FRONTEND
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-cross-false-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-cross-false-aptcache,target=/var/cache/apt \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
binutils-mingw-w64 \
|
||||
g++-mingw-w64-x86-64 \
|
||||
libapparmor-dev \
|
||||
libbtrfs-dev \
|
||||
libdevmapper-dev \
|
||||
libseccomp-dev \
|
||||
libsystemd-dev \
|
||||
libudev-dev
|
||||
|
||||
FROM --platform=linux/amd64 runtime-dev-cross-false AS runtime-dev-cross-true
|
||||
ARG DEBIAN_FRONTEND
|
||||
# These crossbuild packages rely on gcc-<arch>, but this doesn't want to install
|
||||
# on non-amd64 systems, so other architectures cannot crossbuild amd64.
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-cross-true-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-cross-true-aptcache,target=/var/cache/apt \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
libapparmor-dev:arm64 \
|
||||
libapparmor-dev:armel \
|
||||
libapparmor-dev:armhf \
|
||||
libapparmor-dev:ppc64el \
|
||||
libapparmor-dev:s390x \
|
||||
libseccomp-dev:arm64 \
|
||||
libseccomp-dev:armel \
|
||||
libseccomp-dev:armhf \
|
||||
libseccomp-dev:ppc64el \
|
||||
libseccomp-dev:s390x
|
||||
|
||||
FROM runtime-dev-cross-${CROSS} AS runtime-dev
|
||||
|
||||
FROM base AS tomlv
|
||||
ARG TOMLV_COMMIT
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,src=hack/dockerfile/install,target=/tmp/install \
|
||||
PREFIX=/build /tmp/install/install.sh tomlv
|
||||
|
||||
FROM base AS vndr
|
||||
ARG VNDR_VERSION
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,src=hack/dockerfile/install,target=/tmp/install \
|
||||
PREFIX=/build /tmp/install/install.sh vndr
|
||||
|
||||
FROM dev-base AS containerd
|
||||
ARG DEBIAN_FRONTEND
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-containerd-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-containerd-aptcache,target=/var/cache/apt \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
libbtrfs-dev
|
||||
ARG CONTAINERD_VERSION
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,src=hack/dockerfile/install,target=/tmp/install \
|
||||
PREFIX=/build /tmp/install/install.sh containerd
|
||||
|
||||
FROM dev-base AS proxy
|
||||
ARG LIBNETWORK_COMMIT
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,src=hack/dockerfile/install,target=/tmp/install \
|
||||
PREFIX=/build /tmp/install/install.sh proxy
|
||||
|
||||
FROM base AS golangci_lint
|
||||
ARG GOLANGCI_LINT_VERSION
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,src=hack/dockerfile/install,target=/tmp/install \
|
||||
PREFIX=/build /tmp/install/install.sh golangci_lint
|
||||
|
||||
FROM base AS gotestsum
|
||||
ARG GOTESTSUM_VERSION
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,src=hack/dockerfile/install,target=/tmp/install \
|
||||
PREFIX=/build /tmp/install/install.sh gotestsum
|
||||
|
||||
FROM base AS shfmt
|
||||
ARG SHFMT_VERSION
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,src=hack/dockerfile/install,target=/tmp/install \
|
||||
PREFIX=/build /tmp/install/install.sh shfmt
|
||||
|
||||
FROM dev-base AS dockercli
|
||||
ARG DOCKERCLI_CHANNEL
|
||||
ARG DOCKERCLI_VERSION
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,src=hack/dockerfile/install,target=/tmp/install \
|
||||
PREFIX=/build /tmp/install/install.sh dockercli
|
||||
|
||||
FROM runtime-dev AS runc
|
||||
ARG RUNC_VERSION
|
||||
ARG RUNC_BUILDTAGS
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,src=hack/dockerfile/install,target=/tmp/install \
|
||||
PREFIX=/build /tmp/install/install.sh runc
|
||||
|
||||
FROM dev-base AS tini
|
||||
ARG DEBIAN_FRONTEND
|
||||
ARG TINI_VERSION
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-tini-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-tini-aptcache,target=/var/cache/apt \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
cmake \
|
||||
vim-common
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,src=hack/dockerfile/install,target=/tmp/install \
|
||||
PREFIX=/build /tmp/install/install.sh tini
|
||||
|
||||
FROM dev-base AS rootlesskit
|
||||
ARG ROOTLESSKIT_VERSION
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=bind,src=hack/dockerfile/install,target=/tmp/install \
|
||||
PREFIX=/build /tmp/install/install.sh rootlesskit
|
||||
COPY ./contrib/dockerd-rootless.sh /build
|
||||
COPY ./contrib/dockerd-rootless-setuptool.sh /build
|
||||
|
||||
FROM --platform=amd64 djs55/vpnkit:${VPNKIT_VERSION} AS vpnkit-amd64
|
||||
|
||||
FROM --platform=arm64 djs55/vpnkit:${VPNKIT_VERSION} AS vpnkit-arm64
|
||||
|
||||
FROM scratch AS vpnkit
|
||||
COPY --from=vpnkit-amd64 /vpnkit /build/vpnkit.x86_64
|
||||
COPY --from=vpnkit-arm64 /vpnkit /build/vpnkit.aarch64
|
||||
|
||||
# TODO: Some of this is only really needed for testing, it would be nice to split this up
|
||||
FROM runtime-dev AS dev-systemd-false
|
||||
ARG DEBIAN_FRONTEND
|
||||
RUN groupadd -r docker
|
||||
RUN useradd --create-home --gid docker unprivilegeduser \
|
||||
&& mkdir -p /home/unprivilegeduser/.local/share/docker \
|
||||
&& chown -R unprivilegeduser /home/unprivilegeduser
|
||||
# Let us use a .bashrc file
|
||||
RUN ln -sfv /go/src/github.com/docker/docker/.bashrc ~/.bashrc
|
||||
# Activate bash completion and include Docker's completion if mounted with DOCKER_BASH_COMPLETION_PATH
|
||||
RUN echo "source /usr/share/bash-completion/bash_completion" >> /etc/bash.bashrc
|
||||
RUN ln -s /usr/local/completion/bash/docker /etc/bash_completion.d/docker
|
||||
RUN ldconfig
|
||||
# Set dev environment as safe git directory to prevent "dubious ownership" errors
|
||||
# when bind-mounting the source into the dev-container. See https://github.com/moby/moby/pull/44930
|
||||
RUN git config --global --add safe.directory $GOPATH/src/github.com/docker/docker
|
||||
# This should only install packages that are specifically needed for the dev environment and nothing else
|
||||
# Do you really need to add another package here? Can it be done in a different build stage?
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-dev-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-dev-aptcache,target=/var/cache/apt \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
apparmor \
|
||||
bash-completion \
|
||||
bzip2 \
|
||||
inetutils-ping \
|
||||
iproute2 \
|
||||
iptables \
|
||||
jq \
|
||||
libcap2-bin \
|
||||
libnet1 \
|
||||
libnl-3-200 \
|
||||
libprotobuf-c1 \
|
||||
net-tools \
|
||||
patch \
|
||||
pigz \
|
||||
python3-pip \
|
||||
python3-setuptools \
|
||||
python3-wheel \
|
||||
sudo \
|
||||
thin-provisioning-tools \
|
||||
uidmap \
|
||||
vim \
|
||||
vim-common \
|
||||
xfsprogs \
|
||||
xz-utils \
|
||||
zip
|
||||
|
||||
|
||||
# Switch to use iptables instead of nftables (to match the CI hosts)
|
||||
# TODO use some kind of runtime auto-detection instead if/when nftables is supported (https://github.com/moby/moby/issues/26824)
|
||||
RUN update-alternatives --set iptables /usr/sbin/iptables-legacy || true \
|
||||
&& update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy || true \
|
||||
&& update-alternatives --set arptables /usr/sbin/arptables-legacy || true
|
||||
|
||||
RUN pip3 install yamllint==1.26.1
|
||||
|
||||
COPY --from=dockercli /build/ /usr/local/cli
|
||||
COPY --from=frozen-images /build/ /docker-frozen-images
|
||||
COPY --from=swagger /build/ /usr/local/bin/
|
||||
COPY --from=tomlv /build/ /usr/local/bin/
|
||||
COPY --from=tini /build/ /usr/local/bin/
|
||||
COPY --from=registry /build/ /usr/local/bin/
|
||||
|
||||
# Skip the CRIU stage for now, as the opensuse package repository is sometimes
|
||||
# unstable, and we're currently not using it in CI.
|
||||
# This file describes the standard way to build Docker, using docker
|
||||
#
|
||||
# FIXME(thaJeztah): re-enable this stage when https://github.com/moby/moby/issues/38963 is resolved (see https://github.com/moby/moby/pull/38984)
|
||||
# COPY --from=criu /build/ /usr/local/
|
||||
COPY --from=vndr /build/ /usr/local/bin/
|
||||
COPY --from=gotestsum /build/ /usr/local/bin/
|
||||
COPY --from=golangci_lint /build/ /usr/local/bin/
|
||||
COPY --from=shfmt /build/ /usr/local/bin/
|
||||
COPY --from=runc /build/ /usr/local/bin/
|
||||
COPY --from=containerd /build/ /usr/local/bin/
|
||||
COPY --from=rootlesskit /build/ /usr/local/bin/
|
||||
COPY --from=vpnkit /build/ /usr/local/bin/
|
||||
COPY --from=proxy /build/ /usr/local/bin/
|
||||
ENV PATH=/usr/local/cli:$PATH
|
||||
ARG DOCKER_BUILDTAGS
|
||||
ENV DOCKER_BUILDTAGS="${DOCKER_BUILDTAGS}"
|
||||
WORKDIR /go/src/github.com/docker/docker
|
||||
# Usage:
|
||||
#
|
||||
# # Assemble the full dev environment. This is slow the first time.
|
||||
# docker build -t docker .
|
||||
#
|
||||
# # Mount your source in an interactive container for quick testing:
|
||||
# docker run -v `pwd`:/go/src/github.com/docker/docker --privileged -i -t docker bash
|
||||
#
|
||||
# # Run the test suite:
|
||||
# docker run --privileged docker hack/make.sh test
|
||||
#
|
||||
# # Publish a release:
|
||||
# docker run --privileged \
|
||||
# -e AWS_S3_BUCKET=baz \
|
||||
# -e AWS_ACCESS_KEY=foo \
|
||||
# -e AWS_SECRET_KEY=bar \
|
||||
# -e GPG_PASSPHRASE=gloubiboulga \
|
||||
# docker hack/release.sh
|
||||
#
|
||||
# Note: AppArmor used to mess with privileged mode, but this is no longer
|
||||
# the case. Therefore, you don't have to disable it anymore.
|
||||
#
|
||||
|
||||
FROM ubuntu:14.04
|
||||
MAINTAINER Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
|
||||
RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys E871F18B51E0147C77796AC81196BA81F6B0FC61
|
||||
RUN echo deb http://ppa.launchpad.net/zfs-native/stable/ubuntu trusty main > /etc/apt/sources.list.d/zfs.list
|
||||
|
||||
# Packaged dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
apparmor \
|
||||
aufs-tools \
|
||||
automake \
|
||||
bash-completion \
|
||||
btrfs-tools \
|
||||
build-essential \
|
||||
createrepo \
|
||||
curl \
|
||||
dpkg-sig \
|
||||
gcc-mingw-w64 \
|
||||
git \
|
||||
iptables \
|
||||
libapparmor-dev \
|
||||
libcap-dev \
|
||||
libsqlite3-dev \
|
||||
libsystemd-journal-dev \
|
||||
mercurial \
|
||||
parallel \
|
||||
pkg-config \
|
||||
python-mock \
|
||||
python-pip \
|
||||
python-websocket \
|
||||
reprepro \
|
||||
ruby1.9.1 \
|
||||
ruby1.9.1-dev \
|
||||
s3cmd=1.1.0* \
|
||||
ubuntu-zfs \
|
||||
libzfs-dev \
|
||||
--no-install-recommends
|
||||
|
||||
# Get lvm2 source for compiling statically
|
||||
RUN git clone -b v2_02_103 https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2
|
||||
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
|
||||
|
||||
# Compile and install lvm2
|
||||
RUN cd /usr/local/lvm2 \
|
||||
&& ./configure --enable-static_link \
|
||||
&& make device-mapper \
|
||||
&& make install_device-mapper
|
||||
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
|
||||
|
||||
# Install lxc
|
||||
ENV LXC_VERSION 1.1.2
|
||||
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
|
||||
RUN cd /usr/src/lxc \
|
||||
&& ./configure \
|
||||
&& make \
|
||||
&& make install \
|
||||
&& ldconfig
|
||||
|
||||
# Install Go
|
||||
ENV GO_VERSION 1.4.3
|
||||
RUN curl -sSL https://golang.org/dl/go${GO_VERSION}.src.tar.gz | tar -v -C /usr/local -xz \
|
||||
&& mkdir -p /go/bin
|
||||
ENV PATH /go/bin:/usr/local/go/bin:$PATH
|
||||
ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
|
||||
RUN cd /usr/local/go/src && ./make.bash --no-clean 2>&1
|
||||
|
||||
# Compile Go for cross compilation
|
||||
ENV DOCKER_CROSSPLATFORMS \
|
||||
linux/386 linux/arm \
|
||||
darwin/amd64 darwin/386 \
|
||||
freebsd/amd64 freebsd/386 freebsd/arm \
|
||||
windows/amd64 windows/386
|
||||
|
||||
# (set an explicit GOARM of 5 for maximum compatibility)
|
||||
ENV GOARM 5
|
||||
RUN cd /usr/local/go/src \
|
||||
&& set -x \
|
||||
&& for platform in $DOCKER_CROSSPLATFORMS; do \
|
||||
GOOS=${platform%/*} \
|
||||
GOARCH=${platform##*/} \
|
||||
./make.bash --no-clean 2>&1; \
|
||||
done
|
||||
|
||||
# This has been commented out and kept as reference because we don't support compiling with older Go anymore.
|
||||
# ENV GOFMT_VERSION 1.3.3
|
||||
# RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C /go/bin -xz --strip-components=2 go/bin/gofmt
|
||||
|
||||
# Update this sha when we upgrade to go 1.5.0
|
||||
ENV GO_TOOLS_COMMIT 069d2f3bcb68257b627205f0486d6cc69a231ff9
|
||||
# Grab Go's cover tool for dead-simple code coverage testing
|
||||
# Grab Go's vet tool for examining go code to find suspicious constructs
|
||||
# and help prevent errors that the compiler might not catch
|
||||
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) \
|
||||
&& go install -v golang.org/x/tools/cmd/cover \
|
||||
&& 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
|
||||
RUN gem install --no-rdoc --no-ri fpm --version 1.3.2
|
||||
|
||||
# Install registry
|
||||
ENV REGISTRY_COMMIT ec87e9b6971d831f0eff752ddb54fb64693e51cd
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution" \
|
||||
&& (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT") \
|
||||
&& GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
|
||||
go build -o /usr/local/bin/registry-v2 github.com/docker/distribution/cmd/registry \
|
||||
&& 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
|
||||
ENV DOCKER_PY_COMMIT 47ab89ec2bd3bddf1221b856ffbaff333edeabb4
|
||||
RUN git clone https://github.com/docker/docker-py.git /docker-py \
|
||||
&& cd /docker-py \
|
||||
&& git checkout -q $DOCKER_PY_COMMIT \
|
||||
&& pip install -r test-requirements.txt
|
||||
|
||||
# Setup s3cmd config
|
||||
RUN { \
|
||||
echo '[default]'; \
|
||||
echo 'access_key=$AWS_ACCESS_KEY'; \
|
||||
echo 'secret_key=$AWS_SECRET_KEY'; \
|
||||
} > ~/.s3cfg
|
||||
|
||||
# Set user.email so crosbymichael's in-container merge commits go smoothly
|
||||
RUN git config --global user.email 'docker-dummy@example.com'
|
||||
|
||||
# Add an unprivileged user to be used for tests which need it
|
||||
RUN groupadd -r docker
|
||||
RUN useradd --create-home --gid docker unprivilegeduser
|
||||
|
||||
VOLUME /var/lib/docker
|
||||
VOLUME /home/unprivilegeduser/.local/share/docker
|
||||
WORKDIR /go/src/github.com/docker/docker
|
||||
ENV DOCKER_BUILDTAGS apparmor selinux
|
||||
|
||||
# Let us use a .bashrc file
|
||||
RUN ln -sfv $PWD/.bashrc ~/.bashrc
|
||||
|
||||
# Register Docker's bash completion.
|
||||
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
|
||||
COPY contrib/download-frozen-image.sh /go/src/github.com/docker/docker/contrib/
|
||||
RUN ./contrib/download-frozen-image.sh /docker-frozen-images \
|
||||
busybox:latest@8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55 \
|
||||
hello-world:frozen@91c95931e552b11604fea91c2f537284149ec32fff0f700a4769cfd31d7696ae \
|
||||
jess/unshare@5c9f6ea50341a2a8eb6677527f2bdedbf331ae894a41714fda770fb130f3314d
|
||||
# see also "hack/make/.ensure-frozen-images" (which needs to be updated any time this list is)
|
||||
|
||||
# Download man page generator
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& 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 "$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
|
||||
ENV TOMLV_COMMIT 9baf8a8a9f2ed20a8e54160840c492f937eeaf9a
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/BurntSushi/toml.git "$GOPATH/src/github.com/BurntSushi/toml" \
|
||||
&& (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
|
||||
ENV RSRC_COMMIT e48dbf1b7fc464a9e85fcec450dddf80816b76e0
|
||||
RUN set -x \
|
||||
&& git clone https://github.com/akavel/rsrc.git /go/src/github.com/akavel/rsrc \
|
||||
&& 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
|
||||
ENTRYPOINT ["hack/dind"]
|
||||
|
||||
FROM dev-systemd-false AS dev-systemd-true
|
||||
RUN --mount=type=cache,sharing=locked,id=moby-dev-aptlib,target=/var/lib/apt \
|
||||
--mount=type=cache,sharing=locked,id=moby-dev-aptcache,target=/var/cache/apt \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
dbus \
|
||||
dbus-user-session \
|
||||
systemd \
|
||||
systemd-sysv
|
||||
RUN mkdir -p hack \
|
||||
&& curl -o hack/dind-systemd https://raw.githubusercontent.com/AkihiroSuda/containerized-systemd/b70bac0daeea120456764248164c21684ade7d0d/docker-entrypoint.sh \
|
||||
&& chmod +x hack/dind-systemd
|
||||
ENTRYPOINT ["hack/dind-systemd"]
|
||||
|
||||
FROM dev-systemd-${SYSTEMD} AS dev
|
||||
|
||||
FROM runtime-dev AS binary-base
|
||||
ARG DOCKER_GITCOMMIT=HEAD
|
||||
ENV DOCKER_GITCOMMIT=${DOCKER_GITCOMMIT}
|
||||
ARG VERSION
|
||||
ENV VERSION=${VERSION}
|
||||
ARG PLATFORM
|
||||
ENV PLATFORM=${PLATFORM}
|
||||
ARG PRODUCT
|
||||
ENV PRODUCT=${PRODUCT}
|
||||
ARG DEFAULT_PRODUCT_LICENSE
|
||||
ENV DEFAULT_PRODUCT_LICENSE=${DEFAULT_PRODUCT_LICENSE}
|
||||
ARG DOCKER_BUILDTAGS
|
||||
ENV DOCKER_BUILDTAGS="${DOCKER_BUILDTAGS}"
|
||||
ENV PREFIX=/build
|
||||
# TODO: This is here because hack/make.sh binary copies these extras binaries
|
||||
# from $PATH into the bundles dir.
|
||||
# It would be nice to handle this in a different way.
|
||||
COPY --from=tini /build/ /usr/local/bin/
|
||||
COPY --from=runc /build/ /usr/local/bin/
|
||||
COPY --from=containerd /build/ /usr/local/bin/
|
||||
COPY --from=rootlesskit /build/ /usr/local/bin/
|
||||
COPY --from=proxy /build/ /usr/local/bin/
|
||||
COPY --from=vpnkit /build/ /usr/local/bin/
|
||||
WORKDIR /go/src/github.com/docker/docker
|
||||
|
||||
FROM binary-base AS build-binary
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=bind,target=/go/src/github.com/docker/docker \
|
||||
hack/make.sh binary
|
||||
|
||||
FROM binary-base AS build-dynbinary
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=bind,target=/go/src/github.com/docker/docker \
|
||||
hack/make.sh dynbinary
|
||||
|
||||
FROM binary-base AS build-cross
|
||||
ARG DOCKER_CROSSPLATFORMS
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=bind,target=/go/src/github.com/docker/docker \
|
||||
--mount=type=tmpfs,target=/go/src/github.com/docker/docker/autogen \
|
||||
hack/make.sh cross
|
||||
|
||||
FROM scratch AS binary
|
||||
COPY --from=build-binary /build/bundles/ /
|
||||
|
||||
FROM scratch AS dynbinary
|
||||
COPY --from=build-dynbinary /build/bundles/ /
|
||||
|
||||
FROM scratch AS cross
|
||||
COPY --from=build-cross /build/bundles/ /
|
||||
|
||||
FROM dev AS final
|
||||
# Upload docker source
|
||||
COPY . /go/src/github.com/docker/docker
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
ARG GO_VERSION=1.20.10
|
||||
|
||||
FROM golang:${GO_VERSION}-alpine AS base
|
||||
ENV GO111MODULE=off
|
||||
RUN apk --no-cache add \
|
||||
bash \
|
||||
btrfs-progs-dev \
|
||||
build-base \
|
||||
curl \
|
||||
lvm2-dev \
|
||||
jq
|
||||
|
||||
RUN mkdir -p /build/
|
||||
RUN mkdir -p /go/src/github.com/docker/docker/
|
||||
WORKDIR /go/src/github.com/docker/docker/
|
||||
|
||||
FROM base AS frozen-images
|
||||
# Get useful and necessary Hub images so we can "docker load" locally instead of pulling
|
||||
COPY contrib/download-frozen-image-v2.sh /
|
||||
RUN /download-frozen-image-v2.sh /build \
|
||||
busybox:latest@sha256:95cf004f559831017cdf4628aaf1bb30133677be8702a8c5f2994629f637a209 \
|
||||
busybox:latest@sha256:95cf004f559831017cdf4628aaf1bb30133677be8702a8c5f2994629f637a209 \
|
||||
busybox:glibc@sha256:1f81263701cddf6402afe9f33fca0266d9fff379e59b1748f33d3072da71ee85 \
|
||||
debian:bullseye-slim@sha256:dacf278785a4daa9de07596ec739dbc07131e189942772210709c5c0777e8437 \
|
||||
hello-world:latest@sha256:d58e752213a51785838f9eed2b7a498ffa1cb3aa7f946dda11af39286c3db9a9
|
||||
# See also frozenImages in "testutil/environment/protect.go" (which needs to be updated when adding images to this list)
|
||||
|
||||
FROM base AS dockercli
|
||||
ENV INSTALL_BINARY_NAME=dockercli
|
||||
COPY hack/dockerfile/install/install.sh ./install.sh
|
||||
COPY hack/dockerfile/install/$INSTALL_BINARY_NAME.installer ./
|
||||
RUN PREFIX=/build ./install.sh $INSTALL_BINARY_NAME
|
||||
|
||||
# Build DockerSuite.TestBuild* dependency
|
||||
FROM base AS contrib
|
||||
COPY contrib/syscall-test /build/syscall-test
|
||||
COPY contrib/httpserver/Dockerfile /build/httpserver/Dockerfile
|
||||
COPY contrib/httpserver contrib/httpserver
|
||||
RUN CGO_ENABLED=0 go build -buildmode=pie -o /build/httpserver/httpserver github.com/docker/docker/contrib/httpserver
|
||||
|
||||
# Build the integration tests and copy the resulting binaries to /build/tests
|
||||
FROM base AS builder
|
||||
|
||||
# Set tag and add sources
|
||||
COPY . .
|
||||
# Copy test sources tests that use assert can print errors
|
||||
RUN mkdir -p /build${PWD} && find integration integration-cli -name \*_test.go -exec cp --parents '{}' /build${PWD} \;
|
||||
# Build and install test binaries
|
||||
ARG DOCKER_GITCOMMIT=undefined
|
||||
RUN hack/make.sh build-integration-test-binary
|
||||
RUN mkdir -p /build/tests && find . -name test.main -exec cp --parents '{}' /build/tests \;
|
||||
|
||||
## Generate testing image
|
||||
FROM alpine:3.10 as runner
|
||||
|
||||
ENV DOCKER_REMOTE_DAEMON=1
|
||||
ENV DOCKER_INTEGRATION_DAEMON_DEST=/
|
||||
ENTRYPOINT ["/scripts/run.sh"]
|
||||
|
||||
# Add an unprivileged user to be used for tests which need it
|
||||
RUN addgroup docker && adduser -D -G docker unprivilegeduser -s /bin/ash
|
||||
|
||||
# GNU tar is used for generating the emptyfs image
|
||||
RUN apk --no-cache add \
|
||||
bash \
|
||||
ca-certificates \
|
||||
g++ \
|
||||
git \
|
||||
iptables \
|
||||
pigz \
|
||||
tar \
|
||||
xz
|
||||
|
||||
COPY hack/test/e2e-run.sh /scripts/run.sh
|
||||
COPY hack/make/.ensure-emptyfs /scripts/ensure-emptyfs.sh
|
||||
|
||||
COPY integration/testdata /tests/integration/testdata
|
||||
COPY integration/build/testdata /tests/integration/build/testdata
|
||||
COPY integration-cli/fixtures /tests/integration-cli/fixtures
|
||||
|
||||
COPY --from=frozen-images /build/ /docker-frozen-images
|
||||
COPY --from=dockercli /build/ /usr/bin/
|
||||
COPY --from=contrib /build/ /tests/contrib/
|
||||
COPY --from=builder /build/ /
|
||||
72
Dockerfile.gccgo
Normal file
72
Dockerfile.gccgo
Normal file
@@ -0,0 +1,72 @@
|
||||
# This file describes the standard way to build Docker, using docker
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# # Assemble the full dev environment. This is slow the first time.
|
||||
# docker build -t docker -f Dockerfile.gccgo .
|
||||
#
|
||||
|
||||
FROM gcc:5.2
|
||||
|
||||
# Packaged dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
apparmor \
|
||||
aufs-tools \
|
||||
btrfs-tools \
|
||||
build-essential \
|
||||
curl \
|
||||
git \
|
||||
iptables \
|
||||
net-tools \
|
||||
libapparmor-dev \
|
||||
libcap-dev \
|
||||
libsqlite3-dev \
|
||||
mercurial \
|
||||
parallel \
|
||||
python-mock \
|
||||
python-pip \
|
||||
python-websocket \
|
||||
--no-install-recommends
|
||||
|
||||
# Get lvm2 source for compiling statically
|
||||
RUN git clone -b v2_02_103 https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2
|
||||
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
|
||||
|
||||
# Compile and install lvm2
|
||||
RUN cd /usr/local/lvm2 \
|
||||
&& ./configure --enable-static_link \
|
||||
&& make device-mapper \
|
||||
&& make install_device-mapper
|
||||
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
|
||||
|
||||
# Install lxc
|
||||
ENV LXC_VERSION 1.1.2
|
||||
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
|
||||
RUN cd /usr/src/lxc \
|
||||
&& ./configure \
|
||||
&& make \
|
||||
&& make install \
|
||||
&& ldconfig
|
||||
|
||||
ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
|
||||
|
||||
# Get the "docker-py" source so we can run their integration tests
|
||||
ENV DOCKER_PY_COMMIT 139850f3f3b17357bab5ba3edfb745fb14043764
|
||||
RUN git clone https://github.com/docker/docker-py.git /docker-py \
|
||||
&& cd /docker-py \
|
||||
&& git checkout -q $DOCKER_PY_COMMIT
|
||||
|
||||
# Add an unprivileged user to be used for tests which need it
|
||||
RUN groupadd -r docker
|
||||
RUN useradd --create-home --gid docker unprivilegeduser
|
||||
|
||||
VOLUME /var/lib/docker
|
||||
WORKDIR /go/src/github.com/docker/docker
|
||||
ENV DOCKER_BUILDTAGS apparmor selinux
|
||||
|
||||
# Wrap all commands in the "docker-in-docker" script to allow nested containers
|
||||
ENTRYPOINT ["hack/dind"]
|
||||
|
||||
# Upload docker source
|
||||
COPY . /go/src/github.com/docker/docker
|
||||
@@ -1,53 +1,34 @@
|
||||
# docker build -t docker:simple -f Dockerfile.simple .
|
||||
# docker run --rm docker:simple hack/make.sh dynbinary
|
||||
# docker run --rm --privileged docker:simple hack/dind hack/make.sh test-unit
|
||||
# docker run --rm --privileged -v /var/lib/docker docker:simple hack/dind hack/make.sh dynbinary test-integration
|
||||
# docker run --rm --privileged -v /var/lib/docker docker:simple hack/dind hack/make.sh dynbinary test-integration-cli
|
||||
|
||||
# This represents the bare minimum required to build and test Docker.
|
||||
|
||||
ARG GO_VERSION=1.20.10
|
||||
FROM debian:jessie
|
||||
|
||||
FROM golang:${GO_VERSION}-buster
|
||||
ENV GO111MODULE=off
|
||||
|
||||
# allow replacing httpredir or deb mirror
|
||||
ARG APT_MIRROR=deb.debian.org
|
||||
RUN sed -ri "s/(httpredir|deb).debian.org/$APT_MIRROR/g" /etc/apt/sources.list
|
||||
|
||||
# Compile and runtime deps
|
||||
# compile and runtime deps
|
||||
# https://github.com/docker/docker/blob/master/project/PACKAGERS.md#build-dependencies
|
||||
# https://github.com/docker/docker/blob/master/project/PACKAGERS.md#runtime-dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
btrfs-tools \
|
||||
curl \
|
||||
cmake \
|
||||
gcc \
|
||||
git \
|
||||
libapparmor-dev \
|
||||
libbtrfs-dev \
|
||||
golang \
|
||||
libdevmapper-dev \
|
||||
libseccomp-dev \
|
||||
libsqlite3-dev \
|
||||
\
|
||||
ca-certificates \
|
||||
e2fsprogs \
|
||||
iptables \
|
||||
pkg-config \
|
||||
pigz \
|
||||
procps \
|
||||
xfsprogs \
|
||||
xz-utils \
|
||||
\
|
||||
aufs-tools \
|
||||
vim-common \
|
||||
lxc \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install runc, containerd, tini and docker-proxy
|
||||
# Please edit hack/dockerfile/install/<name>.installer to update them.
|
||||
COPY hack/dockerfile/install hack/dockerfile/install
|
||||
RUN for i in runc containerd tini proxy dockercli; \
|
||||
do hack/dockerfile/install/install.sh $i; \
|
||||
done
|
||||
ENV PATH=/usr/local/cli:$PATH
|
||||
|
||||
ENV AUTO_GOPATH 1
|
||||
WORKDIR /usr/src/docker
|
||||
COPY . /usr/src/docker
|
||||
|
||||
@@ -1,286 +0,0 @@
|
||||
# escape=`
|
||||
|
||||
# -----------------------------------------------------------------------------------------
|
||||
# This file describes the standard way to build Docker in a container on Windows
|
||||
# Server 2016 or Windows 10.
|
||||
#
|
||||
# Maintainer: @jhowardmsft
|
||||
# -----------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Prerequisites:
|
||||
# --------------
|
||||
#
|
||||
# 1. Windows Server 2016 or Windows 10 with all Windows updates applied. The major
|
||||
# build number must be at least 14393. This can be confirmed, for example, by
|
||||
# running the following from an elevated PowerShell prompt - this sample output
|
||||
# is from a fully up to date machine as at mid-November 2016:
|
||||
#
|
||||
# >> PS C:\> $(gin).WindowsBuildLabEx
|
||||
# >> 14393.447.amd64fre.rs1_release_inmarket.161102-0100
|
||||
#
|
||||
# 2. Git for Windows (or another git client) must be installed. https://git-scm.com/download/win.
|
||||
#
|
||||
# 3. The machine must be configured to run containers. For example, by following
|
||||
# the quick start guidance at https://msdn.microsoft.com/en-us/virtualization/windowscontainers/quick_start/quick_start or
|
||||
# https://github.com/docker/labs/blob/master/windows/windows-containers/Setup.md
|
||||
#
|
||||
# 4. If building in a Hyper-V VM: For Windows Server 2016 using Windows Server
|
||||
# containers as the default option, it is recommended you have at least 1GB
|
||||
# of memory assigned; For Windows 10 where Hyper-V Containers are employed, you
|
||||
# should have at least 4GB of memory assigned. Note also, to run Hyper-V
|
||||
# containers in a VM, it is necessary to configure the VM for nested virtualization.
|
||||
|
||||
# -----------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Usage:
|
||||
# -----
|
||||
#
|
||||
# The following steps should be run from an (elevated*) Windows PowerShell prompt.
|
||||
#
|
||||
# (*In a default installation of containers on Windows following the quick-start guidance at
|
||||
# https://msdn.microsoft.com/en-us/virtualization/windowscontainers/quick_start/quick_start,
|
||||
# the docker.exe client must run elevated to be able to connect to the daemon).
|
||||
#
|
||||
# 1. Clone the sources from github.com:
|
||||
#
|
||||
# >> git clone https://github.com/docker/docker.git C:\gopath\src\github.com\docker\docker
|
||||
# >> Cloning into 'C:\gopath\src\github.com\docker\docker'...
|
||||
# >> remote: Counting objects: 186216, done.
|
||||
# >> remote: Compressing objects: 100% (21/21), done.
|
||||
# >> remote: Total 186216 (delta 5), reused 0 (delta 0), pack-reused 186195
|
||||
# >> Receiving objects: 100% (186216/186216), 104.32 MiB | 8.18 MiB/s, done.
|
||||
# >> Resolving deltas: 100% (123139/123139), done.
|
||||
# >> Checking connectivity... done.
|
||||
# >> Checking out files: 100% (3912/3912), done.
|
||||
# >> PS C:\>
|
||||
#
|
||||
#
|
||||
# 2. Change directory to the cloned docker sources:
|
||||
#
|
||||
# >> cd C:\gopath\src\github.com\docker\docker
|
||||
#
|
||||
#
|
||||
# 3. Build a docker image with the components required to build the docker binaries from source
|
||||
# by running one of the following:
|
||||
#
|
||||
# >> docker build -t nativebuildimage -f Dockerfile.windows .
|
||||
# >> docker build -t nativebuildimage -f Dockerfile.windows -m 2GB . (if using Hyper-V containers)
|
||||
#
|
||||
#
|
||||
# 4. Build the docker executable binaries by running one of the following:
|
||||
#
|
||||
# >> $DOCKER_GITCOMMIT=(git rev-parse --short HEAD)
|
||||
# >> docker run --name binaries -e DOCKER_GITCOMMIT=$DOCKER_GITCOMMIT nativebuildimage hack\make.ps1 -Binary
|
||||
# >> docker run --name binaries -e DOCKER_GITCOMMIT=$DOCKER_GITCOMMIT -m 2GB nativebuildimage hack\make.ps1 -Binary (if using Hyper-V containers)
|
||||
#
|
||||
#
|
||||
# 5. Copy the binaries out of the container, replacing HostPath with an appropriate destination
|
||||
# folder on the host system where you want the binaries to be located.
|
||||
#
|
||||
# >> docker cp binaries:C:\gopath\src\github.com\docker\docker\bundles\docker.exe C:\HostPath\docker.exe
|
||||
# >> docker cp binaries:C:\gopath\src\github.com\docker\docker\bundles\dockerd.exe C:\HostPath\dockerd.exe
|
||||
#
|
||||
#
|
||||
# 6. (Optional) Remove the interim container holding the built executable binaries:
|
||||
#
|
||||
# >> docker rm binaries
|
||||
#
|
||||
#
|
||||
# 7. (Optional) Remove the image used for the container in which the executable
|
||||
# binaries are build. Tip - it may be useful to keep this image around if you need to
|
||||
# build multiple times. Then you can take advantage of the builder cache to have an
|
||||
# image which has all the components required to build the binaries already installed.
|
||||
#
|
||||
# >> docker rmi nativebuildimage
|
||||
#
|
||||
|
||||
# -----------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# The validation tests can only run directly on the host. This is because they calculate
|
||||
# information from the git repo, but the .git directory is not passed into the image as
|
||||
# it is excluded via .dockerignore. Run the following from a Windows PowerShell prompt
|
||||
# (elevation is not required): (Note Go must be installed to run these tests)
|
||||
#
|
||||
# >> hack\make.ps1 -DCO -PkgImports -GoFormat
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# To run unit tests, ensure you have created the nativebuildimage above. Then run one of
|
||||
# the following from an (elevated) Windows PowerShell prompt:
|
||||
#
|
||||
# >> docker run --rm nativebuildimage hack\make.ps1 -TestUnit
|
||||
# >> docker run --rm -m 2GB nativebuildimage hack\make.ps1 -TestUnit (if using Hyper-V containers)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# To run unit tests and binary build, ensure you have created the nativebuildimage above. Then
|
||||
# run one of the following from an (elevated) Windows PowerShell prompt:
|
||||
#
|
||||
# >> docker run nativebuildimage hack\make.ps1 -All
|
||||
# >> docker run -m 2GB nativebuildimage hack\make.ps1 -All (if using Hyper-V containers)
|
||||
|
||||
# -----------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Important notes:
|
||||
# ---------------
|
||||
#
|
||||
# Don't attempt to use a bind mount to pass a local directory as the bundles target
|
||||
# directory. It does not work (golang attempts for follow a mapped folder incorrectly).
|
||||
# Instead, use docker cp as per the example.
|
||||
#
|
||||
# go.zip is not removed from the image as it is used by the Windows CI servers
|
||||
# to ensure the host and image are running consistent versions of go.
|
||||
#
|
||||
# Nanoserver support is a work in progress. Although the image will build if the
|
||||
# FROM statement is updated, it will not work when running autogen through hack\make.ps1.
|
||||
# It is suspected that the required GCC utilities (eg gcc, windres, windmc) silently
|
||||
# quit due to the use of console hooks which are not available.
|
||||
#
|
||||
# The docker integration tests do not currently run in a container on Windows, predominantly
|
||||
# due to Windows not supporting privileged mode, so anything using a volume would fail.
|
||||
# They (along with the rest of the docker CI suite) can be run using
|
||||
# https://github.com/kevpar/docker-w2wCIScripts/blob/master/runCI/Invoke-DockerCI.ps1.
|
||||
#
|
||||
# -----------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
# The number of build steps below are explicitly minimised to improve performance.
|
||||
|
||||
# Extremely important - do not change the following line to reference a "specific" image,
|
||||
# such as `mcr.microsoft.com/windows/servercore:ltsc2019`. If using this Dockerfile in process
|
||||
# isolated containers, the kernel of the host must match the container image, and hence
|
||||
# would fail between Windows Server 2016 (aka RS1) and Windows Server 2019 (aka RS5).
|
||||
# It is expected that the image `microsoft/windowsservercore:latest` is present, and matches
|
||||
# the hosts kernel version before doing a build.
|
||||
FROM microsoft/windowsservercore
|
||||
|
||||
# Use PowerShell as the default shell
|
||||
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
||||
|
||||
ARG GO_VERSION=1.20.10
|
||||
ARG GOTESTSUM_VERSION=v1.8.2
|
||||
|
||||
# Environment variable notes:
|
||||
# - GO_VERSION must be consistent with 'Dockerfile' used by Linux.
|
||||
# - FROM_DOCKERFILE is used for detection of building within a container.
|
||||
ENV GO_VERSION=${GO_VERSION} `
|
||||
GIT_VERSION=2.11.1 `
|
||||
GOPATH=C:\gopath `
|
||||
GO111MODULE=off `
|
||||
FROM_DOCKERFILE=1 `
|
||||
GOTESTSUM_VERSION=${GOTESTSUM_VERSION}
|
||||
|
||||
RUN `
|
||||
Function Test-Nano() { `
|
||||
$EditionId = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'EditionID').EditionId; `
|
||||
return (($EditionId -eq 'ServerStandardNano') -or ($EditionId -eq 'ServerDataCenterNano') -or ($EditionId -eq 'NanoServer')); `
|
||||
}`
|
||||
`
|
||||
Function Download-File([string] $source, [string] $target) { `
|
||||
if (Test-Nano) { `
|
||||
$handler = New-Object System.Net.Http.HttpClientHandler; `
|
||||
$client = New-Object System.Net.Http.HttpClient($handler); `
|
||||
$client.Timeout = New-Object System.TimeSpan(0, 30, 0); `
|
||||
$cancelTokenSource = [System.Threading.CancellationTokenSource]::new(); `
|
||||
$responseMsg = $client.GetAsync([System.Uri]::new($source), $cancelTokenSource.Token); `
|
||||
$responseMsg.Wait(); `
|
||||
if (!$responseMsg.IsCanceled) { `
|
||||
$response = $responseMsg.Result; `
|
||||
if ($response.IsSuccessStatusCode) { `
|
||||
$downloadedFileStream = [System.IO.FileStream]::new($target, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write); `
|
||||
$copyStreamOp = $response.Content.CopyToAsync($downloadedFileStream); `
|
||||
$copyStreamOp.Wait(); `
|
||||
$downloadedFileStream.Close(); `
|
||||
if ($copyStreamOp.Exception -ne $null) { throw $copyStreamOp.Exception } `
|
||||
} `
|
||||
} else { `
|
||||
Throw ("Failed to download " + $source) `
|
||||
}`
|
||||
} else { `
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; `
|
||||
$webClient = New-Object System.Net.WebClient; `
|
||||
$webClient.DownloadFile($source, $target); `
|
||||
} `
|
||||
} `
|
||||
`
|
||||
setx /M PATH $('C:\git\cmd;C:\git\usr\bin;'+$Env:PATH+';C:\gcc\bin;C:\go\bin'); `
|
||||
`
|
||||
Write-Host INFO: Downloading git...; `
|
||||
$location='https://www.nuget.org/api/v2/package/GitForWindows/'+$Env:GIT_VERSION; `
|
||||
Download-File $location C:\gitsetup.zip; `
|
||||
`
|
||||
Write-Host INFO: Downloading go...; `
|
||||
$dlGoVersion=$Env:GO_VERSION -replace '\.0$',''; `
|
||||
Download-File "https://golang.org/dl/go${dlGoVersion}.windows-amd64.zip" C:\go.zip; `
|
||||
`
|
||||
Write-Host INFO: Downloading compiler 1 of 3...; `
|
||||
Download-File https://raw.githubusercontent.com/moby/docker-tdmgcc/master/gcc.zip C:\gcc.zip; `
|
||||
`
|
||||
Write-Host INFO: Downloading compiler 2 of 3...; `
|
||||
Download-File https://raw.githubusercontent.com/moby/docker-tdmgcc/master/runtime.zip C:\runtime.zip; `
|
||||
`
|
||||
Write-Host INFO: Downloading compiler 3 of 3...; `
|
||||
Download-File https://raw.githubusercontent.com/moby/docker-tdmgcc/master/binutils.zip C:\binutils.zip; `
|
||||
`
|
||||
Write-Host INFO: Extracting git...; `
|
||||
Expand-Archive C:\gitsetup.zip C:\git-tmp; `
|
||||
New-Item -Type Directory C:\git | Out-Null; `
|
||||
Move-Item C:\git-tmp\tools\* C:\git\.; `
|
||||
Remove-Item -Recurse -Force C:\git-tmp; `
|
||||
`
|
||||
Write-Host INFO: Expanding go...; `
|
||||
Expand-Archive C:\go.zip -DestinationPath C:\; `
|
||||
`
|
||||
Write-Host INFO: Expanding compiler 1 of 3...; `
|
||||
Expand-Archive C:\gcc.zip -DestinationPath C:\gcc -Force; `
|
||||
Write-Host INFO: Expanding compiler 2 of 3...; `
|
||||
Expand-Archive C:\runtime.zip -DestinationPath C:\gcc -Force; `
|
||||
Write-Host INFO: Expanding compiler 3 of 3...; `
|
||||
Expand-Archive C:\binutils.zip -DestinationPath C:\gcc -Force; `
|
||||
`
|
||||
Write-Host INFO: Removing downloaded files...; `
|
||||
Remove-Item C:\gcc.zip; `
|
||||
Remove-Item C:\runtime.zip; `
|
||||
Remove-Item C:\binutils.zip; `
|
||||
Remove-Item C:\gitsetup.zip; `
|
||||
`
|
||||
# Ensure all directories exist that we will require below....
|
||||
$srcDir = """$Env:GOPATH`\src\github.com\docker\docker\bundles"""; `
|
||||
Write-Host INFO: Ensuring existence of directory $srcDir...; `
|
||||
New-Item -Force -ItemType Directory -Path $srcDir | Out-Null; `
|
||||
`
|
||||
Write-Host INFO: Configuring git core.autocrlf...; `
|
||||
C:\git\cmd\git config --global core.autocrlf true;
|
||||
|
||||
RUN `
|
||||
Function Install-GoTestSum() { `
|
||||
$Env:GO111MODULE = 'on'; `
|
||||
$tmpGobin = "${Env:GOBIN_TMP}"; `
|
||||
$Env:GOBIN = """${Env:GOPATH}`\bin"""; `
|
||||
Write-Host "INFO: Installing gotestsum version $Env:GOTESTSUM_VERSION in $Env:GOBIN"; `
|
||||
&go install "gotest.tools/gotestsum@${Env:GOTESTSUM_VERSION}"; `
|
||||
$Env:GOBIN = "${tmpGobin}"; `
|
||||
$Env:GO111MODULE = 'off'; `
|
||||
if ($LASTEXITCODE -ne 0) { `
|
||||
Throw '"gotestsum install failed..."'; `
|
||||
} `
|
||||
} `
|
||||
`
|
||||
Install-GoTestSum
|
||||
|
||||
# Make PowerShell the default entrypoint
|
||||
ENTRYPOINT ["powershell.exe"]
|
||||
|
||||
# Set the working directory to the location of the sources
|
||||
WORKDIR ${GOPATH}\src\github.com\docker\docker
|
||||
|
||||
# Copy the sources into the container
|
||||
COPY . .
|
||||
713
Jenkinsfile
vendored
713
Jenkinsfile
vendored
@@ -1,713 +0,0 @@
|
||||
#!groovy
|
||||
pipeline {
|
||||
agent none
|
||||
|
||||
options {
|
||||
buildDiscarder(logRotator(daysToKeepStr: '30'))
|
||||
timeout(time: 2, unit: 'HOURS')
|
||||
timestamps()
|
||||
}
|
||||
parameters {
|
||||
booleanParam(name: 'unit_validate', defaultValue: true, description: 'amd64 (x86_64) unit tests and vendor check')
|
||||
booleanParam(name: 'validate_force', defaultValue: false, description: 'force validation steps to be run, even if no changes were detected')
|
||||
booleanParam(name: 'amd64', defaultValue: true, description: 'amd64 (x86_64) Build/Test')
|
||||
booleanParam(name: 'rootless', defaultValue: true, description: 'amd64 (x86_64) Build/Test (Rootless mode)')
|
||||
booleanParam(name: 'cgroup2', defaultValue: true, description: 'amd64 (x86_64) Build/Test (cgroup v2)')
|
||||
booleanParam(name: 'arm64', defaultValue: true, description: 'ARM (arm64) Build/Test')
|
||||
booleanParam(name: 'windowsRS5', defaultValue: true, description: 'Windows 2019 (RS5) Build/Test')
|
||||
booleanParam(name: 'dco', defaultValue: true, description: 'Run the DCO check')
|
||||
}
|
||||
environment {
|
||||
DOCKER_BUILDKIT = '1'
|
||||
DOCKER_EXPERIMENTAL = '1'
|
||||
DOCKER_GRAPHDRIVER = 'overlay2'
|
||||
CHECK_CONFIG_COMMIT = '78405559cfe5987174aa2cb6463b9b2c1b917255'
|
||||
TESTDEBUG = '0'
|
||||
TIMEOUT = '120m'
|
||||
}
|
||||
stages {
|
||||
stage('pr-hack') {
|
||||
when { changeRequest() }
|
||||
steps {
|
||||
script {
|
||||
echo "Workaround for PR auto-cancel feature. Borrowed from https://issues.jenkins-ci.org/browse/JENKINS-43353"
|
||||
def buildNumber = env.BUILD_NUMBER as int
|
||||
if (buildNumber > 1) milestone(buildNumber - 1)
|
||||
milestone(buildNumber)
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('DCO-check') {
|
||||
when {
|
||||
beforeAgent true
|
||||
expression { params.dco }
|
||||
}
|
||||
agent { label 'arm64 && ubuntu-2004' }
|
||||
steps {
|
||||
sh '''
|
||||
docker run --rm \
|
||||
-v "$WORKSPACE:/workspace" \
|
||||
-e VALIDATE_REPO=${GIT_URL} \
|
||||
-e VALIDATE_BRANCH=${CHANGE_TARGET} \
|
||||
alpine sh -c 'apk add --no-cache -q bash git openssh-client && git config --system --add safe.directory /workspace && cd /workspace && hack/validate/dco'
|
||||
'''
|
||||
}
|
||||
}
|
||||
stage('Build') {
|
||||
parallel {
|
||||
stage('unit-validate') {
|
||||
when {
|
||||
beforeAgent true
|
||||
expression { params.unit_validate }
|
||||
}
|
||||
agent { label 'amd64 && ubuntu-2004 && overlay2' }
|
||||
environment {
|
||||
// On master ("non-pull-request"), force running some validation checks (vendor, swagger),
|
||||
// even if no files were changed. This allows catching problems caused by pull-requests
|
||||
// that were merged out-of-sequence.
|
||||
TEST_FORCE_VALIDATE = sh returnStdout: true, script: 'if [ "${BRANCH_NAME%%-*}" != "PR" ] || [ "${CHANGE_TARGET:-master}" != "master" ] || [ "${validate_force}" = "true" ]; then echo "1"; fi'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage("Print info") {
|
||||
steps {
|
||||
sh 'docker version'
|
||||
sh 'docker info'
|
||||
sh '''
|
||||
echo "check-config.sh version: ${CHECK_CONFIG_COMMIT}"
|
||||
curl -fsSL -o ${WORKSPACE}/check-config.sh "https://raw.githubusercontent.com/moby/moby/${CHECK_CONFIG_COMMIT}/contrib/check-config.sh" \
|
||||
&& bash ${WORKSPACE}/check-config.sh || true
|
||||
'''
|
||||
}
|
||||
}
|
||||
stage("Build dev image") {
|
||||
steps {
|
||||
sh 'docker build --force-rm --build-arg APT_MIRROR -t docker:${GIT_COMMIT} .'
|
||||
}
|
||||
}
|
||||
stage("Validate") {
|
||||
steps {
|
||||
sh '''
|
||||
docker run --rm -t --privileged \
|
||||
-v "$WORKSPACE/bundles:/go/src/github.com/docker/docker/bundles" \
|
||||
-v "$WORKSPACE/.git:/go/src/github.com/docker/docker/.git" \
|
||||
--name docker-pr$BUILD_NUMBER \
|
||||
-e DOCKER_EXPERIMENTAL \
|
||||
-e DOCKER_GITCOMMIT=${GIT_COMMIT} \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
-e TEST_FORCE_VALIDATE \
|
||||
-e VALIDATE_REPO=${GIT_URL} \
|
||||
-e VALIDATE_BRANCH=${CHANGE_TARGET} \
|
||||
docker:${GIT_COMMIT} \
|
||||
hack/validate/default
|
||||
'''
|
||||
}
|
||||
}
|
||||
stage("Docker-py") {
|
||||
steps {
|
||||
sh '''
|
||||
docker run --rm -t --privileged \
|
||||
-v "$WORKSPACE/bundles:/go/src/github.com/docker/docker/bundles" \
|
||||
--name docker-pr$BUILD_NUMBER \
|
||||
-e DOCKER_EXPERIMENTAL \
|
||||
-e DOCKER_GITCOMMIT=${GIT_COMMIT} \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
-e VALIDATE_REPO=${GIT_URL} \
|
||||
-e VALIDATE_BRANCH=${CHANGE_TARGET} \
|
||||
docker:${GIT_COMMIT} \
|
||||
hack/make.sh \
|
||||
dynbinary-daemon \
|
||||
test-docker-py
|
||||
'''
|
||||
}
|
||||
post {
|
||||
always {
|
||||
junit testResults: 'bundles/test-docker-py/junit-report.xml', allowEmptyResults: true
|
||||
|
||||
sh '''
|
||||
echo "Ensuring container killed."
|
||||
docker rm -vf docker-pr$BUILD_NUMBER || true
|
||||
'''
|
||||
|
||||
sh '''
|
||||
echo 'Chowning /workspace to jenkins user'
|
||||
docker run --rm -v "$WORKSPACE:/workspace" busybox chown -R "$(id -u):$(id -g)" /workspace
|
||||
'''
|
||||
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE', message: 'Failed to create bundles.tar.gz') {
|
||||
sh '''
|
||||
bundleName=docker-py
|
||||
echo "Creating ${bundleName}-bundles.tar.gz"
|
||||
tar -czf ${bundleName}-bundles.tar.gz bundles/test-docker-py/*.xml bundles/test-docker-py/*.log
|
||||
'''
|
||||
|
||||
archiveArtifacts artifacts: '*-bundles.tar.gz', allowEmptyArchive: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage("Static") {
|
||||
steps {
|
||||
sh '''
|
||||
docker run --rm -t --privileged \
|
||||
-v "$WORKSPACE/bundles:/go/src/github.com/docker/docker/bundles" \
|
||||
--name docker-pr$BUILD_NUMBER \
|
||||
-e DOCKER_GITCOMMIT=${GIT_COMMIT} \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
docker:${GIT_COMMIT} \
|
||||
hack/make.sh binary-daemon
|
||||
'''
|
||||
}
|
||||
}
|
||||
stage("Cross") {
|
||||
steps {
|
||||
sh '''
|
||||
docker run --rm -t --privileged \
|
||||
-v "$WORKSPACE/bundles:/go/src/github.com/docker/docker/bundles" \
|
||||
--name docker-pr$BUILD_NUMBER \
|
||||
-e DOCKER_GITCOMMIT=${GIT_COMMIT} \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
docker:${GIT_COMMIT} \
|
||||
hack/make.sh cross
|
||||
'''
|
||||
}
|
||||
}
|
||||
// needs to be last stage that calls make.sh for the junit report to work
|
||||
stage("Unit tests") {
|
||||
steps {
|
||||
sh '''
|
||||
docker run --rm -t --privileged \
|
||||
-v "$WORKSPACE/bundles:/go/src/github.com/docker/docker/bundles" \
|
||||
--name docker-pr$BUILD_NUMBER \
|
||||
-e DOCKER_EXPERIMENTAL \
|
||||
-e DOCKER_GITCOMMIT=${GIT_COMMIT} \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
-e VALIDATE_REPO=${GIT_URL} \
|
||||
-e VALIDATE_BRANCH=${CHANGE_TARGET} \
|
||||
docker:${GIT_COMMIT} \
|
||||
hack/test/unit
|
||||
'''
|
||||
}
|
||||
post {
|
||||
always {
|
||||
junit testResults: 'bundles/junit-report.xml', allowEmptyResults: true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage("Validate vendor") {
|
||||
steps {
|
||||
sh '''
|
||||
docker run --rm -t --privileged \
|
||||
-v "$WORKSPACE/.git:/go/src/github.com/docker/docker/.git" \
|
||||
--name docker-pr$BUILD_NUMBER \
|
||||
-e DOCKER_EXPERIMENTAL \
|
||||
-e DOCKER_GITCOMMIT=${GIT_COMMIT} \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
-e TEST_FORCE_VALIDATE \
|
||||
-e VALIDATE_REPO=${GIT_URL} \
|
||||
-e VALIDATE_BRANCH=${CHANGE_TARGET} \
|
||||
docker:${GIT_COMMIT} \
|
||||
hack/validate/vendor
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
sh '''
|
||||
echo 'Ensuring container killed.'
|
||||
docker rm -vf docker-pr$BUILD_NUMBER || true
|
||||
'''
|
||||
|
||||
sh '''
|
||||
echo 'Chowning /workspace to jenkins user'
|
||||
docker run --rm -v "$WORKSPACE:/workspace" busybox chown -R "$(id -u):$(id -g)" /workspace
|
||||
'''
|
||||
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE', message: 'Failed to create bundles.tar.gz') {
|
||||
sh '''
|
||||
bundleName=unit
|
||||
echo "Creating ${bundleName}-bundles.tar.gz"
|
||||
tar -czvf ${bundleName}-bundles.tar.gz bundles/junit-report.xml bundles/go-test-report.json bundles/profile.out
|
||||
'''
|
||||
|
||||
archiveArtifacts artifacts: '*-bundles.tar.gz', allowEmptyArchive: true
|
||||
}
|
||||
}
|
||||
cleanup {
|
||||
sh 'make clean'
|
||||
deleteDir()
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('amd64') {
|
||||
when {
|
||||
beforeAgent true
|
||||
expression { params.amd64 }
|
||||
}
|
||||
agent { label 'amd64 && ubuntu-2004 && overlay2' }
|
||||
|
||||
stages {
|
||||
stage("Print info") {
|
||||
steps {
|
||||
sh 'docker version'
|
||||
sh 'docker info'
|
||||
sh '''
|
||||
echo "check-config.sh version: ${CHECK_CONFIG_COMMIT}"
|
||||
curl -fsSL -o ${WORKSPACE}/check-config.sh "https://raw.githubusercontent.com/moby/moby/${CHECK_CONFIG_COMMIT}/contrib/check-config.sh" \
|
||||
&& bash ${WORKSPACE}/check-config.sh || true
|
||||
'''
|
||||
}
|
||||
}
|
||||
stage("Build dev image") {
|
||||
steps {
|
||||
sh '''
|
||||
# todo: include ip_vs in base image
|
||||
sudo modprobe ip_vs
|
||||
|
||||
docker build --force-rm --build-arg APT_MIRROR -t docker:${GIT_COMMIT} .
|
||||
'''
|
||||
}
|
||||
}
|
||||
stage("Run tests") {
|
||||
steps {
|
||||
sh '''#!/bin/bash
|
||||
# bash is needed so 'jobs -p' works properly
|
||||
# it also accepts setting inline envvars for functions without explicitly exporting
|
||||
set -x
|
||||
|
||||
run_tests() {
|
||||
[ -n "$TESTDEBUG" ] && rm= || rm=--rm;
|
||||
docker run $rm -t --privileged \
|
||||
-v "$WORKSPACE/bundles/${TEST_INTEGRATION_DEST}:/go/src/github.com/docker/docker/bundles" \
|
||||
-v "$WORKSPACE/bundles/dynbinary-daemon:/go/src/github.com/docker/docker/bundles/dynbinary-daemon" \
|
||||
-v "$WORKSPACE/.git:/go/src/github.com/docker/docker/.git" \
|
||||
--name "$CONTAINER_NAME" \
|
||||
-e KEEPBUNDLE=1 \
|
||||
-e TESTDEBUG \
|
||||
-e TESTFLAGS \
|
||||
-e TEST_SKIP_INTEGRATION \
|
||||
-e TEST_SKIP_INTEGRATION_CLI \
|
||||
-e DOCKER_GITCOMMIT=${GIT_COMMIT} \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
-e TIMEOUT \
|
||||
-e VALIDATE_REPO=${GIT_URL} \
|
||||
-e VALIDATE_BRANCH=${CHANGE_TARGET} \
|
||||
docker:${GIT_COMMIT} \
|
||||
hack/make.sh \
|
||||
"$1" \
|
||||
test-integration
|
||||
}
|
||||
|
||||
trap "exit" INT TERM
|
||||
trap 'pids=$(jobs -p); echo "Remaining pids to kill: [$pids]"; [ -z "$pids" ] || kill $pids' EXIT
|
||||
|
||||
CONTAINER_NAME=docker-pr$BUILD_NUMBER
|
||||
|
||||
docker run --rm -t --privileged \
|
||||
-v "$WORKSPACE/bundles:/go/src/github.com/docker/docker/bundles" \
|
||||
-v "$WORKSPACE/.git:/go/src/github.com/docker/docker/.git" \
|
||||
--name ${CONTAINER_NAME}-build \
|
||||
-e DOCKER_EXPERIMENTAL \
|
||||
-e DOCKER_GITCOMMIT=${GIT_COMMIT} \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
docker:${GIT_COMMIT} \
|
||||
hack/make.sh \
|
||||
dynbinary-daemon
|
||||
|
||||
# flaky + integration
|
||||
TEST_INTEGRATION_DEST=1 CONTAINER_NAME=${CONTAINER_NAME}-1 TEST_SKIP_INTEGRATION_CLI=1 run_tests test-integration-flaky &
|
||||
|
||||
# integration-cli first set
|
||||
TEST_INTEGRATION_DEST=2 CONTAINER_NAME=${CONTAINER_NAME}-2 TEST_SKIP_INTEGRATION=1 TESTFLAGS="-test.run Test(DockerSuite|DockerNetworkSuite|DockerHubPullSuite|DockerRegistrySuite|DockerSchema1RegistrySuite|DockerRegistryAuthTokenSuite|DockerRegistryAuthHtpasswdSuite)/" run_tests &
|
||||
|
||||
# integration-cli second set
|
||||
TEST_INTEGRATION_DEST=3 CONTAINER_NAME=${CONTAINER_NAME}-3 TEST_SKIP_INTEGRATION=1 TESTFLAGS="-test.run Test(DockerSwarmSuite|DockerDaemonSuite|DockerExternalVolumeSuite)/" run_tests &
|
||||
|
||||
c=0
|
||||
for job in $(jobs -p); do
|
||||
wait ${job} || c=$?
|
||||
done
|
||||
exit $c
|
||||
'''
|
||||
}
|
||||
post {
|
||||
always {
|
||||
junit testResults: 'bundles/**/*-report.xml', allowEmptyResults: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
sh '''
|
||||
echo "Ensuring container killed."
|
||||
cids=$(docker ps -aq -f name=docker-pr${BUILD_NUMBER}-*)
|
||||
[ -n "$cids" ] && docker rm -vf $cids || true
|
||||
'''
|
||||
|
||||
sh '''
|
||||
echo "Chowning /workspace to jenkins user"
|
||||
docker run --rm -v "$WORKSPACE:/workspace" busybox chown -R "$(id -u):$(id -g)" /workspace
|
||||
'''
|
||||
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE', message: 'Failed to create bundles.tar.gz') {
|
||||
sh '''
|
||||
bundleName=amd64
|
||||
echo "Creating ${bundleName}-bundles.tar.gz"
|
||||
# exclude overlay2 directories
|
||||
find bundles -path '*/root/*overlay2' -prune -o -type f \\( -name '*-report.json' -o -name '*.log' -o -name '*.prof' -o -name '*-report.xml' \\) -print | xargs tar -czf ${bundleName}-bundles.tar.gz
|
||||
'''
|
||||
|
||||
archiveArtifacts artifacts: '*-bundles.tar.gz', allowEmptyArchive: true
|
||||
}
|
||||
}
|
||||
cleanup {
|
||||
sh 'make clean'
|
||||
deleteDir()
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('rootless') {
|
||||
when {
|
||||
beforeAgent true
|
||||
expression { params.rootless }
|
||||
}
|
||||
agent { label 'amd64 && ubuntu-2004 && overlay2' }
|
||||
stages {
|
||||
stage("Print info") {
|
||||
steps {
|
||||
sh 'docker version'
|
||||
sh 'docker info'
|
||||
sh '''
|
||||
echo "check-config.sh version: ${CHECK_CONFIG_COMMIT}"
|
||||
curl -fsSL -o ${WORKSPACE}/check-config.sh "https://raw.githubusercontent.com/moby/moby/${CHECK_CONFIG_COMMIT}/contrib/check-config.sh" \
|
||||
&& bash ${WORKSPACE}/check-config.sh || true
|
||||
'''
|
||||
}
|
||||
}
|
||||
stage("Build dev image") {
|
||||
steps {
|
||||
sh '''
|
||||
docker build --force-rm --build-arg APT_MIRROR -t docker:${GIT_COMMIT} .
|
||||
'''
|
||||
}
|
||||
}
|
||||
stage("Integration tests") {
|
||||
environment {
|
||||
DOCKER_ROOTLESS = '1'
|
||||
TEST_SKIP_INTEGRATION_CLI = '1'
|
||||
}
|
||||
steps {
|
||||
sh '''
|
||||
docker run --rm -t --privileged \
|
||||
-v "$WORKSPACE/bundles:/go/src/github.com/docker/docker/bundles" \
|
||||
--name docker-pr$BUILD_NUMBER \
|
||||
-e DOCKER_GITCOMMIT=${GIT_COMMIT} \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
-e DOCKER_EXPERIMENTAL \
|
||||
-e DOCKER_ROOTLESS \
|
||||
-e TEST_SKIP_INTEGRATION_CLI \
|
||||
-e TIMEOUT \
|
||||
-e VALIDATE_REPO=${GIT_URL} \
|
||||
-e VALIDATE_BRANCH=${CHANGE_TARGET} \
|
||||
docker:${GIT_COMMIT} \
|
||||
hack/make.sh \
|
||||
dynbinary \
|
||||
test-integration
|
||||
'''
|
||||
}
|
||||
post {
|
||||
always {
|
||||
junit testResults: 'bundles/**/*-report.xml', allowEmptyResults: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
sh '''
|
||||
echo "Ensuring container killed."
|
||||
docker rm -vf docker-pr$BUILD_NUMBER || true
|
||||
'''
|
||||
|
||||
sh '''
|
||||
echo "Chowning /workspace to jenkins user"
|
||||
docker run --rm -v "$WORKSPACE:/workspace" busybox chown -R "$(id -u):$(id -g)" /workspace
|
||||
'''
|
||||
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE', message: 'Failed to create bundles.tar.gz') {
|
||||
sh '''
|
||||
bundleName=amd64-rootless
|
||||
echo "Creating ${bundleName}-bundles.tar.gz"
|
||||
# exclude overlay2 directories
|
||||
find bundles -path '*/root/*overlay2' -prune -o -type f \\( -name '*-report.json' -o -name '*.log' -o -name '*.prof' -o -name '*-report.xml' \\) -print | xargs tar -czf ${bundleName}-bundles.tar.gz
|
||||
'''
|
||||
|
||||
archiveArtifacts artifacts: '*-bundles.tar.gz', allowEmptyArchive: true
|
||||
}
|
||||
}
|
||||
cleanup {
|
||||
sh 'make clean'
|
||||
deleteDir()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('cgroup2') {
|
||||
when {
|
||||
beforeAgent true
|
||||
expression { params.cgroup2 }
|
||||
}
|
||||
agent { label 'amd64 && ubuntu-2004 && cgroup2' }
|
||||
stages {
|
||||
stage("Print info") {
|
||||
steps {
|
||||
sh 'docker version'
|
||||
sh 'docker info'
|
||||
}
|
||||
}
|
||||
stage("Build dev image") {
|
||||
steps {
|
||||
sh '''
|
||||
docker build --force-rm --build-arg APT_MIRROR --build-arg SYSTEMD=true -t docker:${GIT_COMMIT} .
|
||||
'''
|
||||
}
|
||||
}
|
||||
stage("Integration tests") {
|
||||
environment {
|
||||
DOCKER_SYSTEMD = '1' // recommended cgroup driver for v2
|
||||
TEST_SKIP_INTEGRATION_CLI = '1' // CLI tests do not support v2
|
||||
}
|
||||
steps {
|
||||
sh '''
|
||||
docker run --rm -t --privileged \
|
||||
-v "$WORKSPACE/bundles:/go/src/github.com/docker/docker/bundles" \
|
||||
--name docker-pr$BUILD_NUMBER \
|
||||
-e DOCKER_GITCOMMIT=${GIT_COMMIT} \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
-e DOCKER_EXPERIMENTAL \
|
||||
-e DOCKER_SYSTEMD \
|
||||
-e TEST_SKIP_INTEGRATION_CLI \
|
||||
-e TIMEOUT \
|
||||
-e VALIDATE_REPO=${GIT_URL} \
|
||||
-e VALIDATE_BRANCH=${CHANGE_TARGET} \
|
||||
docker:${GIT_COMMIT} \
|
||||
hack/make.sh \
|
||||
dynbinary \
|
||||
test-integration
|
||||
'''
|
||||
}
|
||||
post {
|
||||
always {
|
||||
junit testResults: 'bundles/**/*-report.xml', allowEmptyResults: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
sh '''
|
||||
echo "Ensuring container killed."
|
||||
docker rm -vf docker-pr$BUILD_NUMBER || true
|
||||
'''
|
||||
|
||||
sh '''
|
||||
echo "Chowning /workspace to jenkins user"
|
||||
docker run --rm -v "$WORKSPACE:/workspace" busybox chown -R "$(id -u):$(id -g)" /workspace
|
||||
'''
|
||||
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE', message: 'Failed to create bundles.tar.gz') {
|
||||
sh '''
|
||||
bundleName=amd64-cgroup2
|
||||
echo "Creating ${bundleName}-bundles.tar.gz"
|
||||
# exclude overlay2 directories
|
||||
find bundles -path '*/root/*overlay2' -prune -o -type f \\( -name '*-report.json' -o -name '*.log' -o -name '*.prof' -o -name '*-report.xml' \\) -print | xargs tar -czf ${bundleName}-bundles.tar.gz
|
||||
'''
|
||||
|
||||
archiveArtifacts artifacts: '*-bundles.tar.gz', allowEmptyArchive: true
|
||||
}
|
||||
}
|
||||
cleanup {
|
||||
sh 'make clean'
|
||||
deleteDir()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('arm64') {
|
||||
when {
|
||||
beforeAgent true
|
||||
expression { params.arm64 }
|
||||
}
|
||||
agent { label 'arm64 && ubuntu-2004' }
|
||||
environment {
|
||||
TEST_SKIP_INTEGRATION_CLI = '1'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage("Print info") {
|
||||
steps {
|
||||
sh 'docker version'
|
||||
sh 'docker info'
|
||||
sh '''
|
||||
echo "check-config.sh version: ${CHECK_CONFIG_COMMIT}"
|
||||
curl -fsSL -o ${WORKSPACE}/check-config.sh "https://raw.githubusercontent.com/moby/moby/${CHECK_CONFIG_COMMIT}/contrib/check-config.sh" \
|
||||
&& bash ${WORKSPACE}/check-config.sh || true
|
||||
'''
|
||||
}
|
||||
}
|
||||
stage("Build dev image") {
|
||||
steps {
|
||||
sh 'docker build --force-rm -t docker:${GIT_COMMIT} .'
|
||||
}
|
||||
}
|
||||
stage("Unit tests") {
|
||||
steps {
|
||||
sh '''
|
||||
docker run --rm -t --privileged \
|
||||
-v "$WORKSPACE/bundles:/go/src/github.com/docker/docker/bundles" \
|
||||
--name docker-pr$BUILD_NUMBER \
|
||||
-e DOCKER_EXPERIMENTAL \
|
||||
-e DOCKER_GITCOMMIT=${GIT_COMMIT} \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
-e VALIDATE_REPO=${GIT_URL} \
|
||||
-e VALIDATE_BRANCH=${CHANGE_TARGET} \
|
||||
docker:${GIT_COMMIT} \
|
||||
hack/test/unit
|
||||
'''
|
||||
}
|
||||
post {
|
||||
always {
|
||||
junit testResults: 'bundles/junit-report.xml', allowEmptyResults: true
|
||||
}
|
||||
}
|
||||
}
|
||||
stage("Integration tests") {
|
||||
environment { TEST_SKIP_INTEGRATION_CLI = '1' }
|
||||
steps {
|
||||
sh '''
|
||||
docker run --rm -t --privileged \
|
||||
-v "$WORKSPACE/bundles:/go/src/github.com/docker/docker/bundles" \
|
||||
--name docker-pr$BUILD_NUMBER \
|
||||
-e DOCKER_EXPERIMENTAL \
|
||||
-e DOCKER_GITCOMMIT=${GIT_COMMIT} \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
-e TESTDEBUG \
|
||||
-e TEST_SKIP_INTEGRATION_CLI \
|
||||
-e TIMEOUT \
|
||||
-e VALIDATE_REPO=${GIT_URL} \
|
||||
-e VALIDATE_BRANCH=${CHANGE_TARGET} \
|
||||
docker:${GIT_COMMIT} \
|
||||
hack/make.sh \
|
||||
dynbinary \
|
||||
test-integration
|
||||
'''
|
||||
}
|
||||
post {
|
||||
always {
|
||||
junit testResults: 'bundles/**/*-report.xml', allowEmptyResults: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
sh '''
|
||||
echo "Ensuring container killed."
|
||||
docker rm -vf docker-pr$BUILD_NUMBER || true
|
||||
'''
|
||||
|
||||
sh '''
|
||||
echo "Chowning /workspace to jenkins user"
|
||||
docker run --rm -v "$WORKSPACE:/workspace" busybox chown -R "$(id -u):$(id -g)" /workspace
|
||||
'''
|
||||
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE', message: 'Failed to create bundles.tar.gz') {
|
||||
sh '''
|
||||
bundleName=arm64-integration
|
||||
echo "Creating ${bundleName}-bundles.tar.gz"
|
||||
# exclude overlay2 directories
|
||||
find bundles -path '*/root/*overlay2' -prune -o -type f \\( -name '*-report.json' -o -name '*.log' -o -name '*.prof' -o -name '*-report.xml' \\) -print | xargs tar -czf ${bundleName}-bundles.tar.gz
|
||||
'''
|
||||
|
||||
archiveArtifacts artifacts: '*-bundles.tar.gz', allowEmptyArchive: true
|
||||
}
|
||||
}
|
||||
cleanup {
|
||||
sh 'make clean'
|
||||
deleteDir()
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('win-RS5') {
|
||||
when {
|
||||
beforeAgent true
|
||||
expression { params.windowsRS5 }
|
||||
}
|
||||
environment {
|
||||
DOCKER_BUILDKIT = '0'
|
||||
DOCKER_DUT_DEBUG = '1'
|
||||
SKIP_VALIDATION_TESTS = '1'
|
||||
SOURCES_DRIVE = 'd'
|
||||
SOURCES_SUBDIR = 'gopath'
|
||||
TESTRUN_DRIVE = 'd'
|
||||
TESTRUN_SUBDIR = "CI"
|
||||
WINDOWS_BASE_IMAGE = 'mcr.microsoft.com/windows/servercore'
|
||||
WINDOWS_BASE_IMAGE_TAG = 'ltsc2019'
|
||||
}
|
||||
agent {
|
||||
node {
|
||||
customWorkspace 'd:\\gopath\\src\\github.com\\docker\\docker'
|
||||
label 'windows-2019'
|
||||
}
|
||||
}
|
||||
stages {
|
||||
stage("Print info") {
|
||||
steps {
|
||||
sh 'docker version'
|
||||
sh 'docker info'
|
||||
}
|
||||
}
|
||||
stage("Run tests") {
|
||||
steps {
|
||||
powershell '''
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Invoke-WebRequest https://github.com/moby/docker-ci-zap/blob/master/docker-ci-zap.exe?raw=true -OutFile C:/Windows/System32/docker-ci-zap.exe
|
||||
./hack/ci/windows.ps1
|
||||
exit $LastExitCode
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
junit testResults: 'bundles/junit-report-*.xml', allowEmptyResults: true
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE', message: 'Failed to create bundles.tar.gz') {
|
||||
powershell '''
|
||||
cd $env:WORKSPACE
|
||||
$bundleName="windowsRS5-integration"
|
||||
Write-Host -ForegroundColor Green "Creating ${bundleName}-bundles.zip"
|
||||
|
||||
# archiveArtifacts does not support env-vars to , so save the artifacts in a fixed location
|
||||
Compress-Archive -Path "bundles/CIDUT.out", "bundles/CIDUT.err", "bundles/junit-report-*.xml" -CompressionLevel Optimal -DestinationPath "${bundleName}-bundles.zip"
|
||||
'''
|
||||
|
||||
archiveArtifacts artifacts: '*-bundles.zip', allowEmptyArchive: true
|
||||
}
|
||||
}
|
||||
cleanup {
|
||||
sh 'make clean'
|
||||
deleteDir()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
LICENSE
2
LICENSE
@@ -176,7 +176,7 @@
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2013-2018 Docker, Inc.
|
||||
Copyright 2013-2015 Docker, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
763
MAINTAINERS
763
MAINTAINERS
@@ -1,18 +1,243 @@
|
||||
# Moby maintainers file
|
||||
# Docker maintainers file
|
||||
#
|
||||
# This file describes the maintainer groups within the moby/moby project.
|
||||
# More detail on Moby project governance is available in the
|
||||
# project/GOVERNANCE.md file found in this repository.
|
||||
# This file describes who runs the Docker project and how.
|
||||
# This is a living document - if you see something out of date or missing,
|
||||
# speak up!
|
||||
#
|
||||
# It is structured to be consumable by both humans and programs.
|
||||
# To extract its contents programmatically, use any TOML-compliant
|
||||
# parser.
|
||||
#
|
||||
# TODO(estesp): This file should not necessarily depend on docker/opensource
|
||||
# This file is compiled into the MAINTAINERS file in docker/opensource.
|
||||
#
|
||||
|
||||
[Rules]
|
||||
|
||||
[Rules.maintainers]
|
||||
|
||||
title = "What is a maintainer?"
|
||||
|
||||
text = """
|
||||
There are different types of maintainers, with different responsibilities, but
|
||||
all maintainers have 3 things in common:
|
||||
|
||||
1) They share responsibility in the project's success.
|
||||
2) They have made a long-term, recurring time investment to improve the project.
|
||||
3) They spend that time doing whatever needs to be done, not necessarily what
|
||||
is the most interesting or fun.
|
||||
|
||||
Maintainers are often under-appreciated, because their work is harder to appreciate.
|
||||
It's easy to appreciate a really cool and technically advanced feature. It's harder
|
||||
to appreciate the absence of bugs, the slow but steady improvement in stability,
|
||||
or the reliability of a release process. But those things distinguish a good
|
||||
project from a great one.
|
||||
"""
|
||||
|
||||
[Rules.bdfl]
|
||||
|
||||
title = "The Benevolent dictator for life (BDFL)"
|
||||
|
||||
text = """
|
||||
Docker follows the timeless, highly efficient and totally unfair system
|
||||
known as [Benevolent dictator for
|
||||
life](https://en.wikipedia.org/wiki/Benevolent_Dictator_for_Life), with
|
||||
yours truly, Solomon Hykes, in the role of BDFL. This means that all
|
||||
decisions are made, by default, by Solomon. Since making every decision
|
||||
myself would be highly un-scalable, in practice decisions are spread
|
||||
across multiple maintainers.
|
||||
|
||||
Ideally, the BDFL role is like the Queen of England: awesome crown, but not
|
||||
an actual operational role day-to-day. The real job of a BDFL is to NEVER GO AWAY.
|
||||
Every other rule can change, perhaps drastically so, but the BDFL will always
|
||||
be there, preserving the philosophy and principles of the project, and keeping
|
||||
ultimate authority over its fate. This gives us great flexibility in experimenting
|
||||
with various governance models, knowing that we can always press the "reset" button
|
||||
without fear of fragmentation or deadlock. See the US congress for a counter-example.
|
||||
|
||||
BDFL daily routine:
|
||||
|
||||
* Is the project governance stuck in a deadlock or irreversibly fragmented?
|
||||
* If yes: refactor the project governance
|
||||
* Are there issues or conflicts escalated by core?
|
||||
* If yes: resolve them
|
||||
* Go back to polishing that crown.
|
||||
"""
|
||||
|
||||
[Rules.decisions]
|
||||
|
||||
title = "How are decisions made?"
|
||||
|
||||
text = """
|
||||
Short answer: EVERYTHING IS A PULL REQUEST.
|
||||
|
||||
Docker is an open-source project with an open design philosophy. This
|
||||
means that the repository is the source of truth for EVERY aspect of the
|
||||
project, including its philosophy, design, road map, and APIs. *If it's
|
||||
part of the project, it's in the repo. If it's in the repo, it's part of
|
||||
the project.*
|
||||
|
||||
As a result, all decisions can be expressed as changes to the
|
||||
repository. An implementation change is a change to the source code. An
|
||||
API change is a change to the API specification. A philosophy change is
|
||||
a change to the philosophy manifesto, and so on.
|
||||
|
||||
All decisions affecting Docker, big and small, follow the same 3 steps:
|
||||
|
||||
* Step 1: Open a pull request. Anyone can do this.
|
||||
|
||||
* Step 2: Discuss the pull request. Anyone can do this.
|
||||
|
||||
* Step 3: Merge or refuse the pull request. Who does this depends on the nature
|
||||
of the pull request and which areas of the project it affects. See *review flow*
|
||||
for details.
|
||||
|
||||
Because Docker is such a large and active project, it's important for everyone to know
|
||||
who is responsible for deciding what. That is determined by a precise set of rules.
|
||||
|
||||
* For every *decision* in the project, the rules should designate, in a deterministic way,
|
||||
who should *decide*.
|
||||
|
||||
* For every *problem* in the project, the rules should designate, in a deterministic way,
|
||||
who should be responsible for *fixing* it.
|
||||
|
||||
* For every *question* in the project, the rules should designate, in a deterministic way,
|
||||
who should be expected to have the *answer*.
|
||||
"""
|
||||
|
||||
[Rules.review]
|
||||
|
||||
title = "Review flow"
|
||||
|
||||
text = """
|
||||
Pull requests should be processed according to the following flow:
|
||||
|
||||
* For each subsystem affected by the change, the maintainers of the subsystem must approve or refuse it.
|
||||
It is the responsibility of the subsystem maintainers to process patches affecting them in a timely
|
||||
manner.
|
||||
|
||||
* If the change affects areas of the code which are not part of a subsystem,
|
||||
or if subsystem maintainers are unable to reach a timely decision, it must be approved by
|
||||
the core maintainers.
|
||||
|
||||
* If the change affects the UI or public APIs, or if it represents a major change in architecture,
|
||||
the architects must approve or refuse it.
|
||||
|
||||
* If the change affects the operations of the project, it must be approved or rejected by
|
||||
the relevant operators.
|
||||
|
||||
* If the change affects the governance, philosophy, goals or principles of the project,
|
||||
it must be approved by BDFL.
|
||||
"""
|
||||
|
||||
[Rules.DCO]
|
||||
|
||||
title = "Helping contributors with the DCO"
|
||||
|
||||
text = """
|
||||
The [DCO or `Sign your work`](
|
||||
https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work)
|
||||
requirement is not intended as a roadblock or speed bump.
|
||||
|
||||
Some Docker contributors are not as familiar with `git`, or have used a web based
|
||||
editor, and thus asking them to `git commit --amend -s` is not the best way forward.
|
||||
|
||||
In this case, maintainers can update the commits based on clause (c) of the DCO. The
|
||||
most trivial way for a contributor to allow the maintainer to do this, is to add
|
||||
a DCO signature in a Pull Requests's comment, or a maintainer can simply note that
|
||||
the change is sufficiently trivial that it does not substantially change the existing
|
||||
contribution - i.e., a spelling change.
|
||||
|
||||
When you add someone's DCO, please also add your own to keep a log.
|
||||
"""
|
||||
|
||||
[Rules.holiday]
|
||||
|
||||
title = "I'm a maintainer, and I'm going on holiday"
|
||||
|
||||
text = """
|
||||
Please let your co-maintainers and other contributors know by raising a pull
|
||||
request that comments out your `MAINTAINERS` file entry using a `#`.
|
||||
"""
|
||||
|
||||
[Rules."no direct push"]
|
||||
|
||||
title = "I'm a maintainer. Should I make pull requests too?"
|
||||
|
||||
text = """
|
||||
Yes. Nobody should ever push to master directly. All changes should be
|
||||
made through a pull request.
|
||||
"""
|
||||
|
||||
[Rules.meta]
|
||||
|
||||
title = "How is this process changed?"
|
||||
|
||||
text = "Just like everything else: by making a pull request :)"
|
||||
|
||||
# Current project organization
|
||||
[Org]
|
||||
|
||||
bdfl = "shykes"
|
||||
|
||||
# The chief architect is responsible for the overall integrity of the technical architecture
|
||||
# across all subsystems, and the consistency of APIs and UI.
|
||||
#
|
||||
# Changes to UI, public APIs and overall architecture (for example a plugin system) must
|
||||
# be approved by the chief architect.
|
||||
"Chief Architect" = "shykes"
|
||||
|
||||
[Org.Operators]
|
||||
|
||||
# The operators make sure the trains run on time. They are responsible for overall operations
|
||||
# of the project. This includes facilitating communication between all the participants; helping
|
||||
# newcomers get involved and become successful contributors and maintainers; tracking the schedule
|
||||
# of releases; managing the relationship with downstream distributions and upstream dependencies;
|
||||
# define measures of success for the project and measure progress; Devise and implement tools and
|
||||
# processes which make contributors and maintainers happier and more efficient.
|
||||
|
||||
|
||||
[Org.Operators.security]
|
||||
|
||||
people = [
|
||||
"erw",
|
||||
"diogomonica",
|
||||
"nathanmccauley"
|
||||
]
|
||||
|
||||
[Org.Operators."monthly meetings"]
|
||||
|
||||
people = [
|
||||
"sven",
|
||||
"tianon"
|
||||
]
|
||||
|
||||
[Org.Operators.infrastructure]
|
||||
|
||||
people = [
|
||||
"jfrazelle",
|
||||
"crosbymichael"
|
||||
]
|
||||
|
||||
[Org.Operators.community]
|
||||
people = [
|
||||
"theadactyl"
|
||||
]
|
||||
|
||||
# The chief maintainer is responsible for all aspects of quality for the project including
|
||||
# code reviews, usability, stability, security, performance, etc.
|
||||
# The most important function of the chief maintainer is to lead by example. On the first
|
||||
# day of a new maintainer, the best advice should be "follow the C.M.'s example and you'll
|
||||
# be fine".
|
||||
"Chief Maintainer" = "crosbymichael"
|
||||
|
||||
# The community manager is responsible for serving the project community, including users,
|
||||
# contributors and partners. This involves:
|
||||
# - facilitating communication between maintainers, contributors and users
|
||||
# - organizing contributor and maintainer events
|
||||
# - helping new contributors get involved
|
||||
# - anything the project community needs to be successful
|
||||
#
|
||||
# The community manager is a point of contact for any contributor who has questions, concerns
|
||||
# or feedback about project operations.
|
||||
"Community Manager" = "theadactyl"
|
||||
|
||||
[Org."Core maintainers"]
|
||||
|
||||
# The Core maintainers are the ghostbusters of the project: when there's a problem others
|
||||
@@ -23,30 +248,151 @@
|
||||
# a subsystem, they are responsible for doing so and holding the
|
||||
# subsystem maintainers accountable. If ownership is unclear, they are the de facto owners.
|
||||
|
||||
# For each release (including minor releases), a "release captain" is assigned from the
|
||||
# pool of core maintainers. Rotation is encouraged across all maintainers, to ensure
|
||||
# the release process is clear and up-to-date.
|
||||
#
|
||||
# It is common for core maintainers to "branch out" to join or start a subsystem.
|
||||
|
||||
people = [
|
||||
"akihirosuda",
|
||||
"anusha",
|
||||
"coolljt0725",
|
||||
"cpuguy83",
|
||||
"calavera",
|
||||
"crosbymichael",
|
||||
"erikh",
|
||||
"estesp",
|
||||
"johnstep",
|
||||
"justincormack",
|
||||
"kolyshkin",
|
||||
"lowenna",
|
||||
"mhbauer",
|
||||
"icecrime",
|
||||
"jfrazelle",
|
||||
"lk4d4",
|
||||
"runcom",
|
||||
"stevvooe",
|
||||
"thajeztah",
|
||||
"tianon",
|
||||
"tibor",
|
||||
"tonistiigi",
|
||||
"unclejack",
|
||||
"vbatts",
|
||||
"vdemeester",
|
||||
"vieux",
|
||||
"yongtang"
|
||||
"vishh"
|
||||
]
|
||||
|
||||
[Org.Subsystems]
|
||||
|
||||
# As the project grows, it gets separated into well-defined subsystems. Each subsystem
|
||||
# has a dedicated group of maintainers, which are dedicated to that subsytem and responsible
|
||||
# for its quality.
|
||||
# This "cellular division" is the primary mechanism for scaling maintenance of the project as it grows.
|
||||
#
|
||||
# The maintainers of each subsytem are responsible for:
|
||||
#
|
||||
# 1. Exposing a clear road map for improving their subsystem.
|
||||
# 2. Deliver prompt feedback and decisions on pull requests affecting their subsystem.
|
||||
# 3. Be available to anyone with questions, bug reports, criticism etc.
|
||||
# on their component. This includes IRC, GitHub requests and the mailing
|
||||
# list.
|
||||
# 4. Make sure their subsystem respects the philosophy, design and
|
||||
# road map of the project.
|
||||
#
|
||||
# #### How to review patches to your subsystem
|
||||
#
|
||||
# Accepting pull requests:
|
||||
#
|
||||
# - If the pull request appears to be ready to merge, give it a `LGTM`, which
|
||||
# stands for "Looks Good To Me".
|
||||
# - If the pull request has some small problems that need to be changed, make
|
||||
# a comment adressing the issues.
|
||||
# - 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
|
||||
# forth.
|
||||
# - 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
|
||||
# problems before a fast track merge.
|
||||
#
|
||||
# Closing pull requests:
|
||||
#
|
||||
# - If a PR appears to be abandoned, after having attempted to contact the
|
||||
# original contributor, then a replacement PR may be made. Once the
|
||||
# replacement PR is made, any contributor may close the original one.
|
||||
# - If you are not sure if the pull request implements a good feature or you
|
||||
# do not understand the purpose of the PR, ask the contributor to provide
|
||||
# more documentation. If the contributor is not able to adequately explain
|
||||
# the purpose of the PR, the PR may be closed by any MAINTAINER.
|
||||
# - If a MAINTAINER feels that the pull request is sufficiently architecturally
|
||||
# flawed, or if the pull request needs significantly more design discussion
|
||||
# before being considered, the MAINTAINER should close the pull request with
|
||||
# a short explanation of what discussion still needs to be had. It is
|
||||
# important not to leave such pull requests open, as this will waste both the
|
||||
# MAINTAINER's time and the contributor's time. It is not good to string a
|
||||
# contributor on for weeks or months, having them make many changes to a PR
|
||||
# that will eventually be rejected.
|
||||
|
||||
[Org.Subsystems.Documentation]
|
||||
|
||||
people = [
|
||||
"james",
|
||||
"moxiegirl",
|
||||
"thaJeztah",
|
||||
"jamtur01",
|
||||
"sven"
|
||||
]
|
||||
|
||||
[Org.Subsystems.libcontainer]
|
||||
|
||||
people = [
|
||||
"crosbymichael",
|
||||
"jnagal",
|
||||
"lk4d4",
|
||||
"mpatel",
|
||||
"vmarmol"
|
||||
]
|
||||
|
||||
[Org.Subsystems.registry]
|
||||
|
||||
people = [
|
||||
"dmcg",
|
||||
"dmp42",
|
||||
"jlhawn",
|
||||
"samalba",
|
||||
"sday",
|
||||
"vbatts"
|
||||
]
|
||||
|
||||
[Org.Subsystems."build tools"]
|
||||
|
||||
people = [
|
||||
"shykes",
|
||||
"tianon"
|
||||
]
|
||||
|
||||
[Org.Subsystem."remote api"]
|
||||
|
||||
people = [
|
||||
"vieux"
|
||||
]
|
||||
|
||||
[Org.Subsystem.swarm]
|
||||
|
||||
people = [
|
||||
"aluzzardi",
|
||||
"vieux"
|
||||
]
|
||||
|
||||
[Org.Subsystem.machine]
|
||||
|
||||
people = [
|
||||
"bfirsh",
|
||||
"ehazlett"
|
||||
]
|
||||
|
||||
[Org.Subsystem.compose]
|
||||
|
||||
people = [
|
||||
"aanand"
|
||||
]
|
||||
|
||||
[Org.Subsystem.builder]
|
||||
|
||||
people = [
|
||||
"duglin",
|
||||
"erikh",
|
||||
"tibor"
|
||||
]
|
||||
|
||||
[Org.Curators]
|
||||
|
||||
# The curators help ensure that incoming issues and pull requests are properly triaged and
|
||||
@@ -59,186 +405,10 @@
|
||||
# - close an issue or pull request when it's an exact duplicate
|
||||
# - close an issue or pull request when it's inappropriate or off-topic
|
||||
|
||||
people = [
|
||||
"alexellis",
|
||||
"andrewhsu",
|
||||
"fntlnz",
|
||||
"gianarb",
|
||||
"olljanat",
|
||||
"programmerq",
|
||||
"ripcurld",
|
||||
"samwhited",
|
||||
"thajeztah"
|
||||
]
|
||||
people = [
|
||||
"thajeztah"
|
||||
]
|
||||
|
||||
[Org.Alumni]
|
||||
|
||||
# This list contains maintainers that are no longer active on the project.
|
||||
# It is thanks to these people that the project has become what it is today.
|
||||
# Thank you!
|
||||
|
||||
people = [
|
||||
# Aaron Lehmann was a maintainer for swarmkit, the registry, and the engine,
|
||||
# and contributed many improvements, features, and bugfixes in those areas,
|
||||
# among which "automated service rollbacks", templated secrets and configs,
|
||||
# and resumable image layer downloads.
|
||||
"aaronlehmann",
|
||||
|
||||
# Harald Albers is the mastermind behind the bash completion scripts for the
|
||||
# Docker CLI. The completion scripts moved to the Docker CLI repository, so
|
||||
# you can now find him perform his magic in the https://github.com/docker/cli repository.
|
||||
"albers",
|
||||
|
||||
# Andrea Luzzardi started contributing to the Docker codebase in the "dotCloud"
|
||||
# era, even before it was called "Docker". He is one of the architects of both
|
||||
# Swarm and SwarmKit, and its integration into the Docker engine.
|
||||
"aluzzardi",
|
||||
|
||||
# David Calavera contributed many features to Docker, such as an improved
|
||||
# event system, dynamic configuration reloading, volume plugins, fancy
|
||||
# new templating options, and an external client credential store. As a
|
||||
# maintainer, David was release captain for Docker 1.8, and competing
|
||||
# with Jess Frazelle to be "top dream killer".
|
||||
# David is now doing amazing stuff as CTO for https://www.netlify.com,
|
||||
# and tweets as @calavera.
|
||||
"calavera",
|
||||
|
||||
# Before becoming a maintainer, Daniel Nephin was a core contributor
|
||||
# to "Fig" (now known as Docker Compose). As a maintainer for both the
|
||||
# Engine and Docker CLI, Daniel contributed many features, among which
|
||||
# the `docker stack` commands, allowing users to deploy their Docker
|
||||
# Compose projects as a Swarm service.
|
||||
"dnephin",
|
||||
|
||||
# Doug Davis contributed many features and fixes for the classic builder,
|
||||
# such as "wildcard" copy, the dockerignore file, custom paths/names
|
||||
# for the Dockerfile, as well as enhancements to the API and documentation.
|
||||
# Follow Doug on Twitter, where he tweets as @duginabox.
|
||||
"duglin",
|
||||
|
||||
# As a maintainer, Erik was responsible for the "builder", and
|
||||
# started the first designs for the new networking model in
|
||||
# Docker. Erik is now working on all kinds of plugins for Docker
|
||||
# (https://github.com/contiv) and various open source projects
|
||||
# in his own repository https://github.com/erikh. You may
|
||||
# still stumble into him in our issue tracker, or on IRC.
|
||||
"erikh",
|
||||
|
||||
# Evan Hazlett is the creator of the Shipyard and Interlock open source projects,
|
||||
# and the author of "Orca", which became the foundation of Docker Universal Control
|
||||
# Plane (UCP). As a maintainer, Evan helped integrating SwarmKit (secrets, tasks)
|
||||
# into the Docker engine.
|
||||
"ehazlett",
|
||||
|
||||
# Arnaud Porterie (AKA "icecrime") was in charge of maintaining the maintainers.
|
||||
# As a maintainer, he made life easier for contributors to the Docker open-source
|
||||
# projects, bringing order in the chaos by designing a triage- and review workflow
|
||||
# using labels (see https://icecrime.net/technology/a-structured-approach-to-labeling/),
|
||||
# and automating the hell out of things with his buddies GordonTheTurtle and Poule
|
||||
# (a chicken!).
|
||||
#
|
||||
# A lesser-known fact is that he created the first commit in the libnetwork repository
|
||||
# even though he didn't know anything about it. Some say, he's now selling stuff on
|
||||
# the internet ;-)
|
||||
"icecrime",
|
||||
|
||||
# After a false start with his first PR being rejected, James Turnbull became a frequent
|
||||
# contributor to the documentation, and became a docs maintainer on December 5, 2013. As
|
||||
# a maintainer, James lifted the docs to a higher standard, and introduced the community
|
||||
# guidelines ("three strikes"). James is currently changing the world as CTO of https://www.empatico.org,
|
||||
# meanwhile authoring various books that are worth checking out. You can find him on Twitter,
|
||||
# rambling as @kartar, and although no longer active as a maintainer, he's always "game" to
|
||||
# help out reviewing docs PRs, so you may still see him around in the repository.
|
||||
"jamtur01",
|
||||
|
||||
# Jessica Frazelle, also known as the "Keyser Söze of containers",
|
||||
# runs *everything* in containers. She started contributing to
|
||||
# Docker with a (fun fun) change involving both iptables and regular
|
||||
# expressions (coz, YOLO!) on July 10, 2014
|
||||
# https://github.com/docker/docker/pull/6950/commits/f3a68ffa390fb851115c77783fa4031f1d3b2995.
|
||||
# Jess was Release Captain for Docker 1.4, 1.6 and 1.7, and contributed
|
||||
# many features and improvement, among which "seccomp profiles" (making
|
||||
# containers a lot more secure). Besides being a maintainer, she
|
||||
# set up the CI infrastructure for the project, giving everyone
|
||||
# something to shout at if a PR failed ("noooo Janky!").
|
||||
# Be sure you don't miss her talks at a conference near you (a must-see),
|
||||
# read her blog at https://blog.jessfraz.com (a must-read), and
|
||||
# check out her open source projects on GitHub https://github.com/jessfraz (a must-try).
|
||||
"jessfraz",
|
||||
|
||||
# Alexander Morozov contributed many features to Docker, worked on the premise of
|
||||
# what later became containerd (and worked on that too), and made a "stupid" Go
|
||||
# vendor tool specifically for docker/docker needs: vndr (https://github.com/LK4D4/vndr).
|
||||
# Not many know that Alexander is a master negotiator, being able to change course
|
||||
# of action with a single "Nope, we're not gonna do that".
|
||||
"lk4d4",
|
||||
|
||||
# Madhu Venugopal was part of the SocketPlane team that joined Docker.
|
||||
# As a maintainer, he was working with Jana for the Container Network
|
||||
# Model (CNM) implemented through libnetwork, and the "routing mesh" powering
|
||||
# Swarm mode networking.
|
||||
"mavenugo",
|
||||
|
||||
# As a maintainer, Kenfe-Mickaël Laventure worked on the container runtime,
|
||||
# integrating containerd 1.0 with the daemon, and adding support for custom
|
||||
# OCI runtimes, as well as implementing the `docker prune` subcommands,
|
||||
# which was a welcome feature to be added. You can keep up with Mickaél on
|
||||
# Twitter (@kmlaventure).
|
||||
"mlaventure",
|
||||
|
||||
# As a docs maintainer, Mary Anthony contributed greatly to the Docker
|
||||
# docs. She wrote the Docker Contributor Guide and Getting Started
|
||||
# Guides. She helped create a doc build system independent of
|
||||
# docker/docker project, and implemented a new docs.docker.com theme and
|
||||
# nav for 2015 Dockercon. Fun fact: the most inherited layer in DockerHub
|
||||
# public repositories was originally referenced in
|
||||
# maryatdocker/docker-whale back in May 2015.
|
||||
"moxiegirl",
|
||||
|
||||
# Jana Radhakrishnan was part of the SocketPlane team that joined Docker.
|
||||
# As a maintainer, he was the lead architect for the Container Network
|
||||
# Model (CNM) implemented through libnetwork, and the "routing mesh" powering
|
||||
# Swarm mode networking.
|
||||
#
|
||||
# Jana started new adventures in networking, but you can find him tweeting as @mrjana,
|
||||
# coding on GitHub https://github.com/mrjana, and he may be hiding on the Docker Community
|
||||
# slack channel :-)
|
||||
"mrjana",
|
||||
|
||||
# Sven Dowideit became a well known person in the Docker ecosphere, building
|
||||
# boot2docker, and became a regular contributor to the project, starting as
|
||||
# early as October 2013 (https://github.com/docker/docker/pull/2119), to become
|
||||
# a maintainer less than two months later (https://github.com/docker/docker/pull/3061).
|
||||
#
|
||||
# As a maintainer, Sven took on the task to convert the documentation from
|
||||
# ReStructuredText to Markdown, migrate to Hugo for generating the docs, and
|
||||
# writing tooling for building, testing, and publishing them.
|
||||
#
|
||||
# If you're not in the occasion to visit "the Australian office", you
|
||||
# can keep up with Sven on Twitter (@SvenDowideit), his blog http://fosiki.com,
|
||||
# and of course on GitHub.
|
||||
"sven",
|
||||
|
||||
# Vincent "vbatts!" Batts made his first contribution to the project
|
||||
# in November 2013, to become a maintainer a few months later, on
|
||||
# May 10, 2014 (https://github.com/docker/docker/commit/d6e666a87a01a5634c250358a94c814bf26cb778).
|
||||
# As a maintainer, Vincent made important contributions to core elements
|
||||
# of Docker, such as "distribution" (tarsum) and graphdrivers (btrfs, devicemapper).
|
||||
# He also contributed the "tar-split" library, an important element
|
||||
# for the content-addressable store.
|
||||
# Vincent is currently a member of the Open Containers Initiative
|
||||
# Technical Oversight Board (TOB), besides his work at Red Hat and
|
||||
# Project Atomic. You can still find him regularly hanging out in
|
||||
# our repository and the #docker-dev and #docker-maintainers IRC channels
|
||||
# for a chat, as he's always a lot of fun.
|
||||
"vbatts",
|
||||
|
||||
# Vishnu became a maintainer to help out on the daemon codebase and
|
||||
# libcontainer integration. He's currently involved in the
|
||||
# Open Containers Initiative, working on the specifications,
|
||||
# besides his work on cAdvisor and Kubernetes for Google.
|
||||
"vishh"
|
||||
]
|
||||
|
||||
[people]
|
||||
|
||||
@@ -248,71 +418,56 @@
|
||||
|
||||
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
|
||||
|
||||
[people.aaronlehmann]
|
||||
Name = "Aaron Lehmann"
|
||||
Email = "aaron.lehmann@docker.com"
|
||||
GitHub = "aaronlehmann"
|
||||
|
||||
[people.alexellis]
|
||||
Name = "Alex Ellis"
|
||||
Email = "alexellis2@gmail.com"
|
||||
GitHub = "alexellis"
|
||||
|
||||
[people.akihirosuda]
|
||||
Name = "Akihiro Suda"
|
||||
Email = "akihiro.suda.cz@hco.ntt.co.jp"
|
||||
GitHub = "AkihiroSuda"
|
||||
[people.aanand]
|
||||
Name = "Aanand Prasad"
|
||||
Email = "aanand@docker.com"
|
||||
GitHub = "aanand"
|
||||
|
||||
[people.aluzzardi]
|
||||
Name = "Andrea Luzzardi"
|
||||
Email = "al@docker.com"
|
||||
Email = "aluzzardi@docker.com"
|
||||
GitHub = "aluzzardi"
|
||||
|
||||
[people.albers]
|
||||
Name = "Harald Albers"
|
||||
Email = "github@albersweb.de"
|
||||
GitHub = "albers"
|
||||
|
||||
[people.andrewhsu]
|
||||
Name = "Andrew Hsu"
|
||||
Email = "andrewhsu@docker.com"
|
||||
GitHub = "andrewhsu"
|
||||
|
||||
[people.anusha]
|
||||
Name = "Anusha Ragunathan"
|
||||
Email = "anusha@docker.com"
|
||||
GitHub = "anusha-ragunathan"
|
||||
[people.bfirsh]
|
||||
Name = "Ben Firshman"
|
||||
Email = "ben@firshman.co.uk"
|
||||
GitHub = "bfirsh"
|
||||
|
||||
[people.calavera]
|
||||
Name = "David Calavera"
|
||||
Email = "david.calavera@gmail.com"
|
||||
GitHub = "calavera"
|
||||
|
||||
[people.coolljt0725]
|
||||
Name = "Lei Jitang"
|
||||
Email = "leijitang@huawei.com"
|
||||
GitHub = "coolljt0725"
|
||||
|
||||
[people.cpuguy83]
|
||||
Name = "Brian Goff"
|
||||
Email = "cpuguy83@gmail.com"
|
||||
GitHub = "cpuguy83"
|
||||
Github = "cpuguy83"
|
||||
|
||||
[people.crosbymichael]
|
||||
Name = "Michael Crosby"
|
||||
Email = "crosbymichael@gmail.com"
|
||||
GitHub = "crosbymichael"
|
||||
|
||||
[people.dnephin]
|
||||
Name = "Daniel Nephin"
|
||||
Email = "dnephin@gmail.com"
|
||||
GitHub = "dnephin"
|
||||
[people.diogomonica]
|
||||
Name = "Diogo Monica"
|
||||
Email = "diogo@docker.com"
|
||||
GitHub = "diogomonica"
|
||||
|
||||
[people.duglin]
|
||||
Name = "Doug Davis"
|
||||
Email = "dug@us.ibm.com"
|
||||
GitHub = "duglin"
|
||||
|
||||
[people.dmcg]
|
||||
Name = "Derek McGowan"
|
||||
Email = "derek@docker.com"
|
||||
Github = "dmcgowan"
|
||||
|
||||
[people.dmp42]
|
||||
Name = "Olivier Gambier"
|
||||
Email = "olivier@docker.com"
|
||||
Github = "dmp42"
|
||||
|
||||
[people.ehazlett]
|
||||
Name = "Evan Hazlett"
|
||||
Email = "ejhazlett@gmail.com"
|
||||
@@ -323,121 +478,71 @@
|
||||
Email = "erik@docker.com"
|
||||
GitHub = "erikh"
|
||||
|
||||
[people.erw]
|
||||
Name = "Eric Windisch"
|
||||
Email = "eric@windisch.us"
|
||||
GitHub = "ewindisch"
|
||||
|
||||
[people.estesp]
|
||||
Name = "Phil Estes"
|
||||
Email = "estesp@linux.vnet.ibm.com"
|
||||
GitHub = "estesp"
|
||||
|
||||
[people.fntlnz]
|
||||
Name = "Lorenzo Fontana"
|
||||
Email = "fontanalorenz@gmail.com"
|
||||
GitHub = "fntlnz"
|
||||
|
||||
[people.gianarb]
|
||||
Name = "Gianluca Arbezzano"
|
||||
Email = "ga@thumpflow.com"
|
||||
GitHub = "gianarb"
|
||||
|
||||
[people.icecrime]
|
||||
Name = "Arnaud Porterie"
|
||||
Email = "icecrime@gmail.com"
|
||||
Email = "arnaud@docker.com"
|
||||
GitHub = "icecrime"
|
||||
|
||||
[people.jamtur01]
|
||||
Name = "James Turnbull"
|
||||
Email = "james@lovedthanlost.net"
|
||||
GitHub = "jamtur01"
|
||||
|
||||
[people.jessfraz]
|
||||
[people.jfrazelle]
|
||||
Name = "Jessie Frazelle"
|
||||
Email = "jess@linux.com"
|
||||
GitHub = "jessfraz"
|
||||
Email = "j@docker.com"
|
||||
GitHub = "jfrazelle"
|
||||
|
||||
[people.johnstep]
|
||||
Name = "John Stephens"
|
||||
Email = "johnstep@docker.com"
|
||||
GitHub = "johnstep"
|
||||
[people.jlhawn]
|
||||
Name = "Josh Hawn"
|
||||
Email = "josh.hawn@docker.com"
|
||||
Github = "jlhawn"
|
||||
|
||||
[people.justincormack]
|
||||
Name = "Justin Cormack"
|
||||
Email = "justin.cormack@docker.com"
|
||||
GitHub = "justincormack"
|
||||
|
||||
[people.kolyshkin]
|
||||
Name = "Kir Kolyshkin"
|
||||
Email = "kolyshkin@gmail.com"
|
||||
GitHub = "kolyshkin"
|
||||
[people.jnagal]
|
||||
Name = "Rohit Jnagal"
|
||||
Email = "jnagal@google.com"
|
||||
GitHub = "rjnagal"
|
||||
|
||||
[people.lk4d4]
|
||||
Name = "Alexander Morozov"
|
||||
Email = "lk4d4@docker.com"
|
||||
GitHub = "lk4d4"
|
||||
|
||||
[people.lowenna]
|
||||
Name = "John Howard"
|
||||
Email = "github@lowenna.com"
|
||||
GitHub = "lowenna"
|
||||
|
||||
[people.mavenugo]
|
||||
Name = "Madhu Venugopal"
|
||||
Email = "madhu@docker.com"
|
||||
GitHub = "mavenugo"
|
||||
|
||||
[people.mhbauer]
|
||||
Name = "Morgan Bauer"
|
||||
Email = "mbauer@us.ibm.com"
|
||||
GitHub = "mhbauer"
|
||||
|
||||
[people.mlaventure]
|
||||
Name = "Kenfe-Mickaël Laventure"
|
||||
Email = "mickael.laventure@gmail.com"
|
||||
GitHub = "mlaventure"
|
||||
|
||||
[people.moxiegirl]
|
||||
Name = "Mary Anthony"
|
||||
Email = "mary.anthony@docker.com"
|
||||
GitHub = "moxiegirl"
|
||||
|
||||
[people.mrjana]
|
||||
Name = "Jana Radhakrishnan"
|
||||
Email = "mrjana@docker.com"
|
||||
GitHub = "mrjana"
|
||||
[people.mpatel]
|
||||
Name = "Mrunal Patel"
|
||||
Email = "mpatel@redhat.com"
|
||||
GitHub = "mrunalp"
|
||||
|
||||
[people.olljanat]
|
||||
Name = "Olli Janatuinen"
|
||||
Email = "olli.janatuinen@gmail.com"
|
||||
GitHub = "olljanat"
|
||||
|
||||
[people.programmerq]
|
||||
Name = "Jeff Anderson"
|
||||
Email = "jeff@docker.com"
|
||||
GitHub = "programmerq"
|
||||
|
||||
[people.ripcurld]
|
||||
Name = "Boaz Shuster"
|
||||
Email = "ripcurld.github@gmail.com"
|
||||
GitHub = "ripcurld"
|
||||
[people.nathanmccauley]
|
||||
Name = "Nathan McCauley"
|
||||
Email = "nathan.mccauley@docker.com"
|
||||
GitHub = "nathanmccauley"
|
||||
|
||||
[people.runcom]
|
||||
Name = "Antonio Murdaca"
|
||||
Email = "runcom@redhat.com"
|
||||
Email = "me@runcom.ninja"
|
||||
GitHub = "runcom"
|
||||
|
||||
[people.samwhited]
|
||||
Name = "Sam Whited"
|
||||
Email = "sam@samwhited.com"
|
||||
GitHub = "samwhited"
|
||||
[people.sday]
|
||||
Name = "Stephen Day"
|
||||
Email = "stephen.day@docker.com"
|
||||
Github = "stevvooe"
|
||||
|
||||
[people.shykes]
|
||||
Name = "Solomon Hykes"
|
||||
Email = "solomon@docker.com"
|
||||
GitHub = "shykes"
|
||||
|
||||
[people.stevvooe]
|
||||
Name = "Stephen Day"
|
||||
Email = "stephen.day@docker.com"
|
||||
GitHub = "stevvooe"
|
||||
|
||||
[people.sven]
|
||||
Name = "Sven Dowideit"
|
||||
Email = "SvenDowideit@home.org.au"
|
||||
@@ -448,6 +553,11 @@
|
||||
Email = "github@gone.nl"
|
||||
GitHub = "thaJeztah"
|
||||
|
||||
[people.theadactyl]
|
||||
Name = "Thea Lamkin"
|
||||
Email = "thea@docker.com"
|
||||
GitHub = "theadactyl"
|
||||
|
||||
[people.tianon]
|
||||
Name = "Tianon Gravi"
|
||||
Email = "admwiggin@gmail.com"
|
||||
@@ -458,11 +568,6 @@
|
||||
Email = "tibor@docker.com"
|
||||
GitHub = "tiborvass"
|
||||
|
||||
[people.tonistiigi]
|
||||
Name = "Tõnis Tiigi"
|
||||
Email = "tonis@docker.com"
|
||||
GitHub = "tonistiigi"
|
||||
|
||||
[people.unclejack]
|
||||
Name = "Cristian Staretu"
|
||||
Email = "cristian.staretu@gmail.com"
|
||||
@@ -488,7 +593,7 @@
|
||||
Email = "vishnuk@google.com"
|
||||
GitHub = "vishh"
|
||||
|
||||
[people.yongtang]
|
||||
Name = "Yong Tang"
|
||||
Email = "yong.tang.github@outlook.com"
|
||||
GitHub = "yongtang"
|
||||
[people.vmarmol]
|
||||
Name = "Victor Marmol"
|
||||
Email = "vmarmol@google.com"
|
||||
GitHub = "vmarmol"
|
||||
|
||||
288
Makefile
288
Makefile
@@ -1,277 +1,81 @@
|
||||
.PHONY: all binary dynbinary build cross help install manpages run shell test test-docker-py test-integration test-unit validate win
|
||||
|
||||
BUILDX_VERSION ?= v0.8.2
|
||||
|
||||
ifdef USE_BUILDX
|
||||
BUILDX ?= $(shell command -v buildx)
|
||||
BUILDX ?= $(shell command -v docker-buildx)
|
||||
DOCKER_BUILDX_CLI_PLUGIN_PATH ?= ~/.docker/cli-plugins/docker-buildx
|
||||
BUILDX ?= $(shell if [ -x "$(DOCKER_BUILDX_CLI_PLUGIN_PATH)" ]; then echo $(DOCKER_BUILDX_CLI_PLUGIN_PATH); fi)
|
||||
endif
|
||||
|
||||
ifndef USE_BUILDX
|
||||
DOCKER_BUILDKIT := 1
|
||||
export DOCKER_BUILDKIT
|
||||
endif
|
||||
|
||||
BUILDX ?= bundles/buildx
|
||||
DOCKER ?= docker
|
||||
|
||||
# set the graph driver as the current graphdriver if not set
|
||||
DOCKER_GRAPHDRIVER := $(if $(DOCKER_GRAPHDRIVER),$(DOCKER_GRAPHDRIVER),$(shell docker info 2>&1 | grep "Storage Driver" | sed 's/.*: //'))
|
||||
export DOCKER_GRAPHDRIVER
|
||||
|
||||
# get OS/Arch of docker engine
|
||||
DOCKER_OSARCH := $(shell bash -c 'source hack/make/.detect-daemon-osarch && echo $${DOCKER_ENGINE_OSARCH}')
|
||||
DOCKERFILE := $(shell bash -c 'source hack/make/.detect-daemon-osarch && echo $${DOCKERFILE}')
|
||||
|
||||
DOCKER_GITCOMMIT := $(shell git rev-parse --short HEAD || echo unsupported)
|
||||
export DOCKER_GITCOMMIT
|
||||
|
||||
# allow overriding the repository and branch that validation scripts are running
|
||||
# against these are used in hack/validate/.validate to check what changed in the PR.
|
||||
export VALIDATE_REPO
|
||||
export VALIDATE_BRANCH
|
||||
export VALIDATE_ORIGIN_BRANCH
|
||||
.PHONY: all binary build cross default docs docs-build docs-shell shell test test-unit test-integration-cli test-docker-py validate
|
||||
|
||||
# env vars passed through directly to Docker's build scripts
|
||||
# to allow things like `make KEEPBUNDLE=1 binary` easily
|
||||
# `project/PACKAGERS.md` have some limited documentation of some of these
|
||||
#
|
||||
# DOCKER_LDFLAGS can be used to pass additional parameters to -ldflags
|
||||
# option of "go build". For example, a built-in graphdriver priority list
|
||||
# can be changed during build time like this:
|
||||
#
|
||||
# make DOCKER_LDFLAGS="-X github.com/docker/docker/daemon/graphdriver.priority=overlay2,devicemapper" dynbinary
|
||||
#
|
||||
# to allow things like `make DOCKER_CLIENTONLY=1 binary` easily
|
||||
# `docs/sources/contributing/devenvironment.md ` and `project/PACKAGERS.md` have some limited documentation of some of these
|
||||
DOCKER_ENVS := \
|
||||
-e DOCKER_CROSSPLATFORMS \
|
||||
-e BUILDFLAGS \
|
||||
-e KEEPBUNDLE \
|
||||
-e DOCKER_BUILD_ARGS \
|
||||
-e DOCKER_BUILD_GOGC \
|
||||
-e DOCKER_BUILD_OPTS \
|
||||
-e DOCKER_BUILD_PKGS \
|
||||
-e DOCKER_BUILDKIT \
|
||||
-e DOCKER_BASH_COMPLETION_PATH \
|
||||
-e DOCKER_CLI_PATH \
|
||||
-e DOCKER_CLIENTONLY \
|
||||
-e DOCKER_DEBUG \
|
||||
-e DOCKER_EXECDRIVER \
|
||||
-e DOCKER_EXPERIMENTAL \
|
||||
-e DOCKER_GITCOMMIT \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
-e DOCKER_LDFLAGS \
|
||||
-e DOCKER_PORT \
|
||||
-e DOCKER_REMAP_ROOT \
|
||||
-e DOCKER_ROOTLESS \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
-e DOCKER_STORAGE_OPTS \
|
||||
-e DOCKER_TEST_HOST \
|
||||
-e DOCKER_USERLANDPROXY \
|
||||
-e DOCKERD_ARGS \
|
||||
-e TEST_FORCE_VALIDATE \
|
||||
-e TEST_INTEGRATION_DIR \
|
||||
-e TEST_SKIP_INTEGRATION \
|
||||
-e TEST_SKIP_INTEGRATION_CLI \
|
||||
-e TESTDEBUG \
|
||||
-e TESTDIRS \
|
||||
-e TESTFLAGS \
|
||||
-e TESTFLAGS_INTEGRATION \
|
||||
-e TESTFLAGS_INTEGRATION_CLI \
|
||||
-e TEST_FILTER \
|
||||
-e TIMEOUT \
|
||||
-e VALIDATE_REPO \
|
||||
-e VALIDATE_BRANCH \
|
||||
-e VALIDATE_ORIGIN_BRANCH \
|
||||
-e HTTP_PROXY \
|
||||
-e HTTPS_PROXY \
|
||||
-e NO_PROXY \
|
||||
-e http_proxy \
|
||||
-e https_proxy \
|
||||
-e no_proxy \
|
||||
-e VERSION \
|
||||
-e PLATFORM \
|
||||
-e DEFAULT_PRODUCT_LICENSE \
|
||||
-e PRODUCT
|
||||
-e TIMEOUT
|
||||
# note: we _cannot_ add "-e DOCKER_BUILDTAGS" here because even if it's unset in the shell, that would shadow the "ENV DOCKER_BUILDTAGS" set in our Dockerfile, which is very important for our official builds
|
||||
|
||||
# to allow `make BIND_DIR=. shell` or `make BIND_DIR= test`
|
||||
# (default to no bind mount if DOCKER_HOST is set)
|
||||
# note: BINDDIR is supported for backwards-compatibility here
|
||||
BIND_DIR := $(if $(BINDDIR),$(BINDDIR),$(if $(DOCKER_HOST),,bundles))
|
||||
|
||||
# DOCKER_MOUNT can be overriden, but use at your own risk!
|
||||
ifndef DOCKER_MOUNT
|
||||
DOCKER_MOUNT := $(if $(BIND_DIR),-v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/docker/docker/$(BIND_DIR)")
|
||||
DOCKER_MOUNT := $(if $(DOCKER_BINDDIR_MOUNT_OPTS),$(DOCKER_MOUNT):$(DOCKER_BINDDIR_MOUNT_OPTS),$(DOCKER_MOUNT))
|
||||
|
||||
# This allows the test suite to be able to run without worrying about the underlying fs used by the container running the daemon (e.g. aufs-on-aufs), so long as the host running the container is running a supported fs.
|
||||
# The volume will be cleaned up when the container is removed due to `--rm`.
|
||||
# Note that `BIND_DIR` will already be set to `bundles` if `DOCKER_HOST` is not set (see above BIND_DIR line), in such case this will do nothing since `DOCKER_MOUNT` will already be set.
|
||||
DOCKER_MOUNT := $(if $(DOCKER_MOUNT),$(DOCKER_MOUNT),-v /go/src/github.com/docker/docker/bundles) -v "$(CURDIR)/.git:/go/src/github.com/docker/docker/.git"
|
||||
|
||||
DOCKER_MOUNT_CACHE := -v docker-dev-cache:/root/.cache
|
||||
DOCKER_MOUNT_CLI := $(if $(DOCKER_CLI_PATH),-v $(shell dirname $(DOCKER_CLI_PATH)):/usr/local/cli,)
|
||||
DOCKER_MOUNT_BASH_COMPLETION := $(if $(DOCKER_BASH_COMPLETION_PATH),-v $(shell dirname $(DOCKER_BASH_COMPLETION_PATH)):/usr/local/completion/bash,)
|
||||
DOCKER_MOUNT := $(DOCKER_MOUNT) $(DOCKER_MOUNT_CACHE) $(DOCKER_MOUNT_CLI) $(DOCKER_MOUNT_BASH_COMPLETION)
|
||||
endif # ifndef DOCKER_MOUNT
|
||||
|
||||
# This allows to set the docker-dev container name
|
||||
DOCKER_CONTAINER_NAME := $(if $(CONTAINER_NAME),--name $(CONTAINER_NAME),)
|
||||
|
||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
||||
GIT_BRANCH_CLEAN := $(shell echo $(GIT_BRANCH) | sed -e "s/[^[:alnum:]]/-/g")
|
||||
DOCKER_IMAGE := docker-dev$(if $(GIT_BRANCH_CLEAN),:$(GIT_BRANCH_CLEAN))
|
||||
DOCKER_PORT_FORWARD := $(if $(DOCKER_PORT),-p "$(DOCKER_PORT)",)
|
||||
DOCKER_IMAGE := docker-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||
DOCKER_DOCS_IMAGE := docker-docs$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||
|
||||
DOCKER_FLAGS := $(DOCKER) run --rm -i --privileged $(DOCKER_CONTAINER_NAME) $(DOCKER_ENVS) $(DOCKER_MOUNT) $(DOCKER_PORT_FORWARD)
|
||||
DOCKER_RUN_DOCKER := docker run --rm -it --privileged $(DOCKER_ENVS) $(DOCKER_MOUNT) "$(DOCKER_IMAGE)"
|
||||
|
||||
SWAGGER_DOCS_PORT ?= 9000
|
||||
DOCKER_RUN_DOCS := docker run --rm -it $(DOCS_MOUNT) -e AWS_S3_BUCKET -e NOCACHE
|
||||
|
||||
define \n
|
||||
|
||||
|
||||
endef
|
||||
|
||||
# if this session isn't interactive, then we don't want to allocate a
|
||||
# TTY, which would fail, but if it is interactive, we do want to attach
|
||||
# so that the user can send e.g. ^C through.
|
||||
INTERACTIVE := $(shell [ -t 0 ] && echo 1 || echo 0)
|
||||
ifeq ($(INTERACTIVE), 1)
|
||||
DOCKER_FLAGS += -t
|
||||
endif
|
||||
|
||||
DOCKER_RUN_DOCKER := $(DOCKER_FLAGS) "$(DOCKER_IMAGE)"
|
||||
|
||||
DOCKER_BUILD_ARGS += --build-arg=GO_VERSION
|
||||
ifdef DOCKER_SYSTEMD
|
||||
DOCKER_BUILD_ARGS += --build-arg=SYSTEMD=true
|
||||
endif
|
||||
|
||||
BUILD_OPTS := ${DOCKER_BUILD_ARGS} ${DOCKER_BUILD_OPTS} -f "$(DOCKERFILE)"
|
||||
ifdef USE_BUILDX
|
||||
BUILD_OPTS += $(BUILDX_BUILD_EXTRA_OPTS)
|
||||
BUILD_CMD := $(BUILDX) build
|
||||
else
|
||||
BUILD_CMD := $(DOCKER) build
|
||||
endif
|
||||
|
||||
# This is used for the legacy "build" target and anything still depending on it
|
||||
BUILD_CROSS =
|
||||
ifdef DOCKER_CROSS
|
||||
BUILD_CROSS = --build-arg CROSS=$(DOCKER_CROSS)
|
||||
endif
|
||||
ifdef DOCKER_CROSSPLATFORMS
|
||||
BUILD_CROSS = --build-arg CROSS=true
|
||||
endif
|
||||
|
||||
VERSION_AUTOGEN_ARGS = --build-arg VERSION --build-arg DOCKER_GITCOMMIT --build-arg PRODUCT --build-arg PLATFORM --build-arg DEFAULT_PRODUCT_LICENSE
|
||||
# for some docs workarounds (see below in "docs-build" target)
|
||||
GITCOMMIT := $(shell git rev-parse --short HEAD 2>/dev/null)
|
||||
|
||||
default: binary
|
||||
|
||||
all: build ## validate all checks, build linux binaries, run all tests,\ncross build non-linux binaries, and generate archives
|
||||
$(DOCKER_RUN_DOCKER) bash -c 'hack/validate/default && hack/make.sh'
|
||||
all: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh
|
||||
|
||||
# This is only used to work around read-only bind mounts of the source code into
|
||||
# binary build targets. We end up mounting a tmpfs over autogen which allows us
|
||||
# to write build-time generated assets even though the source is mounted read-only
|
||||
# ...But in order to do so, this dir needs to already exist.
|
||||
autogen:
|
||||
mkdir -p autogen
|
||||
binary: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary
|
||||
|
||||
binary: buildx autogen ## build statically linked linux binaries
|
||||
$(BUILD_CMD) $(BUILD_OPTS) --output=bundles/ --target=$@ $(VERSION_AUTOGEN_ARGS) .
|
||||
cross: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross
|
||||
|
||||
dynbinary: buildx autogen ## build dynamically linked linux binaries
|
||||
$(BUILD_CMD) $(BUILD_OPTS) --output=bundles/ --target=$@ $(VERSION_AUTOGEN_ARGS) .
|
||||
deb: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary build-deb
|
||||
|
||||
cross: BUILD_OPTS += --build-arg CROSS=true --build-arg DOCKER_CROSSPLATFORMS
|
||||
cross: buildx autogen ## cross build the binaries for darwin, freebsd and\nwindows
|
||||
$(BUILD_CMD) $(BUILD_OPTS) --output=bundles/ --target=$@ $(VERSION_AUTOGEN_ARGS) .
|
||||
rpm: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary build-rpm
|
||||
|
||||
test: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross test-unit test-integration-cli test-docker-py
|
||||
|
||||
test-unit: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh test-unit
|
||||
|
||||
test-integration-cli: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary test-integration-cli
|
||||
|
||||
test-docker-py: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh binary test-docker-py
|
||||
|
||||
validate: build
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh validate-dco validate-gofmt validate-pkg validate-lint validate-test validate-toml validate-vet
|
||||
|
||||
shell: build
|
||||
$(DOCKER_RUN_DOCKER) bash
|
||||
|
||||
build: bundles
|
||||
docker build -t "$(DOCKER_IMAGE)" .
|
||||
|
||||
bundles:
|
||||
mkdir bundles
|
||||
|
||||
.PHONY: clean
|
||||
clean: clean-cache
|
||||
|
||||
.PHONY: clean-cache
|
||||
clean-cache:
|
||||
docker volume rm -f docker-dev-cache
|
||||
|
||||
help: ## this help
|
||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {gsub("\\\\n",sprintf("\n%22c",""), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
|
||||
install: ## install the linux binaries
|
||||
KEEPBUNDLE=1 hack/make.sh install-binary
|
||||
|
||||
run: build ## run the docker daemon in a container
|
||||
$(DOCKER_RUN_DOCKER) sh -c "KEEPBUNDLE=1 hack/make.sh install-binary run"
|
||||
|
||||
.PHONY: build
|
||||
ifeq ($(BIND_DIR), .)
|
||||
build: shell_target := --target=dev
|
||||
else
|
||||
build: shell_target := --target=final
|
||||
endif
|
||||
ifdef USE_BUILDX
|
||||
build: buildx_load := --load
|
||||
endif
|
||||
build: buildx
|
||||
$(BUILD_CMD) $(BUILD_OPTS) $(shell_target) $(buildx_load) $(BUILD_CROSS) -t "$(DOCKER_IMAGE)" .
|
||||
|
||||
shell: build ## start a shell inside the build env
|
||||
$(DOCKER_RUN_DOCKER) bash
|
||||
|
||||
test: build test-unit ## run the unit, integration and docker-py tests
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh dynbinary cross test-integration test-docker-py
|
||||
|
||||
test-docker-py: build ## run the docker-py tests
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh dynbinary test-docker-py
|
||||
|
||||
test-integration-cli: test-integration ## (DEPRECATED) use test-integration
|
||||
|
||||
ifneq ($(and $(TEST_SKIP_INTEGRATION),$(TEST_SKIP_INTEGRATION_CLI)),)
|
||||
test-integration:
|
||||
@echo Both integrations suites skipped per environment variables
|
||||
else
|
||||
test-integration: build ## run the integration tests
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh dynbinary test-integration
|
||||
endif
|
||||
|
||||
test-integration-flaky: build ## run the stress test for all new integration tests
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh dynbinary test-integration-flaky
|
||||
|
||||
test-unit: build ## run the unit tests
|
||||
$(DOCKER_RUN_DOCKER) hack/test/unit
|
||||
|
||||
validate: build ## validate DCO, Seccomp profile generation, gofmt,\n./pkg/ isolation, golint, tests, tomls, go vet and vendor
|
||||
$(DOCKER_RUN_DOCKER) hack/validate/all
|
||||
|
||||
win: build ## cross build the binary for windows
|
||||
$(DOCKER_RUN_DOCKER) DOCKER_CROSSPLATFORMS=windows/amd64 hack/make.sh cross
|
||||
|
||||
.PHONY: swagger-gen
|
||||
swagger-gen:
|
||||
docker run --rm -v $(PWD):/go/src/github.com/docker/docker \
|
||||
-w /go/src/github.com/docker/docker \
|
||||
--entrypoint hack/generate-swagger-api.sh \
|
||||
-e GOPATH=/go \
|
||||
quay.io/goswagger/swagger:0.7.4
|
||||
|
||||
.PHONY: swagger-docs
|
||||
swagger-docs: ## preview the API documentation
|
||||
@echo "API docs preview will be running at http://localhost:$(SWAGGER_DOCS_PORT)"
|
||||
@docker run --rm -v $(PWD)/api/swagger.yaml:/usr/share/nginx/html/swagger.yaml \
|
||||
-e 'REDOC_OPTIONS=hide-hostname="true" lazy-rendering' \
|
||||
-p $(SWAGGER_DOCS_PORT):80 \
|
||||
bfirsh/redoc:1.6.2
|
||||
|
||||
.PHONY: buildx
|
||||
ifdef USE_BUILDX
|
||||
ifeq ($(BUILDX), bundles/buildx)
|
||||
buildx: bundles/buildx ## build buildx cli tool
|
||||
endif
|
||||
endif
|
||||
|
||||
bundles/buildx: bundles ## build buildx CLI tool
|
||||
curl -fsSL https://raw.githubusercontent.com/moby/buildkit/70deac12b5857a1aa4da65e90b262368e2f71500/hack/install-buildx | VERSION="$(BUILDX_VERSION)" BINDIR="$(@D)" bash
|
||||
$@ version
|
||||
docs:
|
||||
$(MAKE) -C docs docs
|
||||
|
||||
4
NOTICE
4
NOTICE
@@ -1,9 +1,9 @@
|
||||
Docker
|
||||
Copyright 2012-2017 Docker, Inc.
|
||||
Copyright 2012-2015 Docker, Inc.
|
||||
|
||||
This product includes software developed at Docker, Inc. (https://www.docker.com).
|
||||
|
||||
This product contains software (https://github.com/creack/pty) developed
|
||||
This product contains software (https://github.com/kr/pty) developed
|
||||
by Keith Rarick, licensed under the MIT License.
|
||||
|
||||
The following is courtesy of our legal counsel:
|
||||
|
||||
305
README.md
305
README.md
@@ -1,48 +1,266 @@
|
||||
The Moby Project
|
||||
================
|
||||
Docker: the container engine [](https://github.com/docker/docker/releases/latest)
|
||||
============================
|
||||
|
||||

|
||||
Docker is an open source project to pack, ship and run any application
|
||||
as a lightweight container.
|
||||
|
||||
Moby is an open-source project created by Docker to enable and accelerate software containerization.
|
||||
Docker containers are both *hardware-agnostic* and *platform-agnostic*.
|
||||
This means they can run anywhere, from your laptop to the largest
|
||||
cloud compute instance and everything in between - and they don't require
|
||||
you to use a particular language, framework or packaging system. That
|
||||
makes them great building blocks for deploying and scaling web apps,
|
||||
databases, and backend services without depending on a particular stack
|
||||
or provider.
|
||||
|
||||
It provides a "Lego set" of toolkit components, the framework for assembling them into custom container-based systems, and a place for all container enthusiasts and professionals to experiment and exchange ideas.
|
||||
Components include container build tools, a container registry, orchestration tools, a runtime and more, and these can be used as building blocks in conjunction with other tools and projects.
|
||||
Docker began as an open-source implementation of the deployment engine which
|
||||
powers [dotCloud](https://www.dotcloud.com), a popular Platform-as-a-Service.
|
||||
It benefits directly from the experience accumulated over several years
|
||||
of large-scale operation and support of hundreds of thousands of
|
||||
applications and databases.
|
||||
|
||||
## Principles
|
||||

|
||||
|
||||
Moby is an open project guided by strong principles, aiming to be modular, flexible and without too strong an opinion on user experience.
|
||||
It is open to the community to help set its direction.
|
||||
## Security Disclosure
|
||||
|
||||
- Modular: the project includes lots of components that have well-defined functions and APIs that work together.
|
||||
- Batteries included but swappable: Moby includes enough components to build fully featured container system, but its modular architecture ensures that most of the components can be swapped by different implementations.
|
||||
- Usable security: Moby provides secure defaults without compromising usability.
|
||||
- Developer focused: The APIs are intended to be functional and useful to build powerful tools.
|
||||
They are not necessarily intended as end user tools but as components aimed at developers.
|
||||
Documentation and UX is aimed at developers not end users.
|
||||
Security is very important to us. If you have any issue regarding security,
|
||||
please disclose the information responsibly by sending an email to
|
||||
security@docker.com and not by creating a github issue.
|
||||
|
||||
## Audience
|
||||
## Better than VMs
|
||||
|
||||
The Moby Project is intended for engineers, integrators and enthusiasts looking to modify, hack, fix, experiment, invent and build systems based on containers.
|
||||
It is not for people looking for a commercially supported system, but for people who want to work and learn with open source code.
|
||||
A common method for distributing applications and sandboxing their
|
||||
execution is to use virtual machines, or VMs. Typical VM formats are
|
||||
VMware's vmdk, Oracle VirtualBox's vdi, and Amazon EC2's ami. In theory
|
||||
these formats should allow every developer to automatically package
|
||||
their application into a "machine" for easy distribution and deployment.
|
||||
In practice, that almost never happens, for a few reasons:
|
||||
|
||||
## Relationship with Docker
|
||||
* *Size*: VMs are very large which makes them impractical to store
|
||||
and transfer.
|
||||
* *Performance*: running VMs consumes significant CPU and memory,
|
||||
which makes them impractical in many scenarios, for example local
|
||||
development of multi-tier applications, and large-scale deployment
|
||||
of cpu and memory-intensive applications on large numbers of
|
||||
machines.
|
||||
* *Portability*: competing VM environments don't play well with each
|
||||
other. Although conversion tools do exist, they are limited and
|
||||
add even more overhead.
|
||||
* *Hardware-centric*: VMs were designed with machine operators in
|
||||
mind, not software developers. As a result, they offer very
|
||||
limited tooling for what developers need most: building, testing
|
||||
and running their software. For example, VMs offer no facilities
|
||||
for application versioning, monitoring, configuration, logging or
|
||||
service discovery.
|
||||
|
||||
The components and tools in the Moby Project are initially the open source components that Docker and the community have built for the Docker Project.
|
||||
New projects can be added if they fit with the community goals. Docker is committed to using Moby as the upstream for the Docker Product.
|
||||
However, other projects are also encouraged to use Moby as an upstream, and to reuse the components in diverse ways, and all these uses will be treated in the same way. External maintainers and contributors are welcomed.
|
||||
By contrast, Docker relies on a different sandboxing method known as
|
||||
*containerization*. Unlike traditional virtualization, containerization
|
||||
takes place at the kernel level. Most modern operating system kernels
|
||||
now support the primitives necessary for containerization, including
|
||||
Linux with [openvz](https://openvz.org),
|
||||
[vserver](http://linux-vserver.org) and more recently
|
||||
[lxc](https://linuxcontainers.org/), Solaris with
|
||||
[zones](https://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc),
|
||||
and FreeBSD with
|
||||
[Jails](https://www.freebsd.org/doc/handbook/jails.html).
|
||||
|
||||
The Moby project is not intended as a location for support or feature requests for Docker products, but as a place for contributors to work on open source code, fix bugs, and make the code more useful.
|
||||
The releases are supported by the maintainers, community and users, on a best efforts basis only, and are not intended for customers who want enterprise or commercial support; Docker EE is the appropriate product for these use cases.
|
||||
Docker builds on top of these low-level primitives to offer developers a
|
||||
portable format and runtime environment that solves all four problems.
|
||||
Docker containers are small (and their transfer can be optimized with
|
||||
layers), they have basically zero memory and cpu overhead, they are
|
||||
completely portable, and are designed from the ground up with an
|
||||
application-centric design.
|
||||
|
||||
-----
|
||||
Perhaps best of all, because Docker operates at the OS level, it can still be
|
||||
run inside a VM!
|
||||
|
||||
Legal
|
||||
=====
|
||||
## Plays well with others
|
||||
|
||||
Docker does not require you to buy into a particular programming
|
||||
language, framework, packaging system, or configuration language.
|
||||
|
||||
Is your application a Unix process? Does it use files, tcp connections,
|
||||
environment variables, standard Unix streams and command-line arguments
|
||||
as inputs and outputs? Then Docker can run it.
|
||||
|
||||
Can your application's build be expressed as a sequence of such
|
||||
commands? Then Docker can build it.
|
||||
|
||||
## Escape dependency hell
|
||||
|
||||
A common problem for developers is the difficulty of managing all
|
||||
their application's dependencies in a simple and automated way.
|
||||
|
||||
This is usually difficult for several reasons:
|
||||
|
||||
* *Cross-platform dependencies*. Modern applications often depend on
|
||||
a combination of system libraries and binaries, language-specific
|
||||
packages, framework-specific modules, internal components
|
||||
developed for another project, etc. These dependencies live in
|
||||
different "worlds" and require different tools - these tools
|
||||
typically don't work well with each other, requiring awkward
|
||||
custom integrations.
|
||||
|
||||
* *Conflicting dependencies*. Different applications may depend on
|
||||
different versions of the same dependency. Packaging tools handle
|
||||
these situations with various degrees of ease - but they all
|
||||
handle them in different and incompatible ways, which again forces
|
||||
the developer to do extra work.
|
||||
|
||||
* *Custom dependencies*. A developer may need to prepare a custom
|
||||
version of their application's dependency. Some packaging systems
|
||||
can handle custom versions of a dependency, others can't - and all
|
||||
of them handle it differently.
|
||||
|
||||
|
||||
Docker solves the problem of dependency hell by giving the developer a simple
|
||||
way to express *all* their application's dependencies in one place, while
|
||||
streamlining the process of assembling them. If this makes you think of
|
||||
[XKCD 927](https://xkcd.com/927/), don't worry. Docker doesn't
|
||||
*replace* your favorite packaging systems. It simply orchestrates
|
||||
their use in a simple and repeatable way. How does it do that? With
|
||||
layers.
|
||||
|
||||
Docker defines a build as running a sequence of Unix commands, one
|
||||
after the other, in the same container. Build commands modify the
|
||||
contents of the container (usually by installing new files on the
|
||||
filesystem), the next command modifies it some more, etc. Since each
|
||||
build command inherits the result of the previous commands, the
|
||||
*order* in which the commands are executed expresses *dependencies*.
|
||||
|
||||
Here's a typical Docker build process:
|
||||
|
||||
```bash
|
||||
FROM ubuntu:12.04
|
||||
RUN apt-get update && apt-get install -y python python-pip curl
|
||||
RUN curl -sSL https://github.com/shykes/helloflask/archive/master.tar.gz | tar -xzv
|
||||
RUN cd helloflask-master && pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Note that Docker doesn't care *how* dependencies are built - as long
|
||||
as they can be built by running a Unix command in a container.
|
||||
|
||||
|
||||
Getting started
|
||||
===============
|
||||
|
||||
Docker can be installed on your local machine as well as servers - both
|
||||
bare metal and virtualized. It is available as a binary on most modern
|
||||
Linux systems, or as a VM on Windows, Mac and other systems.
|
||||
|
||||
We also offer an [interactive tutorial](https://www.docker.com/tryit/)
|
||||
for quickly learning the basics of using Docker.
|
||||
|
||||
For up-to-date install instructions, see the [Docs](https://docs.docker.com).
|
||||
|
||||
Usage examples
|
||||
==============
|
||||
|
||||
Docker can be used to run short-lived commands, long-running daemons
|
||||
(app servers, databases, etc.), interactive shell sessions, etc.
|
||||
|
||||
You can find a [list of real-world
|
||||
examples](https://docs.docker.com/examples/) in the
|
||||
documentation.
|
||||
|
||||
Under the hood
|
||||
--------------
|
||||
|
||||
Under the hood, Docker is built on the following components:
|
||||
|
||||
* The
|
||||
[cgroups](https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt)
|
||||
and
|
||||
[namespaces](http://man7.org/linux/man-pages/man7/namespaces.7.html)
|
||||
capabilities of the Linux kernel
|
||||
* The [Go](https://golang.org) programming language
|
||||
* 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)
|
||||
|
||||
Contributing to Docker [](https://godoc.org/github.com/docker/docker)
|
||||
======================
|
||||
|
||||
| **Master** (Linux) | **Experimental** (linux) | **Windows** | **FreeBSD** |
|
||||
|------------------|----------------------|---------|---------|
|
||||
| [](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
|
||||
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
|
||||
feels wrong or incomplete. Better yet, submit a PR and improve them yourself.
|
||||
|
||||
Getting the development builds
|
||||
==============================
|
||||
|
||||
Want to run Docker from a master build? You can download
|
||||
master builds at [master.dockerproject.org](https://master.dockerproject.org).
|
||||
They are updated with each commit merged into the master branch.
|
||||
|
||||
Don't know how to use that super cool new feature in the master build? Check
|
||||
out the master docs at
|
||||
[docs.master.dockerproject.org](http://docs.master.dockerproject.org).
|
||||
|
||||
How the project is run
|
||||
======================
|
||||
|
||||
Docker is a very, very active project. If you want to learn more about how it is run,
|
||||
or want to get more involved, the best place to start is [the project directory](https://github.com/docker/docker/tree/master/project).
|
||||
|
||||
We are always open to suggestions on process improvements, and are always looking for more maintainers.
|
||||
|
||||
### Talking to other Docker users and contributors
|
||||
|
||||
<table class="tg">
|
||||
<col width="45%">
|
||||
<col width="65%">
|
||||
<tr>
|
||||
<td>Internet Relay Chat (IRC)</td>
|
||||
<td>
|
||||
<p>
|
||||
IRC a direct line to our most knowledgeable Docker users; we have
|
||||
both the <code>#docker</code> and <code>#docker-dev</code> group on
|
||||
<strong>irc.freenode.net</strong>.
|
||||
IRC is a rich chat protocol but it can overwhelm new users. You can search
|
||||
<a href="https://botbot.me/freenode/docker/#" target="_blank">our chat archives</a>.
|
||||
</p>
|
||||
Read our <a href="https://docs.docker.com/project/get-help/#irc-quickstart" target="_blank">IRC quickstart guide</a> for an easy way to get started.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Google Groups</td>
|
||||
<td>
|
||||
There are two groups.
|
||||
<a href="https://groups.google.com/forum/#!forum/docker-user" target="_blank">Docker-user</a>
|
||||
is for people using Docker containers.
|
||||
The <a href="https://groups.google.com/forum/#!forum/docker-dev" target="_blank">docker-dev</a>
|
||||
group is for contributors and other people contributing to the Docker
|
||||
project.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Twitter</td>
|
||||
<td>
|
||||
You can follow <a href="https://twitter.com/docker/" target="_blank">Docker's Twitter feed</a>
|
||||
to get updates on our products. You can also tweet us questions or just
|
||||
share blogs or stories.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Stack Overflow</td>
|
||||
<td>
|
||||
Stack Overflow has over 7000 Docker questions listed. We regularly
|
||||
monitor <a href="https://stackoverflow.com/search?tab=newest&q=docker" target="_blank">Docker questions</a>
|
||||
and so do many other knowledgeable Docker users.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Legal
|
||||
|
||||
*Brought to you courtesy of our legal counsel. For more context,
|
||||
please see the [NOTICE](https://github.com/moby/moby/blob/master/NOTICE) document in this repo.*
|
||||
please see the [NOTICE](https://github.com/docker/docker/blob/master/NOTICE) document in this repo.*
|
||||
|
||||
Use and transfer of Moby may be subject to certain restrictions by the
|
||||
Use and transfer of Docker may be subject to certain restrictions by the
|
||||
United States and other governments.
|
||||
|
||||
It is your responsibility to ensure that your use and/or transfer does not
|
||||
@@ -50,8 +268,33 @@ violate applicable laws.
|
||||
|
||||
For more information, please see https://www.bis.doc.gov
|
||||
|
||||
|
||||
Licensing
|
||||
=========
|
||||
Moby is licensed under the Apache License, Version 2.0. See
|
||||
[LICENSE](https://github.com/moby/moby/blob/master/LICENSE) for the full
|
||||
Docker is licensed under the Apache License, Version 2.0. See
|
||||
[LICENSE](https://github.com/docker/docker/blob/master/LICENSE) for the full
|
||||
license text.
|
||||
|
||||
Other Docker Related Projects
|
||||
=============================
|
||||
There are a number of projects under development that are based on Docker's
|
||||
core technology. These projects expand the tooling built around the
|
||||
Docker platform to broaden its application and utility.
|
||||
|
||||
* [Docker Registry](https://github.com/docker/distribution): Registry
|
||||
server for Docker (hosting/delivery of repositories and images)
|
||||
* [Docker Machine](https://github.com/docker/machine): Machine management
|
||||
for a container-centric world
|
||||
* [Docker Swarm](https://github.com/docker/swarm): A Docker-native clustering
|
||||
system
|
||||
* [Docker Compose](https://github.com/docker/compose) (formerly Fig):
|
||||
Define and run multi-container apps
|
||||
* [Kitematic](https://github.com/kitematic/kitematic): The easiest way to use
|
||||
Docker on Mac and Windows
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
212
ROADMAP.md
212
ROADMAP.md
@@ -1,117 +1,183 @@
|
||||
Moby Project Roadmap
|
||||
====================
|
||||
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 Moby contributors to understand where the project is going, and
|
||||
help determine if a contribution could be conflicting with some longer term plans.
|
||||
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! We are always happy to receive patches for new cool features we haven't thought about,
|
||||
or didn't judge to be a priority. Please however understand that such patches might take longer
|
||||
for us to review.
|
||||
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
|
||||
[Issues](https://github.com/moby/moby/issues?q=is%3Aopen+is%3Aissue+label%3Aroadmap). Our
|
||||
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 work on it to avoid duplicating effort! Similarly, if a maintainer is already
|
||||
assigned on an issue you'd like to participate in, pinging him on GitHub to offer your help is
|
||||
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 Moby Project: we are only beginning to structure and document the
|
||||
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, it is best to discuss with the maintainers on an
|
||||
issue, in the Slack channel, or in person at the Moby Summits that happen every few months.
|
||||
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 Runtime improvements
|
||||
## 1.1 Security
|
||||
|
||||
Over time we have accumulated a lot of functionality in the container runtime
|
||||
aspect of Moby while also growing in other areas. Much of the container runtime
|
||||
pieces are now duplicated work available in other, lower level components such
|
||||
as [containerd](https://containerd.io).
|
||||
Security is a top objective for the Docker Engine. The most notable items we intend to provide in
|
||||
the near future are:
|
||||
|
||||
Moby currently only utilizes containerd for basic runtime state management, e.g. starting
|
||||
and stopping a container, which is what the pre-containerd 1.0 daemon provided.
|
||||
Now that containerd is a full-fledged container runtime which supports full
|
||||
container life-cycle management, we would like to start relying more on containerd
|
||||
and removing the bits in Moby which are now duplicated. This will necessitate
|
||||
a significant effort to refactor and even remove large parts of Moby's codebase.
|
||||
- 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)
|
||||
|
||||
Tracking issues:
|
||||
## 1.2 Plumbing project
|
||||
|
||||
- [#38043](https://github.com/moby/moby/issues/38043) Proposal: containerd image integration
|
||||
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.
|
||||
|
||||
## 1.2 Image Builder
|
||||
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.
|
||||
|
||||
Work is ongoing to integrate [BuildKit](https://github.com/moby/buildkit) into
|
||||
Moby and replace the "v0" build implementation. Buildkit offers better cache
|
||||
management, parallelizable build steps, and better extensibility while also
|
||||
keeping builds portable, a chief tenent of Moby's builder.
|
||||
The architectural details are still being worked on, but one thing we know for sure is that we need
|
||||
to technically decouple the pieces.
|
||||
|
||||
Upon completion of this effort, users will have a builder that performs better
|
||||
while also being more extensible, enabling users to provide their own custom
|
||||
syntax which can be either Dockerfile-like or something completely different.
|
||||
### 1.2.1 Runtime
|
||||
|
||||
See [buildpacks on buildkit](https://github.com/tonistiigi/buildkit-pack) as an
|
||||
example of this extensibility.
|
||||
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.
|
||||
|
||||
New features for the builder and Dockerfile should be implemented first in the
|
||||
BuildKit backend using an external Dockerfile implementation from the container
|
||||
images. This allows everyone to test and evaluate the feature without upgrading
|
||||
their daemon. New features should go to the experimental channel first, and can be
|
||||
part of the `docker/dockerfile:experimental` image. From there they graduate to
|
||||
`docker/dockerfile:latest` and binary releases. The Dockerfile frontend source
|
||||
code is temporarily located at
|
||||
[https://github.com/moby/buildkit/tree/master/frontend/dockerfile](https://github.com/moby/buildkit/tree/master/frontend/dockerfile)
|
||||
with separate new features defined with go build tags.
|
||||
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.
|
||||
|
||||
Tracking issues:
|
||||
### 1.2.2 Builder
|
||||
|
||||
- [#32925](https://github.com/moby/moby/issues/32925) discussion: builder future: buildkit
|
||||
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.3 Rootless Mode
|
||||
### 1.2.3 Distribution
|
||||
|
||||
Running the daemon requires elevated privileges for many tasks. We would like to
|
||||
support running the daemon as a normal, unprivileged user without requiring `suid`
|
||||
binaries.
|
||||
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.
|
||||
|
||||
Tracking issues:
|
||||
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.
|
||||
|
||||
- [#37375](https://github.com/moby/moby/issues/37375) Proposal: allow running `dockerd` as an unprivileged user (aka rootless mode)
|
||||
### 1.2.4 Networking
|
||||
|
||||
## 1.4 Testing
|
||||
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.
|
||||
|
||||
Moby has many tests, both unit and integration. Moby needs more tests which can
|
||||
cover the full spectrum functionality and edge cases out there.
|
||||
## 1.3 Plugins
|
||||
|
||||
Tests in the `integration-cli` folder should also be migrated into (both in
|
||||
location and style) the `integration` folder. These newer tests are simpler to
|
||||
run in isolation, simpler to read, simpler to write, and more fully exercise the
|
||||
API. Meanwhile tests of the docker CLI should generally live in docker/cli.
|
||||
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.
|
||||
|
||||
Tracking issues:
|
||||
At the time of writing:
|
||||
|
||||
- [#32866](https://github.com/moby/moby/issues/32866) Replace integration-cli suite with API test suite
|
||||
- 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.5 Internal decoupling
|
||||
## 1.4 Volume management
|
||||
|
||||
A lot of work has been done in trying to decouple Moby internals. This process of creating
|
||||
standalone projects with a well defined function that attract a dedicated community should continue.
|
||||
As well as integrating `containerd` we would like to integrate [BuildKit](https://github.com/moby/buildkit)
|
||||
as the next standalone component.
|
||||
We see gRPC as the natural communication layer between decoupled components.
|
||||
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).
|
||||
|
||||
In addition to pushing out large components into other projects, much of the
|
||||
internal code structure, and in particular the
|
||||
["Daemon"](https://godoc.org/github.com/docker/docker/daemon#Daemon) object,
|
||||
should be split into smaller, more manageable, and more testable components.
|
||||
## 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 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.
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Reporting security issues
|
||||
|
||||
The Moby maintainers take security seriously. If you discover a security issue, please bring it to their attention right away!
|
||||
|
||||
### Reporting a Vulnerability
|
||||
|
||||
Please **DO NOT** file a public issue, instead send your report privately to security@docker.com.
|
||||
|
||||
Security reports are greatly appreciated and we will publicly thank you for it, although we keep your name confidential if you request it. We also like to send gifts—if you're into schwag, make sure to let us know. We currently do not offer a paid security bounty program, but are not ruling it out in the future.
|
||||
126
TESTING.md
126
TESTING.md
@@ -1,126 +0,0 @@
|
||||
# Testing
|
||||
|
||||
This document contains the Moby code testing guidelines. It should answer any
|
||||
questions you may have as an aspiring Moby contributor.
|
||||
|
||||
## Test suites
|
||||
|
||||
Moby has two test suites (and one legacy test suite):
|
||||
|
||||
* Unit tests - use standard `go test` and
|
||||
[gotest.tools/assert](https://godoc.org/gotest.tools/assert) assertions. They are located in
|
||||
the package they test. Unit tests should be fast and test only their own
|
||||
package.
|
||||
* API integration tests - use standard `go test` and
|
||||
[gotest.tools/assert](https://godoc.org/gotest.tools/assert) assertions. They are located in
|
||||
`./integration/<component>` directories, where `component` is: container,
|
||||
image, volume, etc. These tests perform HTTP requests to an API endpoint and
|
||||
check the HTTP response and daemon state after the call.
|
||||
|
||||
The legacy test suite `integration-cli/` is deprecated. No new tests will be
|
||||
added to this suite. Any tests in this suite which require updates should be
|
||||
ported to either the unit test suite or the new API integration test suite.
|
||||
|
||||
## Writing new tests
|
||||
|
||||
Most code changes will fall into one of the following categories.
|
||||
|
||||
### Writing tests for new features
|
||||
|
||||
New code should be covered by unit tests. If the code is difficult to test with
|
||||
unit tests, then that is a good sign that it should be refactored to make it
|
||||
easier to reuse and maintain. Consider accepting unexported interfaces instead
|
||||
of structs so that fakes can be provided for dependencies.
|
||||
|
||||
If the new feature includes a completely new API endpoint then a new API
|
||||
integration test should be added to cover the success case of that endpoint.
|
||||
|
||||
If the new feature does not include a completely new API endpoint consider
|
||||
adding the new API fields to the existing test for that endpoint. A new
|
||||
integration test should **not** be added for every new API field or API error
|
||||
case. Error cases should be handled by unit tests.
|
||||
|
||||
### Writing tests for bug fixes
|
||||
|
||||
Bugs fixes should include a unit test case which exercises the bug.
|
||||
|
||||
A bug fix may also include new assertions in existing integration tests for the
|
||||
API endpoint.
|
||||
|
||||
### Writing new integration tests
|
||||
|
||||
Note the `integration-cli` tests are deprecated; new tests will be rejected by
|
||||
the CI.
|
||||
|
||||
Instead, implement new tests under `integration/`.
|
||||
|
||||
### Integration tests environment considerations
|
||||
|
||||
When adding new tests or modifying existing tests under `integration/`, testing
|
||||
environment should be properly considered. `skip.If` from
|
||||
[gotest.tools/skip](https://godoc.org/gotest.tools/skip) can be used to make the
|
||||
test run conditionally. Full testing environment conditions can be found at
|
||||
[environment.go](https://github.com/moby/moby/blob/6b6eeed03b963a27085ea670f40cd5ff8a61f32e/testutil/environment/environment.go)
|
||||
|
||||
Here is a quick example. If the test needs to interact with a docker daemon on
|
||||
the same host, the following condition should be checked within the test code
|
||||
|
||||
```go
|
||||
skip.If(t, testEnv.IsRemoteDaemon())
|
||||
// your integration test code
|
||||
```
|
||||
|
||||
If a remote daemon is detected, the test will be skipped.
|
||||
|
||||
## Running tests
|
||||
|
||||
### Unit Tests
|
||||
|
||||
To run the unit test suite:
|
||||
|
||||
```
|
||||
make test-unit
|
||||
```
|
||||
|
||||
or `hack/test/unit` from inside a `BINDDIR=. make shell` container or properly
|
||||
configured environment.
|
||||
|
||||
The following environment variables may be used to run a subset of tests:
|
||||
|
||||
* `TESTDIRS` - paths to directories to be tested, defaults to `./...`
|
||||
* `TESTFLAGS` - flags passed to `go test`, to run tests which match a pattern
|
||||
use `TESTFLAGS="-test.run TestNameOrPrefix"`
|
||||
|
||||
### Integration Tests
|
||||
|
||||
To run the integration test suite:
|
||||
|
||||
```
|
||||
make test-integration
|
||||
```
|
||||
|
||||
This make target runs both the "integration" suite and the "integration-cli"
|
||||
suite.
|
||||
|
||||
You can specify which integration test dirs to build and run by specifying
|
||||
the list of dirs in the TEST_INTEGRATION_DIR environment variable.
|
||||
|
||||
You can also explicitly skip either suite by setting (any value) in
|
||||
TEST_SKIP_INTEGRATION and/or TEST_SKIP_INTEGRATION_CLI environment variables.
|
||||
|
||||
Flags specific to each suite can be set in the TESTFLAGS_INTEGRATION and
|
||||
TESTFLAGS_INTEGRATION_CLI environment variables.
|
||||
|
||||
If all you want is to specify a test filter to run, you can set the
|
||||
`TEST_FILTER` environment variable. This ends up getting passed directly to `go
|
||||
test -run` (or `go test -check-f`, depending on the test suite). It will also
|
||||
automatically set the other above mentioned environment variables accordingly.
|
||||
|
||||
### Go Version
|
||||
|
||||
You can change a version of golang used for building stuff that is being tested
|
||||
by setting `GO_VERSION` variable, for example:
|
||||
|
||||
```
|
||||
make GO_VERSION=1.12.8 test
|
||||
```
|
||||
46
VENDORING.md
46
VENDORING.md
@@ -1,46 +0,0 @@
|
||||
# Vendoring policies
|
||||
|
||||
This document outlines recommended Vendoring policies for Docker repositories.
|
||||
(Example, libnetwork is a Docker repo and logrus is not.)
|
||||
|
||||
## Vendoring using tags
|
||||
|
||||
Commit ID based vendoring provides little/no information about the updates
|
||||
vendored. To fix this, vendors will now require that repositories use annotated
|
||||
tags along with commit ids to snapshot commits. Annotated tags by themselves
|
||||
are not sufficient, since the same tag can be force updated to reference
|
||||
different commits.
|
||||
|
||||
Each tag should:
|
||||
- Follow Semantic Versioning rules (refer to section on "Semantic Versioning")
|
||||
- Have a corresponding entry in the change tracking document.
|
||||
|
||||
Each repo should:
|
||||
- Have a change tracking document between tags/releases. Ex: CHANGELOG.md,
|
||||
github releases file.
|
||||
|
||||
The goal here is for consuming repos to be able to use the tag version and
|
||||
changelog updates to determine whether the vendoring will cause any breaking or
|
||||
backward incompatible changes. This also means that repos can specify having
|
||||
dependency on a package of a specific version or greater up to the next major
|
||||
release, without encountering breaking changes.
|
||||
|
||||
## Semantic Versioning
|
||||
Annotated version tags should follow [Semantic Versioning](http://semver.org) policies:
|
||||
|
||||
"Given a version number MAJOR.MINOR.PATCH, increment the:
|
||||
|
||||
1. MAJOR version when you make incompatible API changes,
|
||||
2. MINOR version when you add functionality in a backwards-compatible manner, and
|
||||
3. PATCH version when you make backwards-compatible bug fixes.
|
||||
|
||||
Additional labels for pre-release and build metadata are available as extensions
|
||||
to the MAJOR.MINOR.PATCH format."
|
||||
|
||||
## Vendoring cadence
|
||||
In order to avoid huge vendoring changes, it is recommended to have a regular
|
||||
cadence for vendoring updates. e.g. monthly.
|
||||
|
||||
## Pre-merge vendoring tests
|
||||
All related repos will be vendored into docker/docker.
|
||||
CI on docker/docker should catch any breaking changes involving multiple repos.
|
||||
@@ -1,42 +1,5 @@
|
||||
# Working on the Engine API
|
||||
This directory contains code pertaining to the Docker API:
|
||||
|
||||
The Engine API is an HTTP API used by the command-line client to communicate with the daemon. It can also be used by third-party software to control the daemon.
|
||||
- Used by the docker client when communicating with the docker daemon
|
||||
|
||||
It consists of various components in this repository:
|
||||
|
||||
- `api/swagger.yaml` A Swagger definition of the API.
|
||||
- `api/types/` Types shared by both the client and server, representing various objects, options, responses, etc. Most are written manually, but some are automatically generated from the Swagger definition. See [#27919](https://github.com/docker/docker/issues/27919) for progress on this.
|
||||
- `cli/` The command-line client.
|
||||
- `client/` The Go client used by the command-line client. It can also be used by third-party Go programs.
|
||||
- `daemon/` The daemon, which serves the API.
|
||||
|
||||
## Swagger definition
|
||||
|
||||
The API is defined by the [Swagger](http://swagger.io/specification/) definition in `api/swagger.yaml`. This definition can be used to:
|
||||
|
||||
1. Automatically generate documentation.
|
||||
2. Automatically generate the Go server and client. (A work-in-progress.)
|
||||
3. Provide a machine readable version of the API for introspecting what it can do, automatically generating clients for other languages, etc.
|
||||
|
||||
## Updating the API documentation
|
||||
|
||||
The API documentation is generated entirely from `api/swagger.yaml`. If you make updates to the API, edit this file to represent the change in the documentation.
|
||||
|
||||
The file is split into two main sections:
|
||||
|
||||
- `definitions`, which defines re-usable objects used in requests and responses
|
||||
- `paths`, which defines the API endpoints (and some inline objects which don't need to be reusable)
|
||||
|
||||
To make an edit, first look for the endpoint you want to edit under `paths`, then make the required edits. Endpoints may reference reusable objects with `$ref`, which can be found in the `definitions` section.
|
||||
|
||||
There is hopefully enough example material in the file for you to copy a similar pattern from elsewhere in the file (e.g. adding new fields or endpoints), but for the full reference, see the [Swagger specification](https://github.com/docker/docker/issues/27919).
|
||||
|
||||
`swagger.yaml` is validated by `hack/validate/swagger` to ensure it is a valid Swagger definition. This is useful when making edits to ensure you are doing the right thing.
|
||||
|
||||
## Viewing the API documentation
|
||||
|
||||
When you make edits to `swagger.yaml`, you may want to check the generated API documentation to ensure it renders correctly.
|
||||
|
||||
Run `make swagger-docs` and a preview will be running at `http://localhost`. Some of the styling may be incorrect, but you'll be able to ensure that it is generating the correct documentation.
|
||||
|
||||
The production documentation is generated by vendoring `swagger.yaml` into [docker/docker.github.io](https://github.com/docker/docker.github.io).
|
||||
- Used by third party tools wishing to interface with the docker daemon
|
||||
|
||||
84
api/client/attach.go
Normal file
84
api/client/attach.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
)
|
||||
|
||||
// CmdAttach attaches to a running container.
|
||||
//
|
||||
// Usage: docker attach [OPTIONS] CONTAINER
|
||||
func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||
cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, Cli.DockerCommands["attach"].Description, true)
|
||||
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.ParseFlags(args, true)
|
||||
|
||||
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
var c types.ContainerJSON
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !c.State.Running {
|
||||
return fmt.Errorf("You cannot attach to a stopped container, start it first")
|
||||
}
|
||||
|
||||
if err := cli.CheckTtyInput(!*noStdin, c.Config.Tty); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Config.Tty && cli.isTerminalOut {
|
||||
if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
||||
logrus.Debugf("Error monitoring TTY size: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
var in io.ReadCloser
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("stream", "1")
|
||||
if !*noStdin && c.Config.OpenStdin {
|
||||
v.Set("stdin", "1")
|
||||
in = cli.in
|
||||
}
|
||||
|
||||
v.Set("stdout", "1")
|
||||
v.Set("stderr", "1")
|
||||
|
||||
if *proxy && !c.Config.Tty {
|
||||
sigc := cli.forwardAllSignals(cmd.Arg(0))
|
||||
defer signal.StopCatch(sigc)
|
||||
}
|
||||
|
||||
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), c.Config.Tty, in, cli.out, cli.err, nil, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, status, err := getExitCode(cli, cmd.Arg(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status != 0 {
|
||||
return Cli.StatusError{StatusCode: status}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
674
api/client/build.go
Normal file
674
api/client/build.go
Normal file
@@ -0,0 +1,674 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/graph/tags"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/fileutils"
|
||||
"github.com/docker/docker/pkg/httputils"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/pkg/progressreader"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/pkg/ulimit"
|
||||
"github.com/docker/docker/pkg/units"
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
tarHeaderSize = 512
|
||||
)
|
||||
|
||||
// CmdBuild builds a new image from the source code at a given path.
|
||||
//
|
||||
// If '-' is provided instead of a path or URL, Docker will build an image from either a Dockerfile or tar archive read from STDIN.
|
||||
//
|
||||
// Usage: docker build [OPTIONS] PATH | URL | -
|
||||
func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||
cmd := Cli.Subcmd("build", []string{"PATH | URL | -"}, Cli.DockerCommands["build"].Description, true)
|
||||
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")
|
||||
noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
|
||||
rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build")
|
||||
forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers")
|
||||
pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image")
|
||||
dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
|
||||
flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
|
||||
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)")
|
||||
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")
|
||||
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)")
|
||||
flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
|
||||
flBuildArg := opts.NewListOpts(opts.ValidateEnv)
|
||||
cmd.Var(&flBuildArg, []string{"-build-arg"}, "Set build-time variables")
|
||||
|
||||
ulimits := make(map[string]*ulimit.Ulimit)
|
||||
flUlimits := opts.NewUlimitOpt(&ulimits)
|
||||
cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options")
|
||||
|
||||
cmd.Require(flag.Exact, 1)
|
||||
|
||||
// For trusted pull on "FROM <image>" instruction.
|
||||
addTrustedFlags(cmd, true)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
var (
|
||||
context io.ReadCloser
|
||||
isRemote bool
|
||||
err error
|
||||
)
|
||||
|
||||
_, err = exec.LookPath("git")
|
||||
hasGit := err == nil
|
||||
|
||||
specifiedContext := cmd.Arg(0)
|
||||
|
||||
var (
|
||||
contextDir string
|
||||
tempDir string
|
||||
relDockerfile string
|
||||
)
|
||||
|
||||
switch {
|
||||
case specifiedContext == "-":
|
||||
tempDir, relDockerfile, err = getContextFromReader(cli.in, *dockerfileName)
|
||||
case urlutil.IsGitURL(specifiedContext) && hasGit:
|
||||
tempDir, relDockerfile, err = getContextFromGitURL(specifiedContext, *dockerfileName)
|
||||
case urlutil.IsURL(specifiedContext):
|
||||
tempDir, relDockerfile, err = getContextFromURL(cli.out, specifiedContext, *dockerfileName)
|
||||
default:
|
||||
contextDir, relDockerfile, err = getContextFromLocalDir(specifiedContext, *dockerfileName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to prepare context: %s", err)
|
||||
}
|
||||
|
||||
if tempDir != "" {
|
||||
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)
|
||||
}
|
||||
|
||||
f, err := os.Open(filepath.Join(contextDir, ".dockerignore"))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
var excludes []string
|
||||
if err == nil {
|
||||
excludes, err = utils.ReadDockerIgnore(f)
|
||||
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.
|
||||
var includes = []string{"."}
|
||||
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
|
||||
// FIXME: ProgressReader shouldn't be this annoying to use
|
||||
sf := streamformatter.NewStreamFormatter()
|
||||
var body io.Reader = progressreader.New(progressreader.Config{
|
||||
In: context,
|
||||
Out: cli.out,
|
||||
Formatter: sf,
|
||||
NewLines: true,
|
||||
ID: "",
|
||||
Action: "Sending build context to Docker daemon",
|
||||
})
|
||||
|
||||
var memory int64
|
||||
if *flMemoryString != "" {
|
||||
parsedMemory, err := units.RAMInBytes(*flMemoryString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
memory = parsedMemory
|
||||
}
|
||||
|
||||
var memorySwap int64
|
||||
if *flMemorySwap != "" {
|
||||
if *flMemorySwap == "-1" {
|
||||
memorySwap = -1
|
||||
} else {
|
||||
parsedMemorySwap, err := units.RAMInBytes(*flMemorySwap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
memorySwap = parsedMemorySwap
|
||||
}
|
||||
}
|
||||
// Send the build context
|
||||
v := &url.Values{}
|
||||
|
||||
//Check if the given image name can be resolved
|
||||
if *tag != "" {
|
||||
repository, tag := parsers.ParseRepositoryTag(*tag)
|
||||
if err := registry.ValidateRepositoryName(repository); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(tag) > 0 {
|
||||
if err := tags.ValidateTagName(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v.Set("t", *tag)
|
||||
|
||||
if *suppressOutput {
|
||||
v.Set("q", "1")
|
||||
}
|
||||
if isRemote {
|
||||
v.Set("remote", cmd.Arg(0))
|
||||
}
|
||||
if *noCache {
|
||||
v.Set("nocache", "1")
|
||||
}
|
||||
if *rm {
|
||||
v.Set("rm", "1")
|
||||
} else {
|
||||
v.Set("rm", "0")
|
||||
}
|
||||
|
||||
if *forceRm {
|
||||
v.Set("forcerm", "1")
|
||||
}
|
||||
|
||||
if *pull {
|
||||
v.Set("pull", "1")
|
||||
}
|
||||
|
||||
v.Set("cpusetcpus", *flCPUSetCpus)
|
||||
v.Set("cpusetmems", *flCPUSetMems)
|
||||
v.Set("cpushares", strconv.FormatInt(*flCPUShares, 10))
|
||||
v.Set("cpuquota", strconv.FormatInt(*flCPUQuota, 10))
|
||||
v.Set("cpuperiod", strconv.FormatInt(*flCPUPeriod, 10))
|
||||
v.Set("memory", strconv.FormatInt(memory, 10))
|
||||
v.Set("memswap", strconv.FormatInt(memorySwap, 10))
|
||||
v.Set("cgroupparent", *flCgroupParent)
|
||||
|
||||
v.Set("dockerfile", relDockerfile)
|
||||
|
||||
ulimitsVar := flUlimits.GetList()
|
||||
ulimitsJSON, err := json.Marshal(ulimitsVar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set("ulimits", string(ulimitsJSON))
|
||||
|
||||
// collect all the build-time environment variables for the container
|
||||
buildArgs := runconfig.ConvertKVStringsToMap(flBuildArg.GetAll())
|
||||
buildArgsJSON, err := json.Marshal(buildArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set("buildargs", string(buildArgsJSON))
|
||||
|
||||
headers := http.Header(make(map[string][]string))
|
||||
buf, err := json.Marshal(cli.configFile.AuthConfigs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
|
||||
headers.Set("Content-Type", "application/tar")
|
||||
|
||||
sopts := &streamOpts{
|
||||
rawTerminal: true,
|
||||
in: body,
|
||||
out: cli.out,
|
||||
headers: headers,
|
||||
}
|
||||
|
||||
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 no error code is set, default to 1
|
||||
if jerr.Code == 0 {
|
||||
jerr.Code = 1
|
||||
}
|
||||
return Cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
169
api/client/cli.go
Normal file
169
api/client/cli.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cliconfig"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/docker/pkg/sockets"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/pkg/tlsconfig"
|
||||
)
|
||||
|
||||
// DockerCli represents the docker command line client.
|
||||
// Instances of the client can be returned from NewDockerCli.
|
||||
type DockerCli struct {
|
||||
// initializing closure
|
||||
init func() error
|
||||
|
||||
// proto holds the client protocol i.e. unix.
|
||||
proto string
|
||||
// addr holds the client address.
|
||||
addr string
|
||||
// basePath holds the path to prepend to the requests
|
||||
basePath string
|
||||
|
||||
// configFile has the client configuration file
|
||||
configFile *cliconfig.ConfigFile
|
||||
// in holds the input stream and closer (io.ReadCloser) for the client.
|
||||
in io.ReadCloser
|
||||
// out holds the output stream (io.Writer) for the client.
|
||||
out io.Writer
|
||||
// err holds the error stream (io.Writer) for the client.
|
||||
err io.Writer
|
||||
// keyFile holds the key file as a string.
|
||||
keyFile string
|
||||
// tlsConfig holds the TLS configuration for the client, and will
|
||||
// set the scheme to https in NewDockerCli if present.
|
||||
tlsConfig *tls.Config
|
||||
// scheme holds the scheme of the client i.e. https.
|
||||
scheme string
|
||||
// inFd holds the file descriptor of the client's STDIN (if valid).
|
||||
inFd uintptr
|
||||
// outFd holds file descriptor of the client's STDOUT (if valid).
|
||||
outFd uintptr
|
||||
// isTerminalIn indicates whether the client's STDIN is a TTY
|
||||
isTerminalIn bool
|
||||
// isTerminalOut indicates whether the client's STDOUT is a TTY
|
||||
isTerminalOut bool
|
||||
// transport holds the client transport instance.
|
||||
transport *http.Transport
|
||||
}
|
||||
|
||||
// Initialize calls the init function that will setup the configuration for the client
|
||||
// such as the TLS, tcp and other parameters used to run the client.
|
||||
func (cli *DockerCli) Initialize() error {
|
||||
if cli.init == nil {
|
||||
return nil
|
||||
}
|
||||
return cli.init()
|
||||
}
|
||||
|
||||
// CheckTtyInput checks if we are trying to attach to a container tty
|
||||
// from a non-tty client input stream, and if so, returns an error.
|
||||
func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
|
||||
// In order to attach to a container tty, input stream for the client must
|
||||
// be a tty itself: redirecting or piping the client standard input is
|
||||
// incompatible with `docker run -t`, `docker exec -t` or `docker attach`.
|
||||
if ttyMode && attachStdin && !cli.isTerminalIn {
|
||||
return errors.New("cannot enable tty mode on non tty input")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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.
|
||||
// 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 {
|
||||
cli := &DockerCli{
|
||||
in: in,
|
||||
out: out,
|
||||
err: err,
|
||||
keyFile: clientFlags.Common.TrustKey,
|
||||
}
|
||||
|
||||
cli.init = func() error {
|
||||
|
||||
clientFlags.PostParse()
|
||||
|
||||
hosts := clientFlags.Common.Hosts
|
||||
|
||||
switch len(hosts) {
|
||||
case 0:
|
||||
hosts = []string{os.Getenv("DOCKER_HOST")}
|
||||
case 1:
|
||||
// only accept one host to talk to
|
||||
default:
|
||||
return errors.New("Please specify only one -H")
|
||||
}
|
||||
|
||||
defaultHost := opts.DefaultTCPHost
|
||||
if clientFlags.Common.TLSOptions != nil {
|
||||
defaultHost = opts.DefaultTLSHost
|
||||
}
|
||||
|
||||
var e error
|
||||
if hosts[0], e = opts.ParseHost(defaultHost, hosts[0]); e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
5
api/client/client.go
Normal file
5
api/client/client.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// Package client provides a command-line interface for Docker.
|
||||
//
|
||||
// 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.
|
||||
package client
|
||||
84
api/client/commit.go
Normal file
84
api/client/commit.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"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"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/runconfig"
|
||||
)
|
||||
|
||||
// CmdCommit creates a new image from a container's changes.
|
||||
//
|
||||
// Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
|
||||
func (cli *DockerCli) CmdCommit(args ...string) error {
|
||||
cmd := Cli.Subcmd("commit", []string{"CONTAINER [REPOSITORY[:TAG]]"}, Cli.DockerCommands["commit"].Description, true)
|
||||
flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit")
|
||||
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>\")")
|
||||
flChanges := opts.NewListOpts(nil)
|
||||
cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
|
||||
// FIXME: --run is deprecated, it will be replaced with inline Dockerfile 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.Min, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
var (
|
||||
name = cmd.Arg(0)
|
||||
repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1))
|
||||
)
|
||||
|
||||
//Check if the given image name can be resolved
|
||||
if repository != "" {
|
||||
if err := registry.ValidateRepositoryName(repository); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("container", name)
|
||||
v.Set("repo", repository)
|
||||
v.Set("tag", tag)
|
||||
v.Set("comment", *flComment)
|
||||
v.Set("author", *flAuthor)
|
||||
for _, change := range flChanges.GetAll() {
|
||||
v.Add("changes", change)
|
||||
}
|
||||
|
||||
if *flPause != true {
|
||||
v.Set("pause", "0")
|
||||
}
|
||||
|
||||
var (
|
||||
config *runconfig.Config
|
||||
response types.ContainerCommitResponse
|
||||
)
|
||||
|
||||
if *flConfig != "" {
|
||||
config = &runconfig.Config{}
|
||||
if err := json.Unmarshal([]byte(*flConfig), config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
serverResp, err := cli.call("POST", "/commit?"+v.Encode(), config, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(cli.out, response.ID)
|
||||
return nil
|
||||
}
|
||||
325
api/client/cp.go
Normal file
325
api/client/cp.go
Normal file
@@ -0,0 +1,325 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
type copyDirection int
|
||||
|
||||
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
|
||||
// tar archive file to STDOUT.
|
||||
//
|
||||
// When copying to a container, if LOCALPATH is '-' the data is read as a tar
|
||||
// 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 {
|
||||
cmd := Cli.Subcmd(
|
||||
"cp",
|
||||
[]string{"CONTAINER:PATH LOCALPATH|-", "LOCALPATH|- CONTAINER:PATH"},
|
||||
strings.Join([]string{
|
||||
Cli.DockerCommands["cp"].Description,
|
||||
"\nUse '-' 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.ParseFlags(args, true)
|
||||
|
||||
if cmd.Arg(0) == "" {
|
||||
return fmt.Errorf("source can not be empty")
|
||||
}
|
||||
if cmd.Arg(1) == "" {
|
||||
return fmt.Errorf("destination can not be empty")
|
||||
}
|
||||
|
||||
srcContainer, srcPath := splitCpArg(cmd.Arg(0))
|
||||
dstContainer, dstPath := splitCpArg(cmd.Arg(1))
|
||||
|
||||
var direction copyDirection
|
||||
if srcContainer != "" {
|
||||
direction |= fromContainer
|
||||
}
|
||||
if dstContainer != "" {
|
||||
direction |= toContainer
|
||||
}
|
||||
|
||||
switch direction {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
defer response.body.Close()
|
||||
|
||||
if response.statusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
|
||||
}
|
||||
|
||||
if dstPath == "-" {
|
||||
// Send the response to STDOUT.
|
||||
_, 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 {
|
||||
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
|
||||
}
|
||||
185
api/client/create.go
Normal file
185
api/client/create.go
Normal file
@@ -0,0 +1,185 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/graph/tags"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/runconfig"
|
||||
)
|
||||
|
||||
func (cli *DockerCli) pullImage(image string) error {
|
||||
return cli.pullImageCustomOut(image, cli.out)
|
||||
}
|
||||
|
||||
func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
||||
v := url.Values{}
|
||||
repos, tag := parsers.ParseRepositoryTag(image)
|
||||
// pull only the image tagged 'latest' if no tag was specified
|
||||
if tag == "" {
|
||||
tag = tags.DefaultTag
|
||||
}
|
||||
v.Set("fromImage", repos)
|
||||
v.Set("tag", tag)
|
||||
|
||||
// Resolve the Repository name from fqn to RepositoryInfo
|
||||
repoInfo, err := registry.ParseRepositoryInfo(repos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Resolve the Auth config relevant for this server
|
||||
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registryAuthHeader := []string{
|
||||
base64.URLEncoding.EncodeToString(buf),
|
||||
}
|
||||
sopts := &streamOpts{
|
||||
rawTerminal: true,
|
||||
out: out,
|
||||
headers: map[string][]string{"X-Registry-Auth": registryAuthHeader},
|
||||
}
|
||||
if _, err := cli.stream("POST", "/images/create?"+v.Encode(), sopts); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type cidFile struct {
|
||||
path string
|
||||
file *os.File
|
||||
written bool
|
||||
}
|
||||
|
||||
func newCIDFile(path string) (*cidFile, error) {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path)
|
||||
}
|
||||
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to create the container ID file: %s", err)
|
||||
}
|
||||
|
||||
return &cidFile{path: path, file: f}, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runconfig.HostConfig, cidfile, name string) (*types.ContainerCreateResponse, error) {
|
||||
containerValues := url.Values{}
|
||||
if name != "" {
|
||||
containerValues.Set("name", name)
|
||||
}
|
||||
|
||||
mergedConfig := runconfig.MergeConfigs(config, hostConfig)
|
||||
|
||||
var containerIDFile *cidFile
|
||||
if cidfile != "" {
|
||||
var err error
|
||||
if containerIDFile, err = newCIDFile(cidfile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
serverResp, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil)
|
||||
//if image not found try to pull it
|
||||
if serverResp.statusCode == 404 && strings.Contains(err.Error(), config.Image) {
|
||||
fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", ref.ImageName(repo))
|
||||
|
||||
// we don't want to write to stdout anything apart from container.ID
|
||||
if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil {
|
||||
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
|
||||
if serverResp, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
var response types.ContainerCreateResponse
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, warning := range response.Warnings {
|
||||
fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
|
||||
}
|
||||
if containerIDFile != nil {
|
||||
if err = containerIDFile.Write(response.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// CmdCreate creates a new container from a given image.
|
||||
//
|
||||
// Usage: docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
|
||||
func (cli *DockerCli) CmdCreate(args ...string) error {
|
||||
cmd := Cli.Subcmd("create", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["create"].Description, true)
|
||||
addTrustedFlags(cmd, true)
|
||||
|
||||
// These are flags not stored in Config/HostConfig
|
||||
var (
|
||||
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
|
||||
)
|
||||
|
||||
config, hostConfig, cmd, err := runconfig.Parse(cmd, args)
|
||||
if err != nil {
|
||||
cmd.ReportError(err.Error(), true)
|
||||
os.Exit(1)
|
||||
}
|
||||
if config.Image == "" {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
response, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(cli.out, "%s\n", response.ID)
|
||||
return nil
|
||||
}
|
||||
56
api/client/diff.go
Normal file
56
api/client/diff.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
// CmdDiff shows changes on a container's filesystem.
|
||||
//
|
||||
// Each changed file is printed on a separate line, prefixed with a single
|
||||
// character that indicates the status of the file: C (modified), A (added),
|
||||
// or D (deleted).
|
||||
//
|
||||
// Usage: docker diff CONTAINER
|
||||
func (cli *DockerCli) CmdDiff(args ...string) error {
|
||||
cmd := Cli.Subcmd("diff", []string{"CONTAINER"}, Cli.DockerCommands["diff"].Description, true)
|
||||
cmd.Require(flag.Exact, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
if cmd.Arg(0) == "" {
|
||||
return fmt.Errorf("Container name cannot be empty")
|
||||
}
|
||||
|
||||
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
changes := []types.ContainerChange{}
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&changes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, change := range changes {
|
||||
var kind string
|
||||
switch change.Kind {
|
||||
case archive.ChangeModify:
|
||||
kind = "C"
|
||||
case archive.ChangeAdd:
|
||||
kind = "A"
|
||||
case archive.ChangeDelete:
|
||||
kind = "D"
|
||||
}
|
||||
fmt.Fprintf(cli.out, "%s %s\n", kind, change.Path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
63
api/client/events.go
Normal file
63
api/client/events.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
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"
|
||||
"github.com/docker/docker/pkg/timeutils"
|
||||
)
|
||||
|
||||
// CmdEvents prints a live stream of real time events from the server.
|
||||
//
|
||||
// Usage: docker events [OPTIONS]
|
||||
func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||
cmd := Cli.Subcmd("events", nil, Cli.DockerCommands["events"].Description, true)
|
||||
since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
|
||||
until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
|
||||
flFilter := opts.NewListOpts(nil)
|
||||
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
|
||||
cmd.Require(flag.Exact, 0)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
var (
|
||||
v = url.Values{}
|
||||
eventFilterArgs = filters.Args{}
|
||||
)
|
||||
|
||||
// Consolidate all filter flags, and sanity check them early.
|
||||
// They'll get process in the daemon/server.
|
||||
for _, f := range flFilter.GetAll() {
|
||||
var err error
|
||||
eventFilterArgs, err = filters.ParseFlag(f, eventFilterArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
ref := time.Now()
|
||||
if *since != "" {
|
||||
v.Set("since", timeutils.GetTimestamp(*since, ref))
|
||||
}
|
||||
if *until != "" {
|
||||
v.Set("until", timeutils.GetTimestamp(*until, ref))
|
||||
}
|
||||
if len(eventFilterArgs) > 0 {
|
||||
filterJSON, err := filters.ToParam(eventFilterArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set("filters", filterJSON)
|
||||
}
|
||||
sopts := &streamOpts{
|
||||
rawTerminal: true,
|
||||
out: cli.out,
|
||||
}
|
||||
if _, err := cli.stream("GET", "/events?"+v.Encode(), sopts); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
134
api/client/exec.go
Normal file
134
api/client/exec.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/docker/docker/runconfig"
|
||||
)
|
||||
|
||||
// CmdExec runs a command in a running container.
|
||||
//
|
||||
// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
|
||||
func (cli *DockerCli) CmdExec(args ...string) error {
|
||||
cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true)
|
||||
|
||||
execConfig, err := runconfig.ParseExec(cmd, args)
|
||||
// just in case the ParseExec does not exit
|
||||
if execConfig.Container == "" || err != nil {
|
||||
return Cli.StatusError{StatusCode: 1}
|
||||
}
|
||||
|
||||
serverResp, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
var response types.ContainerExecCreateResponse
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
execID := response.ID
|
||||
|
||||
if execID == "" {
|
||||
fmt.Fprintf(cli.out, "exec ID empty")
|
||||
return nil
|
||||
}
|
||||
|
||||
//Temp struct for execStart so that we don't need to transfer all the execConfig
|
||||
execStartCheck := &types.ExecStartCheck{
|
||||
Detach: execConfig.Detach,
|
||||
Tty: execConfig.Tty,
|
||||
}
|
||||
|
||||
if !execConfig.Detach {
|
||||
if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execStartCheck, nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
// For now don't print this - wait for when we support exec wait()
|
||||
// fmt.Fprintf(cli.out, "%s\n", execID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Interactive exec requested.
|
||||
var (
|
||||
out, stderr io.Writer
|
||||
in io.ReadCloser
|
||||
hijacked = make(chan io.Closer)
|
||||
errCh chan error
|
||||
)
|
||||
|
||||
// Block the return until the chan gets closed
|
||||
defer func() {
|
||||
logrus.Debugf("End of CmdExec(), Waiting for hijack to finish.")
|
||||
if _, ok := <-hijacked; ok {
|
||||
fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)")
|
||||
}
|
||||
}()
|
||||
|
||||
if execConfig.AttachStdin {
|
||||
in = cli.in
|
||||
}
|
||||
if execConfig.AttachStdout {
|
||||
out = cli.out
|
||||
}
|
||||
if execConfig.AttachStderr {
|
||||
if execConfig.Tty {
|
||||
stderr = cli.out
|
||||
} else {
|
||||
stderr = cli.err
|
||||
}
|
||||
}
|
||||
errCh = promise.Go(func() error {
|
||||
return cli.hijack("POST", "/exec/"+execID+"/start", execConfig.Tty, in, out, stderr, hijacked, execConfig)
|
||||
})
|
||||
|
||||
// Acknowledge the hijack before starting
|
||||
select {
|
||||
case closer := <-hijacked:
|
||||
// Make sure that hijack gets closed when returning. (result
|
||||
// in closing hijack chan and freeing server's goroutines.
|
||||
if closer != nil {
|
||||
defer closer.Close()
|
||||
}
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
logrus.Debugf("Error hijack: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if execConfig.Tty && cli.isTerminalIn {
|
||||
if err := cli.monitorTtySize(execID, true); err != nil {
|
||||
fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := <-errCh; err != nil {
|
||||
logrus.Debugf("Error hijack: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
var status int
|
||||
if _, status, err = getExecExitCode(cli, execID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if status != 0 {
|
||||
return Cli.StatusError{StatusCode: status}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
46
api/client/export.go
Normal file
46
api/client/export.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
// CmdExport exports a filesystem as a tar archive.
|
||||
//
|
||||
// The tar archive is streamed to STDOUT by default or written to a file.
|
||||
//
|
||||
// Usage: docker export [OPTIONS] CONTAINER
|
||||
func (cli *DockerCli) CmdExport(args ...string) error {
|
||||
cmd := Cli.Subcmd("export", []string{"CONTAINER"}, Cli.DockerCommands["export"].Description, true)
|
||||
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
|
||||
cmd.Require(flag.Exact, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
var (
|
||||
output = cli.out
|
||||
err error
|
||||
)
|
||||
if *outfile != "" {
|
||||
output, err = os.Create(*outfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if cli.isTerminalOut {
|
||||
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
||||
}
|
||||
|
||||
image := cmd.Arg(0)
|
||||
sopts := &streamOpts{
|
||||
rawTerminal: true,
|
||||
out: output,
|
||||
}
|
||||
if _, err := cli.stream("GET", "/containers/"+image+"/export", sopts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
254
api/client/hijack.go
Normal file
254
api/client/hijack.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/autogen/dockerversion"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
)
|
||||
|
||||
type tlsClientCon struct {
|
||||
*tls.Conn
|
||||
rawConn net.Conn
|
||||
}
|
||||
|
||||
func (c *tlsClientCon) CloseWrite() error {
|
||||
// Go standard tls.Conn doesn't provide the CloseWrite() method so we do it
|
||||
// on its underlying connection.
|
||||
if cwc, ok := c.rawConn.(interface {
|
||||
CloseWrite() error
|
||||
}); ok {
|
||||
return cwc.CloseWrite()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
|
||||
return tlsDialWithDialer(new(net.Dialer), network, addr, config)
|
||||
}
|
||||
|
||||
// We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in
|
||||
// order to return our custom tlsClientCon struct which holds both the tls.Conn
|
||||
// object _and_ its underlying raw connection. The rationale for this is that
|
||||
// we need to be able to close the write end of the connection when attaching,
|
||||
// which tls.Conn does not provide.
|
||||
func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
|
||||
// We want the Timeout and Deadline values from dialer to cover the
|
||||
// whole process: TCP connection and TLS handshake. This means that we
|
||||
// also need to start our own timers now.
|
||||
timeout := dialer.Timeout
|
||||
|
||||
if !dialer.Deadline.IsZero() {
|
||||
deadlineTimeout := dialer.Deadline.Sub(time.Now())
|
||||
if timeout == 0 || deadlineTimeout < timeout {
|
||||
timeout = deadlineTimeout
|
||||
}
|
||||
}
|
||||
|
||||
var errChannel chan error
|
||||
|
||||
if timeout != 0 {
|
||||
errChannel = make(chan error, 2)
|
||||
time.AfterFunc(timeout, func() {
|
||||
errChannel <- errors.New("")
|
||||
})
|
||||
}
|
||||
|
||||
rawConn, err := dialer.Dial(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// When we set up a TCP connection for hijack, there could be long periods
|
||||
// of inactivity (a long running command with no output) that in certain
|
||||
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
||||
// state. Setting TCP KeepAlive on the socket connection will prohibit
|
||||
// ECONNTIMEOUT unless the socket connection truly is broken
|
||||
if tcpConn, ok := rawConn.(*net.TCPConn); ok {
|
||||
tcpConn.SetKeepAlive(true)
|
||||
tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
||||
}
|
||||
|
||||
colonPos := strings.LastIndex(addr, ":")
|
||||
if colonPos == -1 {
|
||||
colonPos = len(addr)
|
||||
}
|
||||
hostname := addr[:colonPos]
|
||||
|
||||
// If no ServerName is set, infer the ServerName
|
||||
// from the hostname we're connecting to.
|
||||
if config.ServerName == "" {
|
||||
// Make a copy to avoid polluting argument or default.
|
||||
c := *config
|
||||
c.ServerName = hostname
|
||||
config = &c
|
||||
}
|
||||
|
||||
conn := tls.Client(rawConn, config)
|
||||
|
||||
if timeout == 0 {
|
||||
err = conn.Handshake()
|
||||
} else {
|
||||
go func() {
|
||||
errChannel <- conn.Handshake()
|
||||
}()
|
||||
|
||||
err = <-errChannel
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
rawConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This is Docker difference with standard's crypto/tls package: returned a
|
||||
// wrapper which holds both the TLS and raw connections.
|
||||
return &tlsClientCon{conn, rawConn}, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) dial() (net.Conn, error) {
|
||||
if cli.tlsConfig != nil && cli.proto != "unix" {
|
||||
// Notice this isn't Go standard's tls.Dial function
|
||||
return tlsDial(cli.proto, cli.addr, cli.tlsConfig)
|
||||
}
|
||||
return net.Dial(cli.proto, cli.addr)
|
||||
}
|
||||
|
||||
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer, data interface{}) error {
|
||||
defer func() {
|
||||
if started != nil {
|
||||
close(started)
|
||||
}
|
||||
}()
|
||||
|
||||
params, err := cli.encodeData(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
|
||||
// then the user can't change OUR headers
|
||||
for k, v := range cli.configFile.HTTPHeaders {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
req.Header.Set("Connection", "Upgrade")
|
||||
req.Header.Set("Upgrade", "tcp")
|
||||
req.Host = cli.addr
|
||||
|
||||
dial, err := cli.dial()
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// When we set up a TCP connection for hijack, there could be long periods
|
||||
// of inactivity (a long running command with no output) that in certain
|
||||
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
||||
// state. Setting TCP KeepAlive on the socket connection will prohibit
|
||||
// ECONNTIMEOUT unless the socket connection truly is broken
|
||||
if tcpConn, ok := dial.(*net.TCPConn); ok {
|
||||
tcpConn.SetKeepAlive(true)
|
||||
tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
||||
}
|
||||
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
defer clientconn.Close()
|
||||
|
||||
// Server hijacks the connection, error 'connection closed' expected
|
||||
clientconn.Do(req)
|
||||
|
||||
rwc, br := clientconn.Hijack()
|
||||
defer rwc.Close()
|
||||
|
||||
if started != nil {
|
||||
started <- rwc
|
||||
}
|
||||
|
||||
var oldState *term.State
|
||||
|
||||
if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" {
|
||||
oldState, err = term.SetRawTerminal(cli.inFd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer term.RestoreTerminal(cli.inFd, oldState)
|
||||
}
|
||||
|
||||
receiveStdout := make(chan error, 1)
|
||||
if stdout != nil || stderr != nil {
|
||||
go func() {
|
||||
defer func() {
|
||||
if in != nil {
|
||||
if setRawTerminal && cli.isTerminalIn {
|
||||
term.RestoreTerminal(cli.inFd, oldState)
|
||||
}
|
||||
in.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// When TTY is ON, use regular copy
|
||||
if setRawTerminal && stdout != nil {
|
||||
_, err = io.Copy(stdout, br)
|
||||
} else {
|
||||
_, err = stdcopy.StdCopy(stdout, stderr, br)
|
||||
}
|
||||
logrus.Debugf("[hijack] End of stdout")
|
||||
receiveStdout <- err
|
||||
}()
|
||||
}
|
||||
|
||||
stdinDone := make(chan struct{})
|
||||
go func() {
|
||||
if in != nil {
|
||||
io.Copy(rwc, in)
|
||||
logrus.Debugf("[hijack] End of stdin")
|
||||
}
|
||||
|
||||
if conn, ok := rwc.(interface {
|
||||
CloseWrite() error
|
||||
}); ok {
|
||||
if err := conn.CloseWrite(); err != nil {
|
||||
logrus.Debugf("Couldn't send EOF: %s", err)
|
||||
}
|
||||
}
|
||||
close(stdinDone)
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-receiveStdout:
|
||||
if err != nil {
|
||||
logrus.Debugf("Error receiveStdout: %s", err)
|
||||
return err
|
||||
}
|
||||
case <-stdinDone:
|
||||
if stdout != nil || stderr != nil {
|
||||
if err := <-receiveStdout; err != nil {
|
||||
logrus.Debugf("Error receiveStdout: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
78
api/client/history.go
Normal file
78
api/client/history.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/docker/docker/pkg/units"
|
||||
)
|
||||
|
||||
// CmdHistory shows the history of an image.
|
||||
//
|
||||
// Usage: docker history [OPTIONS] IMAGE
|
||||
func (cli *DockerCli) CmdHistory(args ...string) error {
|
||||
cmd := Cli.Subcmd("history", []string{"IMAGE"}, Cli.DockerCommands["history"].Description, true)
|
||||
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")
|
||||
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
||||
cmd.Require(flag.Exact, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
serverResp, err := cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
history := []types.ImageHistory{}
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&history); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
if !*quiet {
|
||||
fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE\tCOMMENT")
|
||||
}
|
||||
|
||||
for _, entry := range history {
|
||||
if *noTrunc {
|
||||
fmt.Fprintf(w, entry.ID)
|
||||
} else {
|
||||
fmt.Fprintf(w, stringid.TruncateID(entry.ID))
|
||||
}
|
||||
if !*quiet {
|
||||
if *human {
|
||||
fmt.Fprintf(w, "\t%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(entry.Created, 0))))
|
||||
} else {
|
||||
fmt.Fprintf(w, "\t%s\t", time.Unix(entry.Created, 0).Format(time.RFC3339))
|
||||
}
|
||||
|
||||
if *noTrunc {
|
||||
fmt.Fprintf(w, "%s\t", strings.Replace(entry.CreatedBy, "\t", " ", -1))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s\t", stringutils.Truncate(strings.Replace(entry.CreatedBy, "\t", " ", -1), 45))
|
||||
}
|
||||
|
||||
if *human {
|
||||
fmt.Fprintf(w, "%s\t", units.HumanSize(float64(entry.Size)))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%d\t", entry.Size)
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s", entry.Comment)
|
||||
}
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
w.Flush()
|
||||
return nil
|
||||
}
|
||||
130
api/client/images.go
Normal file
130
api/client/images.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"github.com/docker/docker/pkg/parsers/filters"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/units"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
// CmdImages lists the images in a specified repository, or all top-level images if no repository is specified.
|
||||
//
|
||||
// Usage: docker images [OPTIONS] [REPOSITORY]
|
||||
func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
cmd := Cli.Subcmd("images", []string{"[REPOSITORY[:TAG]]"}, Cli.DockerCommands["images"].Description, true)
|
||||
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)")
|
||||
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
||||
showDigests := cmd.Bool([]string{"-digests"}, false, "Show digests")
|
||||
|
||||
flFilter := opts.NewListOpts(nil)
|
||||
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
|
||||
cmd.Require(flag.Max, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
// Consolidate all filter flags, and sanity check them early.
|
||||
// They'll get process in the daemon/server.
|
||||
imageFilterArgs := filters.Args{}
|
||||
for _, f := range flFilter.GetAll() {
|
||||
var err error
|
||||
imageFilterArgs, err = filters.ParseFlag(f, imageFilterArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
matchName := cmd.Arg(0)
|
||||
v := url.Values{}
|
||||
if len(imageFilterArgs) > 0 {
|
||||
filterJSON, err := filters.ToParam(imageFilterArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set("filters", filterJSON)
|
||||
}
|
||||
|
||||
if cmd.NArg() == 1 {
|
||||
// FIXME rename this parameter, to not be confused with the filters flag
|
||||
v.Set("filter", matchName)
|
||||
}
|
||||
if *all {
|
||||
v.Set("all", "1")
|
||||
}
|
||||
|
||||
serverResp, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
images := []types.Image{}
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&images); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
if !*quiet {
|
||||
if *showDigests {
|
||||
fmt.Fprintln(w, "REPOSITORY\tTAG\tDIGEST\tIMAGE ID\tCREATED\tVIRTUAL SIZE")
|
||||
} else {
|
||||
fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE")
|
||||
}
|
||||
}
|
||||
|
||||
for _, image := range images {
|
||||
ID := image.ID
|
||||
if !*noTrunc {
|
||||
ID = stringid.TruncateID(ID)
|
||||
}
|
||||
|
||||
repoTags := image.RepoTags
|
||||
repoDigests := image.RepoDigests
|
||||
|
||||
if len(repoTags) == 1 && repoTags[0] == "<none>:<none>" && len(repoDigests) == 1 && repoDigests[0] == "<none>@<none>" {
|
||||
// dangling image - clear out either repoTags or repoDigsts so we only show it once below
|
||||
repoDigests = []string{}
|
||||
}
|
||||
|
||||
// combine the tags and digests lists
|
||||
tagsAndDigests := append(repoTags, repoDigests...)
|
||||
for _, repoAndRef := range tagsAndDigests {
|
||||
repo, ref := parsers.ParseRepositoryTag(repoAndRef)
|
||||
// default tag and digest to none - if there's a value, it'll be set below
|
||||
tag := "<none>"
|
||||
digest := "<none>"
|
||||
if utils.DigestReference(ref) {
|
||||
digest = ref
|
||||
} else {
|
||||
tag = ref
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
if *showDigests {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize)))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize)))
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(w, ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !*quiet {
|
||||
w.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
79
api/client/import.go
Normal file
79
api/client/import.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/opts"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
"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.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Usage: docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]
|
||||
func (cli *DockerCli) CmdImport(args ...string) error {
|
||||
cmd := Cli.Subcmd("import", []string{"file|URL|- [REPOSITORY[:TAG]]"}, Cli.DockerCommands["import"].Description, true)
|
||||
flChanges := opts.NewListOpts(nil)
|
||||
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.ParseFlags(args, true)
|
||||
|
||||
var (
|
||||
v = url.Values{}
|
||||
src = cmd.Arg(0)
|
||||
repository = cmd.Arg(1)
|
||||
)
|
||||
|
||||
v.Set("fromSrc", src)
|
||||
v.Set("repo", repository)
|
||||
v.Set("message", *message)
|
||||
for _, change := range flChanges.GetAll() {
|
||||
v.Add("changes", change)
|
||||
}
|
||||
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")
|
||||
v.Set("tag", cmd.Arg(2))
|
||||
}
|
||||
|
||||
if repository != "" {
|
||||
//Check if the given image name can be resolved
|
||||
repo, _ := parsers.ParseRepositoryTag(repository)
|
||||
if err := registry.ValidateRepositoryName(repo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var in io.Reader
|
||||
|
||||
if src == "-" {
|
||||
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{
|
||||
rawTerminal: true,
|
||||
in: in,
|
||||
out: cli.out,
|
||||
}
|
||||
|
||||
_, err := cli.stream("POST", "/images/create?"+v.Encode(), sopts)
|
||||
return err
|
||||
}
|
||||
114
api/client/info.go
Normal file
114
api/client/info.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/pkg/httputils"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/units"
|
||||
)
|
||||
|
||||
// CmdInfo displays system-wide information.
|
||||
//
|
||||
// Usage: docker info
|
||||
func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
cmd := Cli.Subcmd("info", nil, Cli.DockerCommands["info"].Description, true)
|
||||
cmd.Require(flag.Exact, 0)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
serverResp, err := cli.call("GET", "/info", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
info := &types.Info{}
|
||||
if err := json.NewDecoder(serverResp.body).Decode(info); err != nil {
|
||||
return fmt.Errorf("Error reading remote info: %v", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(cli.out, "Containers: %d\n", info.Containers)
|
||||
fmt.Fprintf(cli.out, "Images: %d\n", info.Images)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Server Version: %s\n", info.ServerVersion)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Storage Driver: %s\n", info.Driver)
|
||||
if info.DriverStatus != nil {
|
||||
for _, pair := range info.DriverStatus {
|
||||
fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1])
|
||||
}
|
||||
}
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Execution Driver: %s\n", info.ExecutionDriver)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Logging Driver: %s\n", info.LoggingDriver)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Kernel Version: %s\n", info.KernelVersion)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Operating System: %s\n", info.OperatingSystem)
|
||||
fmt.Fprintf(cli.out, "CPUs: %d\n", info.NCPU)
|
||||
fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(info.MemTotal)))
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Name: %s\n", info.Name)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "ID: %s\n", info.ID)
|
||||
|
||||
if info.Debug {
|
||||
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", info.Debug)
|
||||
fmt.Fprintf(cli.out, " File Descriptors: %d\n", info.NFd)
|
||||
fmt.Fprintf(cli.out, " Goroutines: %d\n", info.NGoroutines)
|
||||
fmt.Fprintf(cli.out, " System Time: %s\n", info.SystemTime)
|
||||
fmt.Fprintf(cli.out, " EventsListeners: %d\n", info.NEventsListener)
|
||||
fmt.Fprintf(cli.out, " Init SHA1: %s\n", info.InitSha1)
|
||||
fmt.Fprintf(cli.out, " Init Path: %s\n", info.InitPath)
|
||||
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, "Https Proxy: %s\n", info.HTTPSProxy)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "No Proxy: %s\n", info.NoProxy)
|
||||
|
||||
if info.IndexServerAddress != "" {
|
||||
u := cli.configFile.AuthConfigs[info.IndexServerAddress].Username
|
||||
if len(u) > 0 {
|
||||
fmt.Fprintf(cli.out, "Username: %v\n", u)
|
||||
fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress)
|
||||
}
|
||||
}
|
||||
|
||||
// Only output these warnings if the server does not support these features
|
||||
if h, err := httputils.ParseServerHeader(serverResp.header.Get("Server")); err == nil {
|
||||
if h.OS != "windows" {
|
||||
if !info.MemoryLimit {
|
||||
fmt.Fprintf(cli.err, "WARNING: No memory limit support\n")
|
||||
}
|
||||
if !info.SwapLimit {
|
||||
fmt.Fprintf(cli.err, "WARNING: No swap limit support\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 {
|
||||
fmt.Fprintln(cli.out, "Labels:")
|
||||
for _, attribute := range info.Labels {
|
||||
fmt.Fprintf(cli.out, " %s\n", attribute)
|
||||
}
|
||||
}
|
||||
|
||||
ioutils.FprintfIfTrue(cli.out, "Experimental: %v\n", info.ExperimentalBuild)
|
||||
if info.ClusterStore != "" {
|
||||
fmt.Fprintf(cli.out, "Cluster store: %s\n", info.ClusterStore)
|
||||
}
|
||||
|
||||
if info.ClusterAdvertise != "" {
|
||||
fmt.Fprintf(cli.out, "Cluster advertise: %s\n", info.ClusterAdvertise)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
196
api/client/inspect.go
Normal file
196
api/client/inspect.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
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.
|
||||
//
|
||||
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
|
||||
func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE [CONTAINER|IMAGE...]"}, Cli.DockerCommands["inspect"].Description, true)
|
||||
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)")
|
||||
size := cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes if the type is container")
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
var tmpl *template.Template
|
||||
var err error
|
||||
var obj []byte
|
||||
|
||||
if *tmplStr != "" {
|
||||
if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
|
||||
return Cli.StatusError{StatusCode: 64,
|
||||
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.WriteString("[\n")
|
||||
status := 0
|
||||
isImage := false
|
||||
|
||||
v := url.Values{}
|
||||
if *size {
|
||||
v.Set("size", "1")
|
||||
}
|
||||
|
||||
for _, name := range cmd.Args() {
|
||||
if *inspectType == "" || *inspectType == "container" {
|
||||
obj, _, err = readBody(cli.call("GET", "/containers/"+name+"/json?"+v.Encode(), 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))
|
||||
isImage = true
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "No such") {
|
||||
if *inspectType == "" {
|
||||
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 {
|
||||
fmt.Fprintf(cli.err, "%s", err)
|
||||
}
|
||||
status = 1
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if tmpl == nil {
|
||||
if err := json.Indent(indented, obj, "", " "); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
status = 1
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
rdr := bytes.NewReader(obj)
|
||||
dec := json.NewDecoder(rdr)
|
||||
buf := bytes.NewBufferString("")
|
||||
|
||||
if isImage {
|
||||
inspPtr := types.ImageInspect{}
|
||||
if err := dec.Decode(&inspPtr); err != nil {
|
||||
fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", err)
|
||||
status = 1
|
||||
break
|
||||
}
|
||||
if err := tmpl.Execute(buf, inspPtr); err != nil {
|
||||
rdr.Seek(0, 0)
|
||||
var ok bool
|
||||
|
||||
if buf, ok = cli.decodeRawInspect(tmpl, dec); !ok {
|
||||
fmt.Fprintf(cli.err, "Template parsing error: %v\n", err)
|
||||
status = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
inspPtr := types.ContainerJSON{}
|
||||
if err := dec.Decode(&inspPtr); err != nil {
|
||||
fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", err)
|
||||
status = 1
|
||||
break
|
||||
}
|
||||
if err := tmpl.Execute(buf, inspPtr); err != nil {
|
||||
rdr.Seek(0, 0)
|
||||
var ok bool
|
||||
|
||||
if buf, ok = cli.decodeRawInspect(tmpl, dec); !ok {
|
||||
fmt.Fprintf(cli.err, "Template parsing error: %v\n", err)
|
||||
status = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cli.out.Write(buf.Bytes())
|
||||
cli.out.Write([]byte{'\n'})
|
||||
}
|
||||
indented.WriteString(",")
|
||||
}
|
||||
|
||||
if indented.Len() > 1 {
|
||||
// Remove trailing ','
|
||||
indented.Truncate(indented.Len() - 1)
|
||||
}
|
||||
indented.WriteString("]\n")
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if status != 0 {
|
||||
return Cli.StatusError{StatusCode: status}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeRawInspect executes the inspect template with a raw interface.
|
||||
// This allows docker cli to parse inspect structs injected with Swarm fields.
|
||||
// Unfortunately, go 1.4 doesn't fail executing invalid templates when the input is an interface.
|
||||
// It doesn't allow to modify this behavior either, sending <no value> messages to the output.
|
||||
// We assume that the template is invalid when there is a <no value>, if the template was valid
|
||||
// we'd get <nil> or "" values. In that case we fail with the original error raised executing the
|
||||
// template with the typed input.
|
||||
//
|
||||
// TODO: Go 1.5 allows to customize the error behavior, we can probably get rid of this as soon as
|
||||
// we build Docker with that version:
|
||||
// https://golang.org/pkg/text/template/#Template.Option
|
||||
func (cli *DockerCli) decodeRawInspect(tmpl *template.Template, dec *json.Decoder) (*bytes.Buffer, bool) {
|
||||
var raw interface{}
|
||||
buf := bytes.NewBufferString("")
|
||||
|
||||
if rawErr := dec.Decode(&raw); rawErr != nil {
|
||||
fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", rawErr)
|
||||
return buf, false
|
||||
}
|
||||
|
||||
if rawErr := tmpl.Execute(buf, raw); rawErr != nil {
|
||||
return buf, false
|
||||
}
|
||||
|
||||
if strings.Contains(buf.String(), "<no value>") {
|
||||
return buf, false
|
||||
}
|
||||
return buf, true
|
||||
}
|
||||
33
api/client/kill.go
Normal file
33
api/client/kill.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
// CmdKill kills one or more running container using SIGKILL or a specified signal.
|
||||
//
|
||||
// Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
|
||||
func (cli *DockerCli) CmdKill(args ...string) error {
|
||||
cmd := Cli.Subcmd("kill", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["kill"].Description, true)
|
||||
signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
var errNames []string
|
||||
for _, name := range cmd.Args() {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, nil)); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
errNames = append(errNames, name)
|
||||
} else {
|
||||
fmt.Fprintf(cli.out, "%s\n", name)
|
||||
}
|
||||
}
|
||||
if len(errNames) > 0 {
|
||||
return fmt.Errorf("Error: failed to kill containers: %v", errNames)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
42
api/client/load.go
Normal file
42
api/client/load.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
// CmdLoad loads an image from a tar archive.
|
||||
//
|
||||
// The tar archive is read from STDIN by default, or from a tar archive file.
|
||||
//
|
||||
// Usage: docker load [OPTIONS]
|
||||
func (cli *DockerCli) CmdLoad(args ...string) error {
|
||||
cmd := Cli.Subcmd("load", nil, Cli.DockerCommands["load"].Description, true)
|
||||
infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN")
|
||||
cmd.Require(flag.Exact, 0)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
var (
|
||||
input io.Reader = cli.in
|
||||
err error
|
||||
)
|
||||
if *infile != "" {
|
||||
input, err = os.Open(*infile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
sopts := &streamOpts{
|
||||
rawTerminal: true,
|
||||
in: input,
|
||||
out: cli.out,
|
||||
}
|
||||
if _, err := cli.stream("POST", "/images/load", sopts); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
147
api/client/login.go
Normal file
147
api/client/login.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cliconfig"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/registry"
|
||||
)
|
||||
|
||||
// CmdLogin logs in or registers a user to a Docker registry service.
|
||||
//
|
||||
// If no server is specified, the user will be logged into or registered to the registry's index server.
|
||||
//
|
||||
// Usage: docker login SERVER
|
||||
func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||
cmd := Cli.Subcmd("login", []string{"[SERVER]"}, Cli.DockerCommands["login"].Description+".\nIf no server is specified \""+registry.IndexServer+"\" is the default.", true)
|
||||
cmd.Require(flag.Max, 1)
|
||||
|
||||
var username, password, email string
|
||||
|
||||
cmd.StringVar(&username, []string{"u", "-username"}, "", "Username")
|
||||
cmd.StringVar(&password, []string{"p", "-password"}, "", "Password")
|
||||
cmd.StringVar(&email, []string{"e", "-email"}, "", "Email")
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
serverAddress := registry.IndexServer
|
||||
if len(cmd.Args()) > 0 {
|
||||
serverAddress = cmd.Arg(0)
|
||||
}
|
||||
|
||||
promptDefault := func(prompt string, configDefault string) {
|
||||
if configDefault == "" {
|
||||
fmt.Fprintf(cli.out, "%s: ", prompt)
|
||||
} else {
|
||||
fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault)
|
||||
}
|
||||
}
|
||||
|
||||
readInput := func(in io.Reader, out io.Writer) string {
|
||||
reader := bufio.NewReader(in)
|
||||
line, _, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
fmt.Fprintln(out, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
return string(line)
|
||||
}
|
||||
|
||||
authconfig, ok := cli.configFile.AuthConfigs[serverAddress]
|
||||
if !ok {
|
||||
authconfig = cliconfig.AuthConfig{}
|
||||
}
|
||||
|
||||
if username == "" {
|
||||
promptDefault("Username", authconfig.Username)
|
||||
username = readInput(cli.in, cli.out)
|
||||
username = strings.TrimSpace(username)
|
||||
if username == "" {
|
||||
username = authconfig.Username
|
||||
}
|
||||
}
|
||||
// Assume that a different username means they may not want to use
|
||||
// the password or email from the config file, so prompt them
|
||||
if username != authconfig.Username {
|
||||
if password == "" {
|
||||
oldState, err := term.SaveState(cli.inFd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(cli.out, "Password: ")
|
||||
term.DisableEcho(cli.inFd, oldState)
|
||||
|
||||
password = readInput(cli.in, cli.out)
|
||||
fmt.Fprint(cli.out, "\n")
|
||||
|
||||
term.RestoreTerminal(cli.inFd, oldState)
|
||||
if password == "" {
|
||||
return fmt.Errorf("Error : Password Required")
|
||||
}
|
||||
}
|
||||
|
||||
if email == "" {
|
||||
promptDefault("Email", authconfig.Email)
|
||||
email = readInput(cli.in, cli.out)
|
||||
if email == "" {
|
||||
email = authconfig.Email
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// However, if they don't override the username use the
|
||||
// password or email from the cmd line if specified. IOW, allow
|
||||
// then to change/override them. And if not specified, just
|
||||
// use what's in the config file
|
||||
if password == "" {
|
||||
password = authconfig.Password
|
||||
}
|
||||
if email == "" {
|
||||
email = authconfig.Email
|
||||
}
|
||||
}
|
||||
authconfig.Username = username
|
||||
authconfig.Password = password
|
||||
authconfig.Email = email
|
||||
authconfig.ServerAddress = serverAddress
|
||||
cli.configFile.AuthConfigs[serverAddress] = authconfig
|
||||
|
||||
serverResp, err := cli.call("POST", "/auth", cli.configFile.AuthConfigs[serverAddress], nil)
|
||||
if serverResp.statusCode == 401 {
|
||||
delete(cli.configFile.AuthConfigs, serverAddress)
|
||||
if err2 := cli.configFile.Save(); err2 != nil {
|
||||
fmt.Fprintf(cli.out, "WARNING: could not save config file: %v\n", err2)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
var response types.AuthResponse
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
|
||||
// Upon error, remove entry
|
||||
delete(cli.configFile.AuthConfigs, serverAddress)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cli.configFile.Save(); err != nil {
|
||||
return fmt.Errorf("Error saving config file: %v", err)
|
||||
}
|
||||
fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s\n", cli.configFile.Filename())
|
||||
|
||||
if response.Status != "" {
|
||||
fmt.Fprintf(cli.out, "%s\n", response.Status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
39
api/client/logout.go
Normal file
39
api/client/logout.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/registry"
|
||||
)
|
||||
|
||||
// CmdLogout logs a user out from a Docker registry.
|
||||
//
|
||||
// If no server is specified, the user will be logged out from the registry's index server.
|
||||
//
|
||||
// Usage: docker logout [SERVER]
|
||||
func (cli *DockerCli) CmdLogout(args ...string) error {
|
||||
cmd := Cli.Subcmd("logout", []string{"[SERVER]"}, Cli.DockerCommands["logout"].Description+".\nIf no server is specified \""+registry.IndexServer+"\" is the default.", true)
|
||||
cmd.Require(flag.Max, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
serverAddress := registry.IndexServer
|
||||
if len(cmd.Args()) > 0 {
|
||||
serverAddress = cmd.Arg(0)
|
||||
}
|
||||
|
||||
if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok {
|
||||
fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress)
|
||||
delete(cli.configFile.AuthConfigs, serverAddress)
|
||||
if err := cli.configFile.Save(); err != nil {
|
||||
return fmt.Errorf("Failed to save docker config: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
64
api/client/logs.go
Normal file
64
api/client/logs.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/timeutils"
|
||||
)
|
||||
|
||||
// CmdLogs fetches the logs of a given container.
|
||||
//
|
||||
// docker logs [OPTIONS] CONTAINER
|
||||
func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||
cmd := Cli.Subcmd("logs", []string{"CONTAINER"}, Cli.DockerCommands["logs"].Description, true)
|
||||
follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
|
||||
since := cmd.String([]string{"-since"}, "", "Show logs since timestamp")
|
||||
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.ParseFlags(args, true)
|
||||
|
||||
name := cmd.Arg(0)
|
||||
|
||||
serverResp, err := cli.call("GET", "/containers/"+name+"/json", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var c types.ContainerJSON
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("stdout", "1")
|
||||
v.Set("stderr", "1")
|
||||
|
||||
if *since != "" {
|
||||
v.Set("since", timeutils.GetTimestamp(*since, time.Now()))
|
||||
}
|
||||
|
||||
if *times {
|
||||
v.Set("timestamps", "1")
|
||||
}
|
||||
|
||||
if *follow {
|
||||
v.Set("follow", "1")
|
||||
}
|
||||
v.Set("tail", *tail)
|
||||
|
||||
sopts := &streamOpts{
|
||||
rawTerminal: c.Config.Tty,
|
||||
out: cli.out,
|
||||
err: cli.err,
|
||||
}
|
||||
|
||||
_, err = cli.stream("GET", "/containers/"+name+"/logs?"+v.Encode(), sopts)
|
||||
return err
|
||||
}
|
||||
381
api/client/network.go
Normal file
381
api/client/network.go
Normal file
@@ -0,0 +1,381 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/daemon/network"
|
||||
"github.com/docker/docker/opts"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
)
|
||||
|
||||
// CmdNetwork is the parent subcommand for all network commands
|
||||
//
|
||||
// Usage: docker network <COMMAND> [OPTIONS]
|
||||
func (cli *DockerCli) CmdNetwork(args ...string) error {
|
||||
cmd := Cli.Subcmd("network", []string{"COMMAND [OPTIONS]"}, networkUsage(), false)
|
||||
cmd.Require(flag.Min, 1)
|
||||
err := cmd.ParseFlags(args, true)
|
||||
cmd.Usage()
|
||||
return err
|
||||
}
|
||||
|
||||
// CmdNetworkCreate creates a new network with a given name
|
||||
//
|
||||
// Usage: docker network create [OPTIONS] <NETWORK-NAME>
|
||||
func (cli *DockerCli) CmdNetworkCreate(args ...string) error {
|
||||
cmd := Cli.Subcmd("network create", []string{"NETWORK-NAME"}, "Creates a new network with a name specified by the user", false)
|
||||
flDriver := cmd.String([]string{"d", "-driver"}, "bridge", "Driver to manage the Network")
|
||||
flOpts := opts.NewMapOpts(nil, nil)
|
||||
|
||||
flIpamDriver := cmd.String([]string{"-ipam-driver"}, "default", "IP Address Management Driver")
|
||||
flIpamSubnet := opts.NewListOpts(nil)
|
||||
flIpamIPRange := opts.NewListOpts(nil)
|
||||
flIpamGateway := opts.NewListOpts(nil)
|
||||
flIpamAux := opts.NewMapOpts(nil, nil)
|
||||
|
||||
cmd.Var(&flIpamSubnet, []string{"-subnet"}, "subnet in CIDR format that represents a network segment")
|
||||
cmd.Var(&flIpamIPRange, []string{"-ip-range"}, "allocate container ip from a sub-range")
|
||||
cmd.Var(&flIpamGateway, []string{"-gateway"}, "ipv4 or ipv6 Gateway for the master subnet")
|
||||
cmd.Var(flIpamAux, []string{"-aux-address"}, "auxiliary ipv4 or ipv6 addresses used by Network driver")
|
||||
cmd.Var(flOpts, []string{"o", "-opt"}, "set driver specific options")
|
||||
|
||||
cmd.Require(flag.Exact, 1)
|
||||
err := cmd.ParseFlags(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the default driver to "" if the user didn't set the value.
|
||||
// That way we can know whether it was user input or not.
|
||||
driver := *flDriver
|
||||
if !cmd.IsSet("-driver") && !cmd.IsSet("d") {
|
||||
driver = ""
|
||||
}
|
||||
|
||||
ipamCfg, err := consolidateIpam(flIpamSubnet.GetAll(), flIpamIPRange.GetAll(), flIpamGateway.GetAll(), flIpamAux.GetAll())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Construct network create request body
|
||||
nc := types.NetworkCreate{
|
||||
Name: cmd.Arg(0),
|
||||
Driver: driver,
|
||||
IPAM: network.IPAM{Driver: *flIpamDriver, Config: ipamCfg},
|
||||
Options: flOpts.GetAll(),
|
||||
CheckDuplicate: true,
|
||||
}
|
||||
obj, _, err := readBody(cli.call("POST", "/networks/create", nc, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var resp types.NetworkCreateResponse
|
||||
err = json.Unmarshal(obj, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(cli.out, "%s\n", resp.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CmdNetworkRm deletes a network
|
||||
//
|
||||
// Usage: docker network rm <NETWORK-NAME | NETWORK-ID>
|
||||
func (cli *DockerCli) CmdNetworkRm(args ...string) error {
|
||||
cmd := Cli.Subcmd("network rm", []string{"NETWORK"}, "Deletes a network", false)
|
||||
cmd.Require(flag.Exact, 1)
|
||||
err := cmd.ParseFlags(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, err = readBody(cli.call("DELETE", "/networks/"+cmd.Arg(0), nil, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CmdNetworkConnect connects a container to a network
|
||||
//
|
||||
// Usage: docker network connect <NETWORK> <CONTAINER>
|
||||
func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
|
||||
cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
|
||||
cmd.Require(flag.Exact, 2)
|
||||
err := cmd.ParseFlags(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nc := types.NetworkConnect{Container: cmd.Arg(1)}
|
||||
_, _, err = readBody(cli.call("POST", "/networks/"+cmd.Arg(0)+"/connect", nc, nil))
|
||||
return err
|
||||
}
|
||||
|
||||
// CmdNetworkDisconnect disconnects a container from a network
|
||||
//
|
||||
// Usage: docker network disconnect <NETWORK> <CONTAINER>
|
||||
func (cli *DockerCli) CmdNetworkDisconnect(args ...string) error {
|
||||
cmd := Cli.Subcmd("network disconnect", []string{"NETWORK CONTAINER"}, "Disconnects container from a network", false)
|
||||
cmd.Require(flag.Exact, 2)
|
||||
err := cmd.ParseFlags(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nc := types.NetworkConnect{Container: cmd.Arg(1)}
|
||||
_, _, err = readBody(cli.call("POST", "/networks/"+cmd.Arg(0)+"/disconnect", nc, nil))
|
||||
return err
|
||||
}
|
||||
|
||||
// CmdNetworkLs lists all the netorks managed by docker daemon
|
||||
//
|
||||
// Usage: docker network ls [OPTIONS]
|
||||
func (cli *DockerCli) CmdNetworkLs(args ...string) error {
|
||||
cmd := Cli.Subcmd("network ls", nil, "Lists networks", true)
|
||||
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
||||
noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Do not truncate the output")
|
||||
|
||||
cmd.Require(flag.Exact, 0)
|
||||
err := cmd.ParseFlags(args, true)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj, _, err := readBody(cli.call("GET", "/networks", nil, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var networkResources []types.NetworkResource
|
||||
err = json.Unmarshal(obj, &networkResources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
|
||||
// unless quiet (-q) is specified, print field titles
|
||||
if !*quiet {
|
||||
fmt.Fprintln(wr, "NETWORK ID\tNAME\tDRIVER")
|
||||
}
|
||||
|
||||
for _, networkResource := range networkResources {
|
||||
ID := networkResource.ID
|
||||
netName := networkResource.Name
|
||||
if !*noTrunc {
|
||||
ID = stringid.TruncateID(ID)
|
||||
}
|
||||
if *quiet {
|
||||
fmt.Fprintln(wr, ID)
|
||||
continue
|
||||
}
|
||||
driver := networkResource.Driver
|
||||
fmt.Fprintf(wr, "%s\t%s\t%s\t",
|
||||
ID,
|
||||
netName,
|
||||
driver)
|
||||
fmt.Fprint(wr, "\n")
|
||||
}
|
||||
wr.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CmdNetworkInspect inspects the network object for more details
|
||||
//
|
||||
// Usage: docker network inspect [OPTIONS] <NETWORK> [NETWORK...]
|
||||
func (cli *DockerCli) CmdNetworkInspect(args ...string) error {
|
||||
cmd := Cli.Subcmd("network inspect", []string{"NETWORK [NETWORK...]"}, "Displays detailed information on a network", false)
|
||||
cmd.Require(flag.Min, 1)
|
||||
err := cmd.ParseFlags(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
status := 0
|
||||
var networks []*types.NetworkResource
|
||||
for _, name := range cmd.Args() {
|
||||
obj, _, err := readBody(cli.call("GET", "/networks/"+name, nil, nil))
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
fmt.Fprintf(cli.err, "Error: No such network: %s\n", name)
|
||||
} else {
|
||||
fmt.Fprintf(cli.err, "%s", err)
|
||||
}
|
||||
status = 1
|
||||
continue
|
||||
}
|
||||
networkResource := types.NetworkResource{}
|
||||
if err := json.NewDecoder(bytes.NewReader(obj)).Decode(&networkResource); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
networks = append(networks, &networkResource)
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(networks, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(cli.out, bytes.NewReader(b)); err != nil {
|
||||
return err
|
||||
}
|
||||
io.WriteString(cli.out, "\n")
|
||||
|
||||
if status != 0 {
|
||||
return Cli.StatusError{StatusCode: status}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Consolidates the ipam configuration as a group from differnt related configurations
|
||||
// user can configure network with multiple non-overlapping subnets and hence it is
|
||||
// possible to corelate the various related parameters and consolidate them.
|
||||
// consoidateIpam consolidates subnets, ip-ranges, gateways and auxilary addresses into
|
||||
// structured ipam data.
|
||||
func consolidateIpam(subnets, ranges, gateways []string, auxaddrs map[string]string) ([]network.IPAMConfig, error) {
|
||||
if len(subnets) < len(ranges) || len(subnets) < len(gateways) {
|
||||
return nil, fmt.Errorf("every ip-range or gateway must have a corresponding subnet")
|
||||
}
|
||||
iData := map[string]*network.IPAMConfig{}
|
||||
|
||||
// Populate non-overlapping subnets into consolidation map
|
||||
for _, s := range subnets {
|
||||
for k := range iData {
|
||||
ok1, err := subnetMatches(s, k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ok2, err := subnetMatches(k, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok1 || ok2 {
|
||||
return nil, fmt.Errorf("multiple overlapping subnet configuration is not supported")
|
||||
}
|
||||
}
|
||||
iData[s] = &network.IPAMConfig{Subnet: s, AuxAddress: map[string]string{}}
|
||||
}
|
||||
|
||||
// Validate and add valid ip ranges
|
||||
for _, r := range ranges {
|
||||
match := false
|
||||
for _, s := range subnets {
|
||||
ok, err := subnetMatches(s, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if iData[s].IPRange != "" {
|
||||
return nil, fmt.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s)
|
||||
}
|
||||
d := iData[s]
|
||||
d.IPRange = r
|
||||
match = true
|
||||
}
|
||||
if !match {
|
||||
return nil, fmt.Errorf("no matching subnet for range %s", r)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate and add valid gateways
|
||||
for _, g := range gateways {
|
||||
match := false
|
||||
for _, s := range subnets {
|
||||
ok, err := subnetMatches(s, g)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if iData[s].Gateway != "" {
|
||||
return nil, fmt.Errorf("cannot configure multiple gateways (%s, %s) for the same subnet (%s)", g, iData[s].Gateway, s)
|
||||
}
|
||||
d := iData[s]
|
||||
d.Gateway = g
|
||||
match = true
|
||||
}
|
||||
if !match {
|
||||
return nil, fmt.Errorf("no matching subnet for gateway %s", g)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate and add aux-addresses
|
||||
for key, aa := range auxaddrs {
|
||||
match := false
|
||||
for _, s := range subnets {
|
||||
ok, err := subnetMatches(s, aa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
iData[s].AuxAddress[key] = aa
|
||||
match = true
|
||||
}
|
||||
if !match {
|
||||
return nil, fmt.Errorf("no matching subnet for aux-address %s", aa)
|
||||
}
|
||||
}
|
||||
|
||||
idl := []network.IPAMConfig{}
|
||||
for _, v := range iData {
|
||||
idl = append(idl, *v)
|
||||
}
|
||||
return idl, nil
|
||||
}
|
||||
|
||||
func subnetMatches(subnet, data string) (bool, error) {
|
||||
var (
|
||||
ip net.IP
|
||||
)
|
||||
|
||||
_, s, err := net.ParseCIDR(subnet)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Invalid subnet %s : %v", s, err)
|
||||
}
|
||||
|
||||
if strings.Contains(data, "/") {
|
||||
ip, _, err = net.ParseCIDR(data)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Invalid cidr %s : %v", data, err)
|
||||
}
|
||||
} else {
|
||||
ip = net.ParseIP(data)
|
||||
}
|
||||
|
||||
return s.Contains(ip), nil
|
||||
}
|
||||
|
||||
func networkUsage() string {
|
||||
networkCommands := map[string]string{
|
||||
"create": "Create a network",
|
||||
"connect": "Connect container to a network",
|
||||
"disconnect": "Disconnect container from a network",
|
||||
"inspect": "Display detailed network information",
|
||||
"ls": "List all networks",
|
||||
"rm": "Remove a network",
|
||||
}
|
||||
|
||||
help := "Commands:\n"
|
||||
|
||||
for cmd, description := range networkCommands {
|
||||
help += fmt.Sprintf(" %-25.25s%s\n", cmd, description)
|
||||
}
|
||||
|
||||
help += fmt.Sprintf("\nRun 'docker network COMMAND --help' for more information on a command.")
|
||||
return help
|
||||
}
|
||||
32
api/client/pause.go
Normal file
32
api/client/pause.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
// CmdPause pauses all processes within one or more containers.
|
||||
//
|
||||
// Usage: docker pause CONTAINER [CONTAINER...]
|
||||
func (cli *DockerCli) CmdPause(args ...string) error {
|
||||
cmd := Cli.Subcmd("pause", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["pause"].Description, true)
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
var errNames []string
|
||||
for _, name := range cmd.Args() {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, nil)); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
errNames = append(errNames, name)
|
||||
} else {
|
||||
fmt.Fprintf(cli.out, "%s\n", name)
|
||||
}
|
||||
}
|
||||
if len(errNames) > 0 {
|
||||
return fmt.Errorf("Error: failed to pause containers: %v", errNames)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
72
api/client/port.go
Normal file
72
api/client/port.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/nat"
|
||||
)
|
||||
|
||||
// CmdPort lists port mappings for a container.
|
||||
// If a private port is specified, it also shows the public-facing port that is NATed to the private port.
|
||||
//
|
||||
// Usage: docker port CONTAINER [PRIVATE_PORT[/PROTO]]
|
||||
func (cli *DockerCli) CmdPort(args ...string) error {
|
||||
cmd := Cli.Subcmd("port", []string{"CONTAINER [PRIVATE_PORT[/PROTO]]"}, Cli.DockerCommands["port"].Description, true)
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
var c struct {
|
||||
NetworkSettings struct {
|
||||
Ports nat.PortMap
|
||||
}
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd.NArg() == 2 {
|
||||
var (
|
||||
port = cmd.Arg(1)
|
||||
proto = "tcp"
|
||||
parts = strings.SplitN(port, "/", 2)
|
||||
)
|
||||
|
||||
if len(parts) == 2 && len(parts[1]) != 0 {
|
||||
port = parts[0]
|
||||
proto = parts[1]
|
||||
}
|
||||
natPort := port + "/" + proto
|
||||
newP, err := nat.NewPort(proto, port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if frontends, exists := c.NetworkSettings.Ports[newP]; exists && frontends != nil {
|
||||
for _, frontend := range frontends {
|
||||
fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIP, frontend.HostPort)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Error: No public port '%s' published for %s", natPort, cmd.Arg(0))
|
||||
}
|
||||
|
||||
for from, frontends := range c.NetworkSettings.Ports {
|
||||
for _, frontend := range frontends {
|
||||
fmt.Fprintf(cli.out, "%s -> %s:%s\n", from, frontend.HostIP, frontend.HostPort)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
116
api/client/ps.go
Normal file
116
api/client/ps.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/docker/docker/api/client/ps"
|
||||
"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"
|
||||
)
|
||||
|
||||
// CmdPs outputs a list of Docker containers.
|
||||
//
|
||||
// Usage: docker ps [OPTIONS]
|
||||
func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
var (
|
||||
err error
|
||||
|
||||
psFilterArgs = filters.Args{}
|
||||
v = url.Values{}
|
||||
|
||||
cmd = Cli.Subcmd("ps", nil, Cli.DockerCommands["ps"].Description, true)
|
||||
quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
||||
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)")
|
||||
noTrunc = cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
||||
nLatest = cmd.Bool([]string{"l", "-latest"}, false, "Show the latest created container, 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")
|
||||
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)
|
||||
)
|
||||
cmd.Require(flag.Exact, 0)
|
||||
|
||||
cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided")
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
if *last == -1 && *nLatest {
|
||||
*last = 1
|
||||
}
|
||||
|
||||
if *all {
|
||||
v.Set("all", "1")
|
||||
}
|
||||
|
||||
if *last != -1 {
|
||||
v.Set("limit", strconv.Itoa(*last))
|
||||
}
|
||||
|
||||
if *since != "" {
|
||||
v.Set("since", *since)
|
||||
}
|
||||
|
||||
if *before != "" {
|
||||
v.Set("before", *before)
|
||||
}
|
||||
|
||||
if *size {
|
||||
v.Set("size", "1")
|
||||
}
|
||||
|
||||
// Consolidate all filter flags, and sanity check them.
|
||||
// They'll get processed in the daemon/server.
|
||||
for _, f := range flFilter.GetAll() {
|
||||
if psFilterArgs, err = filters.ParseFlag(f, psFilterArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(psFilterArgs) > 0 {
|
||||
filterJSON, err := filters.ToParam(psFilterArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.Set("filters", filterJSON)
|
||||
}
|
||||
|
||||
serverResp, err := cli.call("GET", "/containers/json?"+v.Encode(), nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
containers := []types.Container{}
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&containers); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f := *format
|
||||
if len(f) == 0 {
|
||||
if len(cli.PsFormat()) > 0 && !*quiet {
|
||||
f = cli.PsFormat()
|
||||
} else {
|
||||
f = "table"
|
||||
}
|
||||
}
|
||||
|
||||
psCtx := ps.Context{
|
||||
Output: cli.out,
|
||||
Format: f,
|
||||
Quiet: *quiet,
|
||||
Size: *size,
|
||||
Trunc: !*noTrunc,
|
||||
}
|
||||
|
||||
ps.Format(psCtx, containers)
|
||||
|
||||
return nil
|
||||
}
|
||||
160
api/client/ps/custom.go
Normal file
160
api/client/ps/custom.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package ps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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 {
|
||||
if stringid.TruncateID(c.c.ImageID) == stringid.TruncateID(c.c.Image) {
|
||||
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 stripNamePrefix(ss []string) []string {
|
||||
for i, s := range ss {
|
||||
ss[i] = s[1:]
|
||||
}
|
||||
|
||||
return ss
|
||||
}
|
||||
126
api/client/ps/custom_test.go
Normal file
126
api/client/ps/custom_test.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package ps
|
||||
|
||||
import (
|
||||
"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{ID: containerID}, false, 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: "verylongimagename"}, true, "verylongimagename", imageHeader, ctx.Image},
|
||||
{types.Container{Image: "verylongimagename"}, false, "verylongimagename", imageHeader, ctx.Image},
|
||||
{types.Container{
|
||||
Image: "a5a665ff33eced1e0803148700880edab4",
|
||||
ImageID: "a5a665ff33eced1e0803148700880edab4269067ed77e27737a708d0d293fbf5",
|
||||
},
|
||||
true,
|
||||
"a5a665ff33ec",
|
||||
imageHeader,
|
||||
ctx.Image,
|
||||
},
|
||||
{types.Container{
|
||||
Image: "a5a665ff33eced1e0803148700880edab4",
|
||||
ImageID: "a5a665ff33eced1e0803148700880edab4269067ed77e27737a708d0d293fbf5",
|
||||
},
|
||||
false,
|
||||
"a5a665ff33eced1e0803148700880edab4",
|
||||
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{}, true, "", labelsHeader, ctx.Labels},
|
||||
{types.Container{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", labelsHeader, ctx.Labels},
|
||||
{types.Container{Created: unix}, true, "Less than a second", runningForHeader, ctx.RunningFor},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
c1 := types.Container{Labels: map[string]string{"com.docker.swarm.swarm-id": "33", "com.docker.swarm.node_name": "ubuntu"}}
|
||||
ctx = containerContext{c: c1, 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)
|
||||
|
||||
}
|
||||
|
||||
c2 := types.Container{}
|
||||
ctx = containerContext{c: c2, trunc: true}
|
||||
|
||||
label := ctx.Label("anything.really")
|
||||
if label != "" {
|
||||
t.Fatalf("Expected an empty string, was %s", label)
|
||||
}
|
||||
|
||||
ctx = containerContext{c: c2, trunc: true}
|
||||
fullHeader := ctx.fullHeader()
|
||||
if fullHeader != "" {
|
||||
t.Fatalf("Expected fullHeader to be empty, was %s", fullHeader)
|
||||
}
|
||||
|
||||
}
|
||||
140
api/client/ps/formatter.go
Normal file
140
api/client/ps/formatter.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package ps
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
|
||||
"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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
208
api/client/ps/formatter_test.go
Normal file
208
api/client/ps/formatter_test.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package ps
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
func TestFormat(t *testing.T) {
|
||||
contexts := []struct {
|
||||
context Context
|
||||
expected string
|
||||
}{
|
||||
// Errors
|
||||
{
|
||||
Context{
|
||||
Format: "{{InvalidFunction}}",
|
||||
},
|
||||
`Template parsing error: template: :1: function "InvalidFunction" not defined
|
||||
`,
|
||||
},
|
||||
{
|
||||
Context{
|
||||
Format: "{{nil}}",
|
||||
},
|
||||
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
||||
`,
|
||||
},
|
||||
// Table Format
|
||||
{
|
||||
Context{
|
||||
Format: "table",
|
||||
},
|
||||
`CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
containerID1 ubuntu "" 45 years ago foobar_baz
|
||||
containerID2 ubuntu "" 45 years ago foobar_bar
|
||||
`,
|
||||
},
|
||||
{
|
||||
Context{
|
||||
Format: "table {{.Image}}",
|
||||
},
|
||||
"IMAGE\nubuntu\nubuntu\n",
|
||||
},
|
||||
{
|
||||
Context{
|
||||
Format: "table {{.Image}}",
|
||||
Size: true,
|
||||
},
|
||||
"IMAGE SIZE\nubuntu 0 B\nubuntu 0 B\n",
|
||||
},
|
||||
{
|
||||
Context{
|
||||
Format: "table {{.Image}}",
|
||||
Quiet: true,
|
||||
},
|
||||
"IMAGE\nubuntu\nubuntu\n",
|
||||
},
|
||||
{
|
||||
Context{
|
||||
Format: "table",
|
||||
Quiet: true,
|
||||
},
|
||||
"containerID1\ncontainerID2\n",
|
||||
},
|
||||
// Raw Format
|
||||
{
|
||||
Context{
|
||||
Format: "raw",
|
||||
},
|
||||
`container_id: containerID1
|
||||
image: ubuntu
|
||||
command: ""
|
||||
created_at: 1970-01-01 00:00:00 +0000 UTC
|
||||
status:
|
||||
names: foobar_baz
|
||||
labels:
|
||||
ports:
|
||||
|
||||
container_id: containerID2
|
||||
image: ubuntu
|
||||
command: ""
|
||||
created_at: 1970-01-01 00:00:00 +0000 UTC
|
||||
status:
|
||||
names: foobar_bar
|
||||
labels:
|
||||
ports:
|
||||
|
||||
`,
|
||||
},
|
||||
{
|
||||
Context{
|
||||
Format: "raw",
|
||||
Size: true,
|
||||
},
|
||||
`container_id: containerID1
|
||||
image: ubuntu
|
||||
command: ""
|
||||
created_at: 1970-01-01 00:00:00 +0000 UTC
|
||||
status:
|
||||
names: foobar_baz
|
||||
labels:
|
||||
ports:
|
||||
size: 0 B
|
||||
|
||||
container_id: containerID2
|
||||
image: ubuntu
|
||||
command: ""
|
||||
created_at: 1970-01-01 00:00:00 +0000 UTC
|
||||
status:
|
||||
names: foobar_bar
|
||||
labels:
|
||||
ports:
|
||||
size: 0 B
|
||||
|
||||
`,
|
||||
},
|
||||
{
|
||||
Context{
|
||||
Format: "raw",
|
||||
Quiet: true,
|
||||
},
|
||||
"container_id: containerID1\ncontainer_id: containerID2\n",
|
||||
},
|
||||
// Custom Format
|
||||
{
|
||||
Context{
|
||||
Format: "{{.Image}}",
|
||||
},
|
||||
"ubuntu\nubuntu\n",
|
||||
},
|
||||
{
|
||||
Context{
|
||||
Format: "{{.Image}}",
|
||||
Size: true,
|
||||
},
|
||||
"ubuntu\nubuntu\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, context := range contexts {
|
||||
containers := []types.Container{
|
||||
{ID: "containerID1", Names: []string{"/foobar_baz"}, Image: "ubuntu"},
|
||||
{ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu"},
|
||||
}
|
||||
out := bytes.NewBufferString("")
|
||||
context.context.Output = out
|
||||
Format(context.context, containers)
|
||||
actual := out.String()
|
||||
if actual != context.expected {
|
||||
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
||||
}
|
||||
// Clean buffer
|
||||
out.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomFormatNoContainers(t *testing.T) {
|
||||
out := bytes.NewBufferString("")
|
||||
containers := []types.Container{}
|
||||
|
||||
contexts := []struct {
|
||||
context Context
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
Context{
|
||||
Format: "{{.Image}}",
|
||||
Output: out,
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
Context{
|
||||
Format: "table {{.Image}}",
|
||||
Output: out,
|
||||
},
|
||||
"IMAGE\n",
|
||||
},
|
||||
{
|
||||
Context{
|
||||
Format: "{{.Image}}",
|
||||
Output: out,
|
||||
Size: true,
|
||||
},
|
||||
"",
|
||||
},
|
||||
{
|
||||
Context{
|
||||
Format: "table {{.Image}}",
|
||||
Output: out,
|
||||
Size: true,
|
||||
},
|
||||
"IMAGE SIZE\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, context := range contexts {
|
||||
customFormat(context.context, containers)
|
||||
actual := out.String()
|
||||
if actual != context.expected {
|
||||
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
||||
}
|
||||
// Clean buffer
|
||||
out.Reset()
|
||||
}
|
||||
}
|
||||
53
api/client/pull.go
Normal file
53
api/client/pull.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/graph/tags"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/registry"
|
||||
)
|
||||
|
||||
// CmdPull pulls an image or a repository from the registry.
|
||||
//
|
||||
// Usage: docker pull [OPTIONS] IMAGENAME[:TAG|@DIGEST]
|
||||
func (cli *DockerCli) CmdPull(args ...string) error {
|
||||
cmd := Cli.Subcmd("pull", []string{"NAME[:TAG|@DIGEST]"}, Cli.DockerCommands["pull"].Description, true)
|
||||
allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository")
|
||||
addTrustedFlags(cmd, true)
|
||||
cmd.Require(flag.Exact, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
remote := cmd.Arg(0)
|
||||
|
||||
taglessRemote, tag := parsers.ParseRepositoryTag(remote)
|
||||
if tag == "" && !*allTags {
|
||||
tag = tags.DefaultTag
|
||||
fmt.Fprintf(cli.out, "Using default tag: %s\n", tag)
|
||||
} else if tag != "" && *allTags {
|
||||
return fmt.Errorf("tag can't be used with --all-tags/-a")
|
||||
}
|
||||
|
||||
ref := registry.ParseReference(tag)
|
||||
|
||||
// Resolve the Repository name from fqn to RepositoryInfo
|
||||
repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)
|
||||
if err != nil {
|
||||
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")
|
||||
return err
|
||||
}
|
||||
53
api/client/push.go
Normal file
53
api/client/push.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/registry"
|
||||
)
|
||||
|
||||
// CmdPush pushes an image or repository to the registry.
|
||||
//
|
||||
// Usage: docker push NAME[:TAG]
|
||||
func (cli *DockerCli) CmdPush(args ...string) error {
|
||||
cmd := Cli.Subcmd("push", []string{"NAME[:TAG]"}, Cli.DockerCommands["push"].Description, true)
|
||||
addTrustedFlags(cmd, false)
|
||||
cmd.Require(flag.Exact, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
remote, tag := parsers.ParseRepositoryTag(cmd.Arg(0))
|
||||
|
||||
// Resolve the Repository name from fqn to RepositoryInfo
|
||||
repoInfo, err := registry.ParseRepositoryInfo(remote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Resolve the Auth config relevant for this server
|
||||
authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)
|
||||
// If we're not using a custom registry, we know the restrictions
|
||||
// applied to repository names and can warn the user in advance.
|
||||
// Custom repositories can have different rules, and we must also
|
||||
// allow pushing by image ID.
|
||||
if repoInfo.Official {
|
||||
username := authConfig.Username
|
||||
if username == "" {
|
||||
username = "<user>"
|
||||
}
|
||||
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.Set("tag", tag)
|
||||
|
||||
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, repoInfo.Index, "push")
|
||||
return err
|
||||
}
|
||||
32
api/client/rename.go
Normal file
32
api/client/rename.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
// CmdRename renames a container.
|
||||
//
|
||||
// Usage: docker rename OLD_NAME NEW_NAME
|
||||
func (cli *DockerCli) CmdRename(args ...string) error {
|
||||
cmd := Cli.Subcmd("rename", []string{"OLD_NAME NEW_NAME"}, Cli.DockerCommands["rename"].Description, true)
|
||||
cmd.Require(flag.Exact, 2)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
oldName := strings.TrimSpace(cmd.Arg(0))
|
||||
newName := strings.TrimSpace(cmd.Arg(1))
|
||||
|
||||
if oldName == "" || newName == "" {
|
||||
return fmt.Errorf("Error: Neither old nor new names may be empty")
|
||||
}
|
||||
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", oldName, newName), nil, nil)); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
return fmt.Errorf("Error: failed to rename container named %s", oldName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
39
api/client/restart.go
Normal file
39
api/client/restart.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
// CmdRestart restarts one or more containers.
|
||||
//
|
||||
// Usage: docker restart [OPTIONS] CONTAINER [CONTAINER...]
|
||||
func (cli *DockerCli) CmdRestart(args ...string) error {
|
||||
cmd := Cli.Subcmd("restart", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["restart"].Description, true)
|
||||
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container")
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("t", strconv.Itoa(*nSeconds))
|
||||
|
||||
var errNames []string
|
||||
for _, name := range cmd.Args() {
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, nil))
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
errNames = append(errNames, name)
|
||||
} else {
|
||||
fmt.Fprintf(cli.out, "%s\n", name)
|
||||
}
|
||||
}
|
||||
if len(errNames) > 0 {
|
||||
return fmt.Errorf("Error: failed to restart containers: %v", errNames)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
55
api/client/rm.go
Normal file
55
api/client/rm.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
// CmdRm removes one or more containers.
|
||||
//
|
||||
// Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...]
|
||||
func (cli *DockerCli) CmdRm(args ...string) error {
|
||||
cmd := Cli.Subcmd("rm", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["rm"].Description, true)
|
||||
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")
|
||||
force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)")
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
val := url.Values{}
|
||||
if *v {
|
||||
val.Set("v", "1")
|
||||
}
|
||||
if *link {
|
||||
val.Set("link", "1")
|
||||
}
|
||||
|
||||
if *force {
|
||||
val.Set("force", "1")
|
||||
}
|
||||
|
||||
var errNames []string
|
||||
for _, name := range cmd.Args() {
|
||||
if name == "" {
|
||||
return fmt.Errorf("Container name cannot be empty")
|
||||
}
|
||||
name = strings.Trim(name, "/")
|
||||
|
||||
_, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, nil))
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
errNames = append(errNames, name)
|
||||
} else {
|
||||
fmt.Fprintf(cli.out, "%s\n", name)
|
||||
}
|
||||
}
|
||||
if len(errNames) > 0 {
|
||||
return fmt.Errorf("Error: failed to remove containers: %v", errNames)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
61
api/client/rmi.go
Normal file
61
api/client/rmi.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
// CmdRmi removes all images with the specified name(s).
|
||||
//
|
||||
// Usage: docker rmi [OPTIONS] IMAGE [IMAGE...]
|
||||
func (cli *DockerCli) CmdRmi(args ...string) error {
|
||||
cmd := Cli.Subcmd("rmi", []string{"IMAGE [IMAGE...]"}, Cli.DockerCommands["rmi"].Description, true)
|
||||
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.ParseFlags(args, true)
|
||||
|
||||
v := url.Values{}
|
||||
if *force {
|
||||
v.Set("force", "1")
|
||||
}
|
||||
if *noprune {
|
||||
v.Set("noprune", "1")
|
||||
}
|
||||
|
||||
var errNames []string
|
||||
for _, name := range cmd.Args() {
|
||||
serverResp, err := cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
errNames = append(errNames, name)
|
||||
} else {
|
||||
defer serverResp.body.Close()
|
||||
|
||||
dels := []types.ImageDelete{}
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&dels); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
errNames = append(errNames, name)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, del := range dels {
|
||||
if del.Deleted != "" {
|
||||
fmt.Fprintf(cli.out, "Deleted: %s\n", del.Deleted)
|
||||
} else {
|
||||
fmt.Fprintf(cli.out, "Untagged: %s\n", del.Untagged)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(errNames) > 0 {
|
||||
return fmt.Errorf("Error: failed to remove images: %v", errNames)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
257
api/client/run.go
Normal file
257
api/client/run.go
Normal file
@@ -0,0 +1,257 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/libnetwork/resolvconf/dns"
|
||||
)
|
||||
|
||||
func (cid *cidFile) Close() error {
|
||||
cid.file.Close()
|
||||
|
||||
if !cid.written {
|
||||
if err := os.Remove(cid.path); err != nil {
|
||||
return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cid *cidFile) Write(id string) error {
|
||||
if _, err := cid.file.Write([]byte(id)); err != nil {
|
||||
return fmt.Errorf("Failed to write the container ID to the file: %s", err)
|
||||
}
|
||||
cid.written = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// CmdRun runs a command in a new container.
|
||||
//
|
||||
// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
|
||||
func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["run"].Description, true)
|
||||
addTrustedFlags(cmd, true)
|
||||
|
||||
// These are flags not stored in Config/HostConfig
|
||||
var (
|
||||
flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits")
|
||||
flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
|
||||
flSigProxy = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process")
|
||||
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
|
||||
flAttach *opts.ListOpts
|
||||
|
||||
ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d")
|
||||
ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
|
||||
ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
|
||||
)
|
||||
|
||||
config, hostConfig, cmd, err := runconfig.Parse(cmd, args)
|
||||
// just in case the Parse does not exit
|
||||
if err != nil {
|
||||
cmd.ReportError(err.Error(), true)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(hostConfig.DNS) > 0 {
|
||||
// check the DNS settings passed via --dns against
|
||||
// localhost regexp to warn if they are trying to
|
||||
// set a DNS to a localhost address
|
||||
for _, dnsIP := range hostConfig.DNS {
|
||||
if dns.IsLocalhost(dnsIP) {
|
||||
fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if config.Image == "" {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
if !*flDetach {
|
||||
if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if fl := cmd.Lookup("-attach"); fl != nil {
|
||||
flAttach = fl.Value.(*opts.ListOpts)
|
||||
if flAttach.Len() != 0 {
|
||||
return ErrConflictAttachDetach
|
||||
}
|
||||
}
|
||||
if *flAutoRemove {
|
||||
return ErrConflictDetachAutoRemove
|
||||
}
|
||||
|
||||
config.AttachStdin = false
|
||||
config.AttachStdout = false
|
||||
config.AttachStderr = false
|
||||
config.StdinOnce = false
|
||||
}
|
||||
|
||||
// Disable flSigProxy when in TTY mode
|
||||
sigProxy := *flSigProxy
|
||||
if config.Tty {
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sigProxy {
|
||||
sigc := cli.forwardAllSignals(createResponse.ID)
|
||||
defer signal.StopCatch(sigc)
|
||||
}
|
||||
var (
|
||||
waitDisplayID chan struct{}
|
||||
errCh chan error
|
||||
)
|
||||
if !config.AttachStdout && !config.AttachStderr {
|
||||
// Make this asynchronous to allow the client to write to stdin before having to read the ID
|
||||
waitDisplayID = make(chan struct{})
|
||||
go func() {
|
||||
defer close(waitDisplayID)
|
||||
fmt.Fprintf(cli.out, "%s\n", createResponse.ID)
|
||||
}()
|
||||
}
|
||||
if *flAutoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) {
|
||||
return ErrConflictRestartPolicyAndAutoRemove
|
||||
}
|
||||
// We need to instantiate the chan because the select needs it. It can
|
||||
// be closed but can't be uninitialized.
|
||||
hijacked := make(chan io.Closer)
|
||||
// Block the return until the chan gets closed
|
||||
defer func() {
|
||||
logrus.Debugf("End of CmdRun(), Waiting for hijack to finish.")
|
||||
if _, ok := <-hijacked; ok {
|
||||
fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)")
|
||||
}
|
||||
}()
|
||||
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
||||
var (
|
||||
out, stderr io.Writer
|
||||
in io.ReadCloser
|
||||
v = url.Values{}
|
||||
)
|
||||
v.Set("stream", "1")
|
||||
if config.AttachStdin {
|
||||
v.Set("stdin", "1")
|
||||
in = cli.in
|
||||
}
|
||||
if config.AttachStdout {
|
||||
v.Set("stdout", "1")
|
||||
out = cli.out
|
||||
}
|
||||
if config.AttachStderr {
|
||||
v.Set("stderr", "1")
|
||||
if config.Tty {
|
||||
stderr = cli.out
|
||||
} else {
|
||||
stderr = cli.err
|
||||
}
|
||||
}
|
||||
errCh = promise.Go(func() error {
|
||||
return cli.hijack("POST", "/containers/"+createResponse.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil)
|
||||
})
|
||||
} else {
|
||||
close(hijacked)
|
||||
}
|
||||
// Acknowledge the hijack before starting
|
||||
select {
|
||||
case closer := <-hijacked:
|
||||
// Make sure that the hijack gets closed when returning (results
|
||||
// in closing the hijack chan and freeing server's goroutines)
|
||||
if closer != nil {
|
||||
defer closer.Close()
|
||||
}
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
logrus.Debugf("Error hijack: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if *flAutoRemove {
|
||||
if _, _, err = readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, nil)); err != nil {
|
||||
fmt.Fprintf(cli.err, "Error deleting container: %s\n", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
//start the container
|
||||
if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
|
||||
if err := cli.monitorTtySize(createResponse.ID, false); err != nil {
|
||||
fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
if errCh != nil {
|
||||
if err := <-errCh; err != nil {
|
||||
logrus.Debugf("Error hijack: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Detached mode: wait for the id to be displayed and return.
|
||||
if !config.AttachStdout && !config.AttachStderr {
|
||||
// Detached mode
|
||||
<-waitDisplayID
|
||||
return nil
|
||||
}
|
||||
|
||||
var status int
|
||||
|
||||
// Attached mode
|
||||
if *flAutoRemove {
|
||||
// Autoremove: wait for the container to finish, retrieve
|
||||
// the exit code and remove the container
|
||||
if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// No Autoremove: Simply retrieve the exit code
|
||||
if !config.Tty {
|
||||
// In non-TTY mode, we can't detach, so we must wait for container exit
|
||||
if status, err = waitForExit(cli, createResponse.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// In TTY mode, there is a race: if the process dies too slowly, the state could
|
||||
// be updated after the getExitCode call and result in the wrong exit code being reported
|
||||
if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if status != 0 {
|
||||
return Cli.StatusError{StatusCode: status}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
52
api/client/save.go
Normal file
52
api/client/save.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
// CmdSave saves one or more images to a tar archive.
|
||||
//
|
||||
// The tar archive is written to STDOUT by default, or written to a file.
|
||||
//
|
||||
// Usage: docker save [OPTIONS] IMAGE [IMAGE...]
|
||||
func (cli *DockerCli) CmdSave(args ...string) error {
|
||||
cmd := Cli.Subcmd("save", []string{"IMAGE [IMAGE...]"}, Cli.DockerCommands["save"].Description+" (streamed to STDOUT by default)", true)
|
||||
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
var (
|
||||
output = cli.out
|
||||
err error
|
||||
)
|
||||
|
||||
if *outfile == "" && cli.isTerminalOut {
|
||||
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
||||
}
|
||||
if *outfile != "" {
|
||||
if output, err = os.Create(*outfile); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sopts := &streamOpts{
|
||||
rawTerminal: true,
|
||||
out: output,
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
for _, arg := range cmd.Args() {
|
||||
v.Add("names", arg)
|
||||
}
|
||||
if _, err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
88
api/client/search.go
Normal file
88
api/client/search.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/docker/docker/registry"
|
||||
)
|
||||
|
||||
// ByStars sorts search results in ascending order by number of stars.
|
||||
type ByStars []registry.SearchResult
|
||||
|
||||
func (r ByStars) Len() int { return len(r) }
|
||||
func (r ByStars) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||||
func (r ByStars) Less(i, j int) bool { return r[i].StarCount < r[j].StarCount }
|
||||
|
||||
// CmdSearch searches the Docker Hub for images.
|
||||
//
|
||||
// Usage: docker search [OPTIONS] TERM
|
||||
func (cli *DockerCli) CmdSearch(args ...string) error {
|
||||
cmd := Cli.Subcmd("search", []string{"TERM"}, Cli.DockerCommands["search"].Description, true)
|
||||
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
||||
trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds")
|
||||
automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds")
|
||||
stars := cmd.Uint([]string{"s", "#stars", "-stars"}, 0, "Only displays with at least x stars")
|
||||
cmd.Require(flag.Exact, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
name := cmd.Arg(0)
|
||||
v := url.Values{}
|
||||
v.Set("term", name)
|
||||
|
||||
// Resolve the Repository name from fqn to hostname + name
|
||||
taglessRemote, _ := parsers.ParseRepositoryTag(name)
|
||||
|
||||
indexInfo, err := registry.ParseIndexInfo(taglessRemote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rdr, _, err := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, indexInfo, "search")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer rdr.Close()
|
||||
|
||||
results := ByStars{}
|
||||
if err := json.NewDecoder(rdr).Decode(&results); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sort.Sort(sort.Reverse(results))
|
||||
|
||||
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
|
||||
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
|
||||
for _, res := range results {
|
||||
if (*automated && !res.IsAutomated) || (int(*stars) > res.StarCount) || (*trusted && !res.IsTrusted) {
|
||||
continue
|
||||
}
|
||||
desc := strings.Replace(res.Description, "\n", " ", -1)
|
||||
desc = strings.Replace(desc, "\r", " ", -1)
|
||||
if !*noTrunc && len(desc) > 45 {
|
||||
desc = stringutils.Truncate(desc, 42) + "..."
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount)
|
||||
if res.IsOfficial {
|
||||
fmt.Fprint(w, "[OK]")
|
||||
|
||||
}
|
||||
fmt.Fprint(w, "\t")
|
||||
if res.IsAutomated || res.IsTrusted {
|
||||
fmt.Fprint(w, "[OK]")
|
||||
}
|
||||
fmt.Fprint(w, "\n")
|
||||
}
|
||||
w.Flush()
|
||||
return nil
|
||||
}
|
||||
171
api/client/start.go
Normal file
171
api/client/start.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
)
|
||||
|
||||
func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
||||
sigc := make(chan os.Signal, 128)
|
||||
signal.CatchAll(sigc)
|
||||
go func() {
|
||||
for s := range sigc {
|
||||
if s == signal.SIGCHLD {
|
||||
continue
|
||||
}
|
||||
var sig string
|
||||
for sigStr, sigN := range signal.SignalMap {
|
||||
if sigN == s {
|
||||
sig = sigStr
|
||||
break
|
||||
}
|
||||
}
|
||||
if sig == "" {
|
||||
fmt.Fprintf(cli.err, "Unsupported signal: %v. Discarding.\n", s)
|
||||
continue
|
||||
}
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, nil)); err != nil {
|
||||
logrus.Debugf("Error sending signal: %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return sigc
|
||||
}
|
||||
|
||||
// CmdStart starts one or more containers.
|
||||
//
|
||||
// Usage: docker start [OPTIONS] CONTAINER [CONTAINER...]
|
||||
func (cli *DockerCli) CmdStart(args ...string) error {
|
||||
cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["start"].Description, 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 (
|
||||
cErr chan error
|
||||
tty bool
|
||||
)
|
||||
|
||||
if *attach || *openStdin {
|
||||
if cmd.NArg() > 1 {
|
||||
return fmt.Errorf("You cannot start and attach multiple containers at once.")
|
||||
}
|
||||
|
||||
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
var c types.ContainerJSON
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tty = c.Config.Tty
|
||||
|
||||
if !tty {
|
||||
sigc := cli.forwardAllSignals(cmd.Arg(0))
|
||||
defer signal.StopCatch(sigc)
|
||||
}
|
||||
|
||||
var in io.ReadCloser
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("stream", "1")
|
||||
|
||||
if *openStdin && c.Config.OpenStdin {
|
||||
v.Set("stdin", "1")
|
||||
in = cli.in
|
||||
}
|
||||
|
||||
v.Set("stdout", "1")
|
||||
v.Set("stderr", "1")
|
||||
|
||||
hijacked := make(chan io.Closer)
|
||||
// Block the return until the chan gets closed
|
||||
defer func() {
|
||||
logrus.Debugf("CmdStart() returned, defer waiting for hijack to finish.")
|
||||
if _, ok := <-hijacked; ok {
|
||||
fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)")
|
||||
}
|
||||
cli.in.Close()
|
||||
}()
|
||||
cErr = promise.Go(func() error {
|
||||
return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, hijacked, nil)
|
||||
})
|
||||
|
||||
// Acknowledge the hijack before starting
|
||||
select {
|
||||
case closer := <-hijacked:
|
||||
// Make sure that the hijack gets closed when returning (results
|
||||
// in closing the hijack chan and freeing server's goroutines)
|
||||
if closer != nil {
|
||||
defer closer.Close()
|
||||
}
|
||||
case err := <-cErr:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var encounteredError error
|
||||
var errNames []string
|
||||
for _, name := range cmd.Args() {
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, nil))
|
||||
if err != nil {
|
||||
if !*attach && !*openStdin {
|
||||
// attach and openStdin is false means it could be starting multiple containers
|
||||
// when a container start failed, show the error message and start next
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
errNames = append(errNames, name)
|
||||
} else {
|
||||
encounteredError = err
|
||||
}
|
||||
} else {
|
||||
if !*attach && !*openStdin {
|
||||
fmt.Fprintf(cli.out, "%s\n", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errNames) > 0 {
|
||||
encounteredError = fmt.Errorf("Error: failed to start containers: %v", errNames)
|
||||
}
|
||||
if encounteredError != nil {
|
||||
return encounteredError
|
||||
}
|
||||
|
||||
if *openStdin || *attach {
|
||||
if tty && cli.isTerminalOut {
|
||||
if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
||||
fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
|
||||
}
|
||||
}
|
||||
if attchErr := <-cErr; attchErr != nil {
|
||||
return attchErr
|
||||
}
|
||||
_, status, err := getExitCode(cli, cmd.Arg(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status != 0 {
|
||||
return Cli.StatusError{StatusCode: status}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
240
api/client/stats.go
Normal file
240
api/client/stats.go
Normal file
@@ -0,0 +1,240 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/units"
|
||||
)
|
||||
|
||||
type containerStats struct {
|
||||
Name string
|
||||
CPUPercentage float64
|
||||
Memory float64
|
||||
MemoryLimit float64
|
||||
MemoryPercentage float64
|
||||
NetworkRx float64
|
||||
NetworkTx float64
|
||||
BlockRead float64
|
||||
BlockWrite float64
|
||||
mu sync.RWMutex
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
||||
v := url.Values{}
|
||||
if streamStats {
|
||||
v.Set("stream", "1")
|
||||
} else {
|
||||
v.Set("stream", "0")
|
||||
}
|
||||
serverResp, err := cli.call("GET", "/containers/"+s.Name+"/stats?"+v.Encode(), nil, nil)
|
||||
if err != nil {
|
||||
s.mu.Lock()
|
||||
s.err = err
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
var (
|
||||
previousCPU uint64
|
||||
previousSystem uint64
|
||||
dec = json.NewDecoder(serverResp.body)
|
||||
u = make(chan error, 1)
|
||||
)
|
||||
go func() {
|
||||
for {
|
||||
var v *types.StatsJSON
|
||||
if err := dec.Decode(&v); err != nil {
|
||||
u <- err
|
||||
return
|
||||
}
|
||||
|
||||
var memPercent = 0.0
|
||||
var cpuPercent = 0.0
|
||||
|
||||
// MemoryStats.Limit will never be 0 unless the container is not running and we havn't
|
||||
// got any data from cgroup
|
||||
if v.MemoryStats.Limit != 0 {
|
||||
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
||||
}
|
||||
|
||||
previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
|
||||
previousSystem = v.PreCPUStats.SystemUsage
|
||||
cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
|
||||
blkRead, blkWrite := calculateBlockIO(v.BlkioStats)
|
||||
s.mu.Lock()
|
||||
s.CPUPercentage = cpuPercent
|
||||
s.Memory = float64(v.MemoryStats.Usage)
|
||||
s.MemoryLimit = float64(v.MemoryStats.Limit)
|
||||
s.MemoryPercentage = memPercent
|
||||
s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks)
|
||||
s.BlockRead = float64(blkRead)
|
||||
s.BlockWrite = float64(blkWrite)
|
||||
s.mu.Unlock()
|
||||
u <- nil
|
||||
if !streamStats {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-time.After(2 * time.Second):
|
||||
// zero out the values if we have not received an update within
|
||||
// the specified duration.
|
||||
s.mu.Lock()
|
||||
s.CPUPercentage = 0
|
||||
s.Memory = 0
|
||||
s.MemoryPercentage = 0
|
||||
s.MemoryLimit = 0
|
||||
s.NetworkRx = 0
|
||||
s.NetworkTx = 0
|
||||
s.BlockRead = 0
|
||||
s.BlockWrite = 0
|
||||
s.mu.Unlock()
|
||||
case err := <-u:
|
||||
if err != nil {
|
||||
s.mu.Lock()
|
||||
s.err = err
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
if !streamStats {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *containerStats) Display(w io.Writer) error {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
if s.err != nil {
|
||||
return s.err
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\n",
|
||||
s.Name,
|
||||
s.CPUPercentage,
|
||||
units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit),
|
||||
s.MemoryPercentage,
|
||||
units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx),
|
||||
units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite))
|
||||
return nil
|
||||
}
|
||||
|
||||
// CmdStats displays a live stream of resource usage statistics for one or more containers.
|
||||
//
|
||||
// This shows real-time information on CPU usage, memory usage, and network I/O.
|
||||
//
|
||||
// Usage: docker stats CONTAINER [CONTAINER...]
|
||||
func (cli *DockerCli) CmdStats(args ...string) error {
|
||||
cmd := Cli.Subcmd("stats", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["stats"].Description, true)
|
||||
noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
names := cmd.Args()
|
||||
sort.Strings(names)
|
||||
var (
|
||||
cStats []*containerStats
|
||||
w = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
)
|
||||
printHeader := func() {
|
||||
if !*noStream {
|
||||
fmt.Fprint(cli.out, "\033[2J")
|
||||
fmt.Fprint(cli.out, "\033[H")
|
||||
}
|
||||
io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\n")
|
||||
}
|
||||
for _, n := range names {
|
||||
s := &containerStats{Name: n}
|
||||
cStats = append(cStats, s)
|
||||
go s.Collect(cli, !*noStream)
|
||||
}
|
||||
// do a quick pause so that any failed connections for containers that do not exist are able to be
|
||||
// evicted before we display the initial or default values.
|
||||
time.Sleep(1500 * time.Millisecond)
|
||||
var errs []string
|
||||
for _, c := range cStats {
|
||||
c.mu.Lock()
|
||||
if c.err != nil {
|
||||
errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err))
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("%s", strings.Join(errs, ", "))
|
||||
}
|
||||
for range time.Tick(500 * time.Millisecond) {
|
||||
printHeader()
|
||||
toRemove := []int{}
|
||||
for i, s := range cStats {
|
||||
if err := s.Display(w); err != nil && !*noStream {
|
||||
toRemove = append(toRemove, i)
|
||||
}
|
||||
}
|
||||
for j := len(toRemove) - 1; j >= 0; j-- {
|
||||
i := toRemove[j]
|
||||
cStats = append(cStats[:i], cStats[i+1:]...)
|
||||
}
|
||||
if len(cStats) == 0 {
|
||||
return nil
|
||||
}
|
||||
w.Flush()
|
||||
if *noStream {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
|
||||
var (
|
||||
cpuPercent = 0.0
|
||||
// calculate the change for the cpu usage of the container in between readings
|
||||
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage - previousCPU)
|
||||
// calculate the change for the entire system between readings
|
||||
systemDelta = float64(v.CPUStats.SystemUsage - previousSystem)
|
||||
)
|
||||
|
||||
if systemDelta > 0.0 && cpuDelta > 0.0 {
|
||||
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
|
||||
var rx, tx float64
|
||||
|
||||
for _, v := range network {
|
||||
rx += float64(v.RxBytes)
|
||||
tx += float64(v.TxBytes)
|
||||
}
|
||||
return rx, tx
|
||||
}
|
||||
46
api/client/stats_unit_test.go
Normal file
46
api/client/stats_unit_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
func TestDisplay(t *testing.T) {
|
||||
c := &containerStats{
|
||||
Name: "app",
|
||||
CPUPercentage: 30.0,
|
||||
Memory: 100 * 1024 * 1024.0,
|
||||
MemoryLimit: 2048 * 1024 * 1024.0,
|
||||
MemoryPercentage: 100.0 / 2048.0 * 100.0,
|
||||
NetworkRx: 100 * 1024 * 1024,
|
||||
NetworkTx: 800 * 1024 * 1024,
|
||||
BlockRead: 100 * 1024 * 1024,
|
||||
BlockWrite: 800 * 1024 * 1024,
|
||||
mu: sync.RWMutex{},
|
||||
}
|
||||
var b bytes.Buffer
|
||||
if err := c.Display(&b); err != nil {
|
||||
t.Fatalf("c.Display() gave error: %s", err)
|
||||
}
|
||||
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"
|
||||
if 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)
|
||||
}
|
||||
}
|
||||
41
api/client/stop.go
Normal file
41
api/client/stop.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
// CmdStop stops one or more containers.
|
||||
//
|
||||
// A running container is stopped by first sending SIGTERM and then SIGKILL if the container fails to stop within a grace period (the default is 10 seconds).
|
||||
//
|
||||
// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
|
||||
func (cli *DockerCli) CmdStop(args ...string) error {
|
||||
cmd := Cli.Subcmd("stop", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["stop"].Description+".\nSending SIGTERM and then SIGKILL after a grace period", true)
|
||||
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it")
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("t", strconv.Itoa(*nSeconds))
|
||||
|
||||
var errNames []string
|
||||
for _, name := range cmd.Args() {
|
||||
_, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, nil))
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
errNames = append(errNames, name)
|
||||
} else {
|
||||
fmt.Fprintf(cli.out, "%s\n", name)
|
||||
}
|
||||
}
|
||||
if len(errNames) > 0 {
|
||||
return fmt.Errorf("Error: failed to stop containers: %v", errNames)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
42
api/client/tag.go
Normal file
42
api/client/tag.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/registry"
|
||||
)
|
||||
|
||||
// CmdTag tags an image into a repository.
|
||||
//
|
||||
// Usage: docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
|
||||
func (cli *DockerCli) CmdTag(args ...string) error {
|
||||
cmd := Cli.Subcmd("tag", []string{"IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]"}, Cli.DockerCommands["tag"].Description, true)
|
||||
force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force")
|
||||
cmd.Require(flag.Exact, 2)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
var (
|
||||
repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1))
|
||||
v = url.Values{}
|
||||
)
|
||||
|
||||
//Check if the given image name can be resolved
|
||||
if err := registry.ValidateRepositoryName(repository); err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set("repo", repository)
|
||||
v.Set("tag", tag)
|
||||
|
||||
if *force {
|
||||
v.Set("force", "1")
|
||||
}
|
||||
|
||||
if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
49
api/client/top.go
Normal file
49
api/client/top.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
// CmdTop displays the running processes of a container.
|
||||
//
|
||||
// Usage: docker top CONTAINER
|
||||
func (cli *DockerCli) CmdTop(args ...string) error {
|
||||
cmd := Cli.Subcmd("top", []string{"CONTAINER [ps OPTIONS]"}, Cli.DockerCommands["top"].Description, true)
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
val := url.Values{}
|
||||
if cmd.NArg() > 1 {
|
||||
val.Set("ps_args", strings.Join(cmd.Args()[1:], " "))
|
||||
}
|
||||
|
||||
serverResp, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
procList := types.ContainerProcessList{}
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&procList); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
fmt.Fprintln(w, strings.Join(procList.Titles, "\t"))
|
||||
|
||||
for _, proc := range procList.Processes {
|
||||
fmt.Fprintln(w, strings.Join(proc, "\t"))
|
||||
}
|
||||
w.Flush()
|
||||
return nil
|
||||
}
|
||||
479
api/client/trust.go
Normal file
479
api/client/trust.go
Normal file
@@ -0,0 +1,479 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"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, error) {
|
||||
if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
|
||||
urlObj, err := url.Parse(s)
|
||||
if err != nil || urlObj.Scheme != "https" {
|
||||
return "", fmt.Errorf("valid https URL required for trust server, got %s", s)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
if index.Official {
|
||||
return registry.NotaryServer, nil
|
||||
}
|
||||
return "https://" + index.Name, nil
|
||||
}
|
||||
|
||||
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, err := trustServer(repoInfo.Index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
challengeManager := auth.NewSimpleChallengeManager()
|
||||
|
||||
resp, err := pingClient.Do(req)
|
||||
if err != nil {
|
||||
// Ignore error on ping to operate in offline mode
|
||||
logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err)
|
||||
} else {
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Add response to the challenge manager to parse out
|
||||
// authentication header and register authentication method
|
||||
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": "root",
|
||||
"snapshot": "repository",
|
||||
"targets": "repository",
|
||||
}
|
||||
baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap)
|
||||
env := map[string]string{
|
||||
"root": os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
|
||||
"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
|
||||
"targets": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
|
||||
}
|
||||
|
||||
// Backwards compatibility with old env names. We should remove this in 1.10
|
||||
if env["root"] == "" {
|
||||
if passphrase := os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE"); passphrase != "" {
|
||||
env["root"] = passphrase
|
||||
fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE\n")
|
||||
}
|
||||
}
|
||||
if env["snapshot"] == "" || env["targets"] == "" {
|
||||
if passphrase := os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"); passphrase != "" {
|
||||
env["snapshot"] = passphrase
|
||||
env["targets"] = passphrase
|
||||
fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE\n")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
case *net.OpError:
|
||||
return fmt.Errorf("error contacting notary server: %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())
|
||||
}
|
||||
55
api/client/trust_test.go
Normal file
55
api/client/trust_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/registry"
|
||||
)
|
||||
|
||||
func unsetENV() {
|
||||
os.Unsetenv("DOCKER_CONTENT_TRUST")
|
||||
os.Unsetenv("DOCKER_CONTENT_TRUST_SERVER")
|
||||
}
|
||||
|
||||
func TestENVTrustServer(t *testing.T) {
|
||||
defer unsetENV()
|
||||
indexInfo := ®istry.IndexInfo{Name: "testserver"}
|
||||
if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "https://notary-test.com:5000"); err != nil {
|
||||
t.Fatal("Failed to set ENV variable")
|
||||
}
|
||||
output, err := trustServer(indexInfo)
|
||||
expectedStr := "https://notary-test.com:5000"
|
||||
if err != nil || output != expectedStr {
|
||||
t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPENVTrustServer(t *testing.T) {
|
||||
defer unsetENV()
|
||||
indexInfo := ®istry.IndexInfo{Name: "testserver"}
|
||||
if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "http://notary-test.com:5000"); err != nil {
|
||||
t.Fatal("Failed to set ENV variable")
|
||||
}
|
||||
_, err := trustServer(indexInfo)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error with invalid scheme")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOfficialTrustServer(t *testing.T) {
|
||||
indexInfo := ®istry.IndexInfo{Name: "testserver", Official: true}
|
||||
output, err := trustServer(indexInfo)
|
||||
if err != nil || output != registry.NotaryServer {
|
||||
t.Fatalf("Expected server to be %s, got %s", registry.NotaryServer, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonOfficialTrustServer(t *testing.T) {
|
||||
indexInfo := ®istry.IndexInfo{Name: "testserver", Official: false}
|
||||
output, err := trustServer(indexInfo)
|
||||
expectedStr := "https://" + indexInfo.Name
|
||||
if err != nil || output != expectedStr {
|
||||
t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
|
||||
}
|
||||
}
|
||||
32
api/client/unpause.go
Normal file
32
api/client/unpause.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
// CmdUnpause unpauses all processes within a container, for one or more containers.
|
||||
//
|
||||
// Usage: docker unpause CONTAINER [CONTAINER...]
|
||||
func (cli *DockerCli) CmdUnpause(args ...string) error {
|
||||
cmd := Cli.Subcmd("unpause", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["unpause"].Description, true)
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
var errNames []string
|
||||
for _, name := range cmd.Args() {
|
||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, nil)); err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
errNames = append(errNames, name)
|
||||
} else {
|
||||
fmt.Fprintf(cli.out, "%s\n", name)
|
||||
}
|
||||
}
|
||||
if len(errNames) > 0 {
|
||||
return fmt.Errorf("Error: failed to unpause containers: %v", errNames)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
384
api/client/utils.go
Normal file
384
api/client/utils.go
Normal file
@@ -0,0 +1,384 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
gosignal "os/signal"
|
||||
"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/cliconfig"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
errConnectionFailed = errors.New("Cannot connect to the Docker daemon. Is the docker daemon running on this host?")
|
||||
)
|
||||
|
||||
type serverResponse struct {
|
||||
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 {
|
||||
return &http.Client{Transport: cli.transport}
|
||||
}
|
||||
|
||||
func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
|
||||
params := bytes.NewBuffer(nil)
|
||||
if data != nil {
|
||||
if err := json.NewEncoder(params).Encode(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (*serverResponse, error) {
|
||||
|
||||
serverResp := &serverResponse{
|
||||
body: nil,
|
||||
statusCode: -1,
|
||||
}
|
||||
|
||||
expectedPayload := (method == "POST" || method == "PUT")
|
||||
if expectedPayload && in == nil {
|
||||
in = bytes.NewReader([]byte{})
|
||||
}
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), in)
|
||||
if err != nil {
|
||||
return serverResp, err
|
||||
}
|
||||
|
||||
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
|
||||
// then the user can't change OUR headers
|
||||
for k, v := range cli.configFile.HTTPHeaders {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
|
||||
req.URL.Host = cli.addr
|
||||
req.URL.Scheme = cli.scheme
|
||||
|
||||
if headers != nil {
|
||||
for k, v := range headers {
|
||||
req.Header[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if expectedPayload && req.Header.Get("Content-Type") == "" {
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
}
|
||||
|
||||
resp, err := cli.HTTPClient().Do(req)
|
||||
if resp != nil {
|
||||
serverResp.statusCode = resp.StatusCode
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if utils.IsTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") {
|
||||
return serverResp, errConnectionFailed
|
||||
}
|
||||
|
||||
if cli.tlsConfig == nil && strings.Contains(err.Error(), "malformed HTTP response") {
|
||||
return serverResp, fmt.Errorf("%v.\n* 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 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 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return serverResp, err
|
||||
}
|
||||
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 serverResp, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
||||
}
|
||||
|
||||
serverResp.body = resp.Body
|
||||
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) {
|
||||
|
||||
// Resolve the Auth config relevant for this server
|
||||
authConfig := registry.ResolveAuthConfig(cli.configFile, index)
|
||||
body, statusCode, err := cli.cmdAttempt(authConfig, method, path, in, out)
|
||||
if statusCode == http.StatusUnauthorized {
|
||||
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
|
||||
if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
authConfig = registry.ResolveAuthConfig(cli.configFile, index)
|
||||
return cli.cmdAttempt(authConfig, method, path, in, out)
|
||||
}
|
||||
return body, statusCode, err
|
||||
}
|
||||
|
||||
func (cli *DockerCli) callWrapper(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, 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)
|
||||
if err != nil {
|
||||
sr := &serverResponse{
|
||||
body: nil,
|
||||
header: nil,
|
||||
statusCode: -1,
|
||||
}
|
||||
return sr, nil
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
if headers == nil {
|
||||
headers = make(map[string][]string)
|
||||
}
|
||||
headers["Content-Type"] = []string{"application/json"}
|
||||
}
|
||||
|
||||
serverResp, err := cli.clientRequest(method, path, params, headers)
|
||||
return serverResp, err
|
||||
}
|
||||
|
||||
type streamOpts struct {
|
||||
rawTerminal bool
|
||||
in io.Reader
|
||||
out io.Writer
|
||||
err io.Writer
|
||||
headers map[string][]string
|
||||
}
|
||||
|
||||
func (cli *DockerCli) stream(method, path string, opts *streamOpts) (*serverResponse, error) {
|
||||
serverResp, err := cli.clientRequest(method, path, opts.in, opts.headers)
|
||||
if err != nil {
|
||||
return serverResp, err
|
||||
}
|
||||
return serverResp, cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), opts.rawTerminal, opts.out, opts.err)
|
||||
}
|
||||
|
||||
func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, rawTerminal bool, stdout, stderr io.Writer) error {
|
||||
defer body.Close()
|
||||
|
||||
if api.MatchesContentType(contentType, "application/json") {
|
||||
return jsonmessage.DisplayJSONMessagesStream(body, stdout, cli.outFd, cli.isTerminalOut)
|
||||
}
|
||||
if stdout != nil || stderr != nil {
|
||||
// When TTY is ON, use regular copy
|
||||
var err error
|
||||
if rawTerminal {
|
||||
_, err = io.Copy(stdout, body)
|
||||
} else {
|
||||
_, err = stdcopy.StdCopy(stdout, stderr, body)
|
||||
}
|
||||
logrus.Debugf("[stream] End of stdout")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) resizeTty(id string, isExec bool) {
|
||||
height, width := cli.getTtySize()
|
||||
if height == 0 && width == 0 {
|
||||
return
|
||||
}
|
||||
v := url.Values{}
|
||||
v.Set("h", strconv.Itoa(height))
|
||||
v.Set("w", strconv.Itoa(width))
|
||||
|
||||
path := ""
|
||||
if !isExec {
|
||||
path = "/containers/" + id + "/resize?"
|
||||
} else {
|
||||
path = "/exec/" + id + "/resize?"
|
||||
}
|
||||
|
||||
if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, nil)); err != nil {
|
||||
logrus.Debugf("Error resize: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func waitForExit(cli *DockerCli, containerID string) (int, error) {
|
||||
serverResp, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, nil)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
var res types.ContainerWaitResponse
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&res); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return res.StatusCode, nil
|
||||
}
|
||||
|
||||
// getExitCode perform an inspect on the container. It returns
|
||||
// the running state and the exit code.
|
||||
func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
|
||||
serverResp, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil)
|
||||
if err != nil {
|
||||
// If we can't connect, then the daemon probably died.
|
||||
if err != errConnectionFailed {
|
||||
return false, -1, err
|
||||
}
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
var c types.ContainerJSON
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
||||
return false, -1, err
|
||||
}
|
||||
|
||||
return c.State.Running, c.State.ExitCode, nil
|
||||
}
|
||||
|
||||
// getExecExitCode perform an inspect on the exec command. It returns
|
||||
// the running state and the exit code.
|
||||
func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
|
||||
serverResp, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil)
|
||||
if err != nil {
|
||||
// If we can't connect, then the daemon probably died.
|
||||
if err != errConnectionFailed {
|
||||
return false, -1, err
|
||||
}
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
//TODO: Should we reconsider having a type in api/types?
|
||||
//this is a response to exex/id/json not container
|
||||
var c struct {
|
||||
Running bool
|
||||
ExitCode int
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
||||
return false, -1, err
|
||||
}
|
||||
|
||||
return c.Running, c.ExitCode, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
||||
cli.resizeTty(id, isExec)
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
go func() {
|
||||
prevH, prevW := cli.getTtySize()
|
||||
for {
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
h, w := cli.getTtySize()
|
||||
|
||||
if prevW != w || prevH != h {
|
||||
cli.resizeTty(id, isExec)
|
||||
}
|
||||
prevH = h
|
||||
prevW = w
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
gosignal.Notify(sigchan, signal.SIGWINCH)
|
||||
go func() {
|
||||
for range sigchan {
|
||||
cli.resizeTty(id, isExec)
|
||||
}
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) getTtySize() (int, int) {
|
||||
if !cli.isTerminalOut {
|
||||
return 0, 0
|
||||
}
|
||||
ws, err := term.GetWinsize(cli.outFd)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error getting size: %s", err)
|
||||
if ws == nil {
|
||||
return 0, 0
|
||||
}
|
||||
}
|
||||
return int(ws.Height), int(ws.Width)
|
||||
}
|
||||
|
||||
func readBody(serverResp *serverResponse, err error) ([]byte, int, error) {
|
||||
if serverResp.body != nil {
|
||||
defer serverResp.body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, serverResp.statusCode, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(serverResp.body)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
return body, serverResp.statusCode, nil
|
||||
}
|
||||
96
api/client/version.go
Normal file
96
api/client/version.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"runtime"
|
||||
"text/template"
|
||||
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/autogen/dockerversion"
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"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.
|
||||
//
|
||||
// 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
|
||||
func (cli *DockerCli) CmdVersion(args ...string) (err error) {
|
||||
cmd := Cli.Subcmd("version", nil, Cli.DockerCommands["version"].Description, true)
|
||||
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
|
||||
cmd.Require(flag.Exact, 0)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
if *tmplStr == "" {
|
||||
*tmplStr = versionTemplate
|
||||
}
|
||||
|
||||
var tmpl *template.Template
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
defer serverResp.body.Close()
|
||||
|
||||
if err = json.NewDecoder(serverResp.body).Decode(&vd.Server); err != nil {
|
||||
return Cli.StatusError{StatusCode: 1,
|
||||
Status: "Error reading remote version: " + err.Error()}
|
||||
}
|
||||
|
||||
vd.ServerOK = true
|
||||
|
||||
return
|
||||
}
|
||||
234
api/client/volume.go
Normal file
234
api/client/volume.go
Normal file
@@ -0,0 +1,234 @@
|
||||
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 := Cli.DockerCommands["volume"].Description + "\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, false)
|
||||
|
||||
cmd.Require(flag.Exact, 0)
|
||||
err := cmd.ParseFlags(args, true)
|
||||
cmd.Usage()
|
||||
return err
|
||||
}
|
||||
|
||||
// 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 [VOLUME...]"}, "Return low-level information on a volume", true)
|
||||
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
|
||||
|
||||
cmd.Require(flag.Min, 1)
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
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/create", 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{"VOLUME [VOLUME...]"}, "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
|
||||
}
|
||||
35
api/client/wait.go
Normal file
35
api/client/wait.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
// CmdWait blocks until a container stops, then prints its exit code.
|
||||
//
|
||||
// If more than one container is specified, this will wait synchronously on each container.
|
||||
//
|
||||
// Usage: docker wait CONTAINER [CONTAINER...]
|
||||
func (cli *DockerCli) CmdWait(args ...string) error {
|
||||
cmd := Cli.Subcmd("wait", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["wait"].Description, true)
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
var errNames []string
|
||||
for _, name := range cmd.Args() {
|
||||
status, err := waitForExit(cli, name)
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
errNames = append(errNames, name)
|
||||
} else {
|
||||
fmt.Fprintf(cli.out, "%d\n", status)
|
||||
}
|
||||
}
|
||||
if len(errNames) > 0 {
|
||||
return fmt.Errorf("Error: failed to wait containers: %v", errNames)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
128
api/common.go
128
api/common.go
@@ -1,11 +1,127 @@
|
||||
package api // import "github.com/docker/docker/api"
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/pkg/version"
|
||||
"github.com/docker/libtrust"
|
||||
)
|
||||
|
||||
// Common constants for daemon and client.
|
||||
const (
|
||||
// DefaultVersion of Current REST API
|
||||
DefaultVersion = "1.41"
|
||||
// Version of Current REST API
|
||||
Version version.Version = "1.21"
|
||||
|
||||
// NoBaseImageSpecifier is the symbol used by the FROM
|
||||
// command to specify that no base image is to be used.
|
||||
NoBaseImageSpecifier = "scratch"
|
||||
// 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
|
||||
|
||||
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) 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 {
|
||||
type portGroup struct {
|
||||
first int
|
||||
last int
|
||||
}
|
||||
groupMap := make(map[string]*portGroup)
|
||||
var result []string
|
||||
var hostMappings []string
|
||||
sort.Sort(byPrivatePort(ports))
|
||||
for _, port := range ports {
|
||||
current := port.PrivatePort
|
||||
portKey := port.Type
|
||||
if port.IP != "" {
|
||||
if port.PublicPort != current {
|
||||
hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
|
||||
continue
|
||||
}
|
||||
portKey = fmt.Sprintf("%s/%s", port.IP, port.Type)
|
||||
}
|
||||
group := groupMap[portKey]
|
||||
|
||||
if group == nil {
|
||||
groupMap[portKey] = &portGroup{first: current, last: current}
|
||||
continue
|
||||
}
|
||||
if current == (group.last + 1) {
|
||||
group.last = current
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, formGroup(portKey, group.first, group.last))
|
||||
groupMap[portKey] = &portGroup{first: current, last: current}
|
||||
}
|
||||
for portKey, g := range groupMap {
|
||||
result = append(result, formGroup(portKey, g.first, g.last))
|
||||
}
|
||||
result = append(result, hostMappings...)
|
||||
return strings.Join(result, ", ")
|
||||
}
|
||||
|
||||
func formGroup(key string, start, last int) string {
|
||||
parts := strings.Split(key, "/")
|
||||
groupType := parts[0]
|
||||
var ip string
|
||||
if len(parts) > 1 {
|
||||
ip = parts[0]
|
||||
groupType = parts[1]
|
||||
}
|
||||
group := strconv.Itoa(start)
|
||||
if start != last {
|
||||
group = fmt.Sprintf("%s-%d", group, last)
|
||||
}
|
||||
if ip != "" {
|
||||
group = fmt.Sprintf("%s:%s->%s", ip, group, group)
|
||||
}
|
||||
return fmt.Sprintf("%s/%s", group, groupType)
|
||||
}
|
||||
|
||||
// MatchesContentType validates the content type against the expected one
|
||||
func MatchesContentType(contentType, expectedType string) bool {
|
||||
mimetype, _, err := mime.ParseMediaType(contentType)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error parsing media type: %s error: %v", contentType, err)
|
||||
}
|
||||
return err == nil && mimetype == expectedType
|
||||
}
|
||||
|
||||
// LoadOrCreateTrustKey attempts to load the libtrust key at the given path,
|
||||
// otherwise generates a new one
|
||||
func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
|
||||
err := system.MkdirAll(filepath.Dir(trustKeyPath), 0700)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trustKey, err := libtrust.LoadKeyFile(trustKeyPath)
|
||||
if err == libtrust.ErrKeyFileDoesNotExist {
|
||||
trustKey, err = libtrust.GenerateECP256PrivateKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error generating key: %s", err)
|
||||
}
|
||||
if err := libtrust.SaveKey(trustKeyPath, trustKey); err != nil {
|
||||
return nil, fmt.Errorf("Error saving key file: %s", err)
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("Error loading key file %s: %s", trustKeyPath, err)
|
||||
}
|
||||
return trustKey, nil
|
||||
}
|
||||
|
||||
282
api/common_test.go
Normal file
282
api/common_test.go
Normal file
@@ -0,0 +1,282 @@
|
||||
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 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package api // import "github.com/docker/docker/api"
|
||||
|
||||
// MinVersion represents Minimum REST API version supported
|
||||
const MinVersion = "1.12"
|
||||
@@ -1,8 +0,0 @@
|
||||
package api // import "github.com/docker/docker/api"
|
||||
|
||||
// MinVersion represents Minimum REST API version supported
|
||||
// Technically the first daemon API version released on Windows is v1.25 in
|
||||
// engine version 1.13. However, some clients are explicitly using downlevel
|
||||
// APIs (e.g. docker-compose v2.1 file format) and that is just too restrictive.
|
||||
// Hence also allowing 1.24 on Windows.
|
||||
const MinVersion string = "1.24"
|
||||
@@ -1,130 +0,0 @@
|
||||
package build // import "github.com/docker/docker/api/server/backend/build"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/builder"
|
||||
buildkit "github.com/docker/docker/builder/builder-next"
|
||||
daemonevents "github.com/docker/docker/daemon/events"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// ImageComponent provides an interface for working with images
|
||||
type ImageComponent interface {
|
||||
SquashImage(from string, to string) (string, error)
|
||||
TagImageWithReference(image.ID, reference.Named) error
|
||||
}
|
||||
|
||||
// Builder defines interface for running a build
|
||||
type Builder interface {
|
||||
Build(context.Context, backend.BuildConfig) (*builder.Result, error)
|
||||
}
|
||||
|
||||
// Backend provides build functionality to the API router
|
||||
type Backend struct {
|
||||
builder Builder
|
||||
imageComponent ImageComponent
|
||||
buildkit *buildkit.Builder
|
||||
eventsService *daemonevents.Events
|
||||
}
|
||||
|
||||
// NewBackend creates a new build backend from components
|
||||
func NewBackend(components ImageComponent, builder Builder, buildkit *buildkit.Builder, es *daemonevents.Events) (*Backend, error) {
|
||||
return &Backend{imageComponent: components, builder: builder, buildkit: buildkit, eventsService: es}, nil
|
||||
}
|
||||
|
||||
// RegisterGRPC registers buildkit controller to the grpc server.
|
||||
func (b *Backend) RegisterGRPC(s *grpc.Server) {
|
||||
if b.buildkit != nil {
|
||||
b.buildkit.RegisterGRPC(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Build builds an image from a Source
|
||||
func (b *Backend) Build(ctx context.Context, config backend.BuildConfig) (string, error) {
|
||||
options := config.Options
|
||||
useBuildKit := options.Version == types.BuilderBuildKit
|
||||
|
||||
tagger, err := NewTagger(b.imageComponent, config.ProgressWriter.StdoutFormatter, options.Tags)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var build *builder.Result
|
||||
if useBuildKit {
|
||||
build, err = b.buildkit.Build(ctx, config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
build, err = b.builder.Build(ctx, config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if build == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var imageID = build.ImageID
|
||||
if options.Squash {
|
||||
if imageID, err = squashBuild(build, b.imageComponent); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if config.ProgressWriter.AuxFormatter != nil {
|
||||
if err = config.ProgressWriter.AuxFormatter.Emit("moby.image.id", types.BuildResult{ID: imageID}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !useBuildKit {
|
||||
stdout := config.ProgressWriter.StdoutFormatter
|
||||
fmt.Fprintf(stdout, "Successfully built %s\n", stringid.TruncateID(imageID))
|
||||
}
|
||||
if imageID != "" {
|
||||
err = tagger.TagImages(image.ID(imageID))
|
||||
}
|
||||
return imageID, err
|
||||
}
|
||||
|
||||
// PruneCache removes all cached build sources
|
||||
func (b *Backend) PruneCache(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) {
|
||||
buildCacheSize, cacheIDs, err := b.buildkit.Prune(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to prune build cache")
|
||||
}
|
||||
b.eventsService.Log("prune", events.BuilderEventType, events.Actor{
|
||||
Attributes: map[string]string{
|
||||
"reclaimed": strconv.FormatInt(buildCacheSize, 10),
|
||||
},
|
||||
})
|
||||
return &types.BuildCachePruneReport{SpaceReclaimed: uint64(buildCacheSize), CachesDeleted: cacheIDs}, nil
|
||||
}
|
||||
|
||||
// Cancel cancels the build by ID
|
||||
func (b *Backend) Cancel(ctx context.Context, id string) error {
|
||||
return b.buildkit.Cancel(ctx, id)
|
||||
}
|
||||
|
||||
func squashBuild(build *builder.Result, imageComponent ImageComponent) (string, error) {
|
||||
var fromID string
|
||||
if build.FromImage != nil {
|
||||
fromID = build.FromImage.ImageID()
|
||||
}
|
||||
imageID, err := imageComponent.SquashImage(build.ImageID, fromID)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error squashing image")
|
||||
}
|
||||
return imageID, nil
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package build // import "github.com/docker/docker/api/server/backend/build"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Tagger is responsible for tagging an image created by a builder
|
||||
type Tagger struct {
|
||||
imageComponent ImageComponent
|
||||
stdout io.Writer
|
||||
repoAndTags []reference.Named
|
||||
}
|
||||
|
||||
// NewTagger returns a new Tagger for tagging the images of a build.
|
||||
// If any of the names are invalid tags an error is returned.
|
||||
func NewTagger(backend ImageComponent, stdout io.Writer, names []string) (*Tagger, error) {
|
||||
reposAndTags, err := sanitizeRepoAndTags(names)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Tagger{
|
||||
imageComponent: backend,
|
||||
stdout: stdout,
|
||||
repoAndTags: reposAndTags,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TagImages creates image tags for the imageID
|
||||
func (bt *Tagger) TagImages(imageID image.ID) error {
|
||||
for _, rt := range bt.repoAndTags {
|
||||
if err := bt.imageComponent.TagImageWithReference(imageID, rt); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(bt.stdout, "Successfully tagged %s\n", reference.FamiliarString(rt))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sanitizeRepoAndTags parses the raw "t" parameter received from the client
|
||||
// to a slice of repoAndTag.
|
||||
// It also validates each repoName and tag.
|
||||
func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
|
||||
var (
|
||||
repoAndTags []reference.Named
|
||||
// This map is used for deduplicating the "-t" parameter.
|
||||
uniqNames = make(map[string]struct{})
|
||||
)
|
||||
for _, repo := range names {
|
||||
if repo == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
ref, err := reference.ParseNormalizedNamed(repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, isCanonical := ref.(reference.Canonical); isCanonical {
|
||||
return nil, errors.New("build tag cannot contain a digest")
|
||||
}
|
||||
|
||||
ref = reference.TagNameOnly(ref)
|
||||
|
||||
nameWithTag := ref.String()
|
||||
|
||||
if _, exists := uniqNames[nameWithTag]; !exists {
|
||||
uniqNames[nameWithTag] = struct{}{}
|
||||
repoAndTags = append(repoAndTags, ref)
|
||||
}
|
||||
}
|
||||
return repoAndTags, nil
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/server/httpstatus"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/gorilla/mux"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// makeErrorHandler makes an HTTP handler that decodes a Docker error and
|
||||
// returns it in the response.
|
||||
func makeErrorHandler(err error) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
statusCode := httpstatus.FromError(err)
|
||||
vars := mux.Vars(r)
|
||||
if apiVersionSupportsJSONErrors(vars["version"]) {
|
||||
response := &types.ErrorResponse{
|
||||
Message: err.Error(),
|
||||
}
|
||||
_ = httputils.WriteJSON(w, statusCode, response)
|
||||
} else {
|
||||
http.Error(w, status.Convert(err).Message(), statusCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func apiVersionSupportsJSONErrors(version string) bool {
|
||||
const firstAPIVersionWithJSONErrors = "1.23"
|
||||
return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors)
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
package httpstatus // import "github.com/docker/docker/api/server/httpstatus"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
containerderrors "github.com/containerd/containerd/errdefs"
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
// FromError retrieves status code from error message.
|
||||
func FromError(err error) int {
|
||||
if err == nil {
|
||||
logrus.WithFields(logrus.Fields{"error": err}).Error("unexpected HTTP error handling")
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
|
||||
var statusCode int
|
||||
|
||||
// Stop right there
|
||||
// Are you sure you should be adding a new error class here? Do one of the existing ones work?
|
||||
|
||||
// Note that the below functions are already checking the error causal chain for matches.
|
||||
switch {
|
||||
case errdefs.IsNotFound(err):
|
||||
statusCode = http.StatusNotFound
|
||||
case errdefs.IsInvalidParameter(err):
|
||||
statusCode = http.StatusBadRequest
|
||||
case errdefs.IsConflict(err):
|
||||
statusCode = http.StatusConflict
|
||||
case errdefs.IsUnauthorized(err):
|
||||
statusCode = http.StatusUnauthorized
|
||||
case errdefs.IsUnavailable(err):
|
||||
statusCode = http.StatusServiceUnavailable
|
||||
case errdefs.IsForbidden(err):
|
||||
statusCode = http.StatusForbidden
|
||||
case errdefs.IsNotModified(err):
|
||||
statusCode = http.StatusNotModified
|
||||
case errdefs.IsNotImplemented(err):
|
||||
statusCode = http.StatusNotImplemented
|
||||
case errdefs.IsSystem(err) || errdefs.IsUnknown(err) || errdefs.IsDataLoss(err) || errdefs.IsDeadline(err) || errdefs.IsCancelled(err):
|
||||
statusCode = http.StatusInternalServerError
|
||||
default:
|
||||
statusCode = statusCodeFromGRPCError(err)
|
||||
if statusCode != http.StatusInternalServerError {
|
||||
return statusCode
|
||||
}
|
||||
statusCode = statusCodeFromContainerdError(err)
|
||||
if statusCode != http.StatusInternalServerError {
|
||||
return statusCode
|
||||
}
|
||||
statusCode = statusCodeFromDistributionError(err)
|
||||
if statusCode != http.StatusInternalServerError {
|
||||
return statusCode
|
||||
}
|
||||
if e, ok := err.(causer); ok {
|
||||
return FromError(e.Cause())
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"module": "api",
|
||||
"error_type": fmt.Sprintf("%T", err),
|
||||
}).Debugf("FIXME: Got an API for which error does not match any expected type!!!: %+v", err)
|
||||
}
|
||||
|
||||
if statusCode == 0 {
|
||||
statusCode = http.StatusInternalServerError
|
||||
}
|
||||
|
||||
return statusCode
|
||||
}
|
||||
|
||||
// statusCodeFromGRPCError returns status code according to gRPC error
|
||||
func statusCodeFromGRPCError(err error) int {
|
||||
switch status.Code(err) {
|
||||
case codes.InvalidArgument: // code 3
|
||||
return http.StatusBadRequest
|
||||
case codes.NotFound: // code 5
|
||||
return http.StatusNotFound
|
||||
case codes.AlreadyExists: // code 6
|
||||
return http.StatusConflict
|
||||
case codes.PermissionDenied: // code 7
|
||||
return http.StatusForbidden
|
||||
case codes.FailedPrecondition: // code 9
|
||||
return http.StatusBadRequest
|
||||
case codes.Unauthenticated: // code 16
|
||||
return http.StatusUnauthorized
|
||||
case codes.OutOfRange: // code 11
|
||||
return http.StatusBadRequest
|
||||
case codes.Unimplemented: // code 12
|
||||
return http.StatusNotImplemented
|
||||
case codes.Unavailable: // code 14
|
||||
return http.StatusServiceUnavailable
|
||||
default:
|
||||
// codes.Canceled(1)
|
||||
// codes.Unknown(2)
|
||||
// codes.DeadlineExceeded(4)
|
||||
// codes.ResourceExhausted(8)
|
||||
// codes.Aborted(10)
|
||||
// codes.Internal(13)
|
||||
// codes.DataLoss(15)
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
// statusCodeFromDistributionError returns status code according to registry errcode
|
||||
// code is loosely based on errcode.ServeJSON() in docker/distribution
|
||||
func statusCodeFromDistributionError(err error) int {
|
||||
switch errs := err.(type) {
|
||||
case errcode.Errors:
|
||||
if len(errs) < 1 {
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
if _, ok := errs[0].(errcode.ErrorCoder); ok {
|
||||
return statusCodeFromDistributionError(errs[0])
|
||||
}
|
||||
case errcode.ErrorCoder:
|
||||
return errs.ErrorCode().Descriptor().HTTPStatusCode
|
||||
}
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
|
||||
// statusCodeFromContainerdError returns status code for containerd errors when
|
||||
// consumed directly (not through gRPC)
|
||||
func statusCodeFromContainerdError(err error) int {
|
||||
switch {
|
||||
case containerderrors.IsInvalidArgument(err):
|
||||
return http.StatusBadRequest
|
||||
case containerderrors.IsNotFound(err):
|
||||
return http.StatusNotFound
|
||||
case containerderrors.IsAlreadyExists(err):
|
||||
return http.StatusConflict
|
||||
case containerderrors.IsFailedPrecondition(err):
|
||||
return http.StatusPreconditionFailed
|
||||
case containerderrors.IsUnavailable(err):
|
||||
return http.StatusServiceUnavailable
|
||||
case containerderrors.IsNotImplemented(err):
|
||||
return http.StatusNotImplemented
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package httputils // import "github.com/docker/docker/api/server/httputils"
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
)
|
||||
|
||||
// ContainerDecoder specifies how
|
||||
// to translate an io.Reader into
|
||||
// container configuration.
|
||||
type ContainerDecoder interface {
|
||||
DecodeConfig(src io.Reader) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error)
|
||||
DecodeHostConfig(src io.Reader) (*container.HostConfig, error)
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package httputils // import "github.com/docker/docker/api/server/httputils"
|
||||
package httputils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -13,7 +15,7 @@ func BoolValue(r *http.Request, k string) bool {
|
||||
}
|
||||
|
||||
// BoolValueOrDefault returns the default bool passed if the query param is
|
||||
// missing, otherwise it's just a proxy to boolValue above.
|
||||
// missing, otherwise it's just a proxy to boolValue above
|
||||
func BoolValueOrDefault(r *http.Request, k string, d bool) bool {
|
||||
if _, ok := r.Form[k]; !ok {
|
||||
return d
|
||||
@@ -36,7 +38,10 @@ func Int64ValueOrZero(r *http.Request, k string) int64 {
|
||||
func Int64ValueOrDefault(r *http.Request, field string, def int64) (int64, error) {
|
||||
if r.Form.Get(field) != "" {
|
||||
value, err := strconv.ParseInt(r.Form.Get(field), 10, 64)
|
||||
return value, err
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
return def, nil
|
||||
}
|
||||
@@ -47,30 +52,25 @@ type ArchiveOptions struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
type badParameterError struct {
|
||||
param string
|
||||
}
|
||||
|
||||
func (e badParameterError) Error() string {
|
||||
return "bad parameter: " + e.param + "cannot be empty"
|
||||
}
|
||||
|
||||
func (e badParameterError) InvalidParameter() {}
|
||||
|
||||
// ArchiveFormValues parses form values and turns them into ArchiveOptions.
|
||||
// It fails if the archive name and path are not in the request.
|
||||
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"]
|
||||
if name == "" {
|
||||
return ArchiveOptions{}, badParameterError{"name"}
|
||||
}
|
||||
path := r.Form.Get("path")
|
||||
if path == "" {
|
||||
return ArchiveOptions{}, badParameterError{"path"}
|
||||
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,4 +1,4 @@
|
||||
package httputils // import "github.com/docker/docker/api/server/httputils"
|
||||
package httputils
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
@@ -23,7 +23,7 @@ func TestBoolValue(t *testing.T) {
|
||||
for c, e := range cases {
|
||||
v := url.Values{}
|
||||
v.Set("test", c)
|
||||
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
||||
r, _ := http.NewRequest("POST", "", nil)
|
||||
r.Form = v
|
||||
|
||||
a := BoolValue(r, "test")
|
||||
@@ -34,14 +34,14 @@ func TestBoolValue(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBoolValueOrDefault(t *testing.T) {
|
||||
r, _ := http.NewRequest(http.MethodGet, "", nil)
|
||||
r, _ := http.NewRequest("GET", "", nil)
|
||||
if !BoolValueOrDefault(r, "queryparam", true) {
|
||||
t.Fatal("Expected to get true default value, got false")
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("param", "")
|
||||
r, _ = http.NewRequest(http.MethodGet, "", nil)
|
||||
r, _ = http.NewRequest("GET", "", nil)
|
||||
r.Form = v
|
||||
if BoolValueOrDefault(r, "param", true) {
|
||||
t.Fatal("Expected not to get true")
|
||||
@@ -59,7 +59,7 @@ func TestInt64ValueOrZero(t *testing.T) {
|
||||
for c, e := range cases {
|
||||
v := url.Values{}
|
||||
v.Set("test", c)
|
||||
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
||||
r, _ := http.NewRequest("POST", "", nil)
|
||||
r.Form = v
|
||||
|
||||
a := Int64ValueOrZero(r, "test")
|
||||
@@ -79,7 +79,7 @@ func TestInt64ValueOrDefault(t *testing.T) {
|
||||
for c, e := range cases {
|
||||
v := url.Values{}
|
||||
v.Set("test", c)
|
||||
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
||||
r, _ := http.NewRequest("POST", "", nil)
|
||||
r.Form = v
|
||||
|
||||
a, err := Int64ValueOrDefault(r, "test", -1)
|
||||
@@ -95,11 +95,11 @@ func TestInt64ValueOrDefault(t *testing.T) {
|
||||
func TestInt64ValueOrDefaultWithError(t *testing.T) {
|
||||
v := url.Values{}
|
||||
v.Set("test", "invalid")
|
||||
r, _ := http.NewRequest(http.MethodPost, "", nil)
|
||||
r, _ := http.NewRequest("POST", "", nil)
|
||||
r.Form = v
|
||||
|
||||
_, err := Int64ValueOrDefault(r, "test", -1)
|
||||
if err == nil {
|
||||
t.Fatal("Expected an error.")
|
||||
t.Fatalf("Expected an error.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
package httputils // import "github.com/docker/docker/api/server/httputils"
|
||||
package httputils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/pkg/version"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
// APIVersionKey is the client's requested API version.
|
||||
type APIVersionKey struct{}
|
||||
const APIVersionKey = "api-version"
|
||||
|
||||
// APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
|
||||
// Any function that has the appropriate signature can be registered as an API endpoint (e.g. getVersion).
|
||||
// Any function that has the appropriate signature can be register as a API endpoint (e.g. getVersion).
|
||||
type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
|
||||
|
||||
// HijackConnection interrupts the http response writer to get the
|
||||
@@ -27,7 +31,7 @@ func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Flush the options to make sure the client sets the raw mode
|
||||
_, _ = conn.Write([]byte{})
|
||||
conn.Write([]byte{})
|
||||
return conn, conn, nil
|
||||
}
|
||||
|
||||
@@ -37,9 +41,9 @@ func CloseStreams(streams ...interface{}) {
|
||||
if tcpc, ok := stream.(interface {
|
||||
CloseWrite() error
|
||||
}); ok {
|
||||
_ = tcpc.CloseWrite()
|
||||
tcpc.CloseWrite()
|
||||
} else if closer, ok := stream.(io.Closer); ok {
|
||||
_ = closer.Close()
|
||||
closer.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,10 +60,10 @@ func CheckForJSON(r *http.Request) error {
|
||||
}
|
||||
|
||||
// Otherwise it better be json
|
||||
if matchesContentType(ct, "application/json") {
|
||||
if api.MatchesContentType(ct, "application/json") {
|
||||
return nil
|
||||
}
|
||||
return errdefs.InvalidParameter(errors.Errorf("Content-Type specified (%s) must be 'application/json'", ct))
|
||||
return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct)
|
||||
}
|
||||
|
||||
// ParseForm ensures the request form is parsed even with invalid content types.
|
||||
@@ -69,30 +73,108 @@ func ParseForm(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
||||
return errdefs.InvalidParameter(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseMultipartForm ensure the request form is parsed, even with invalid content types.
|
||||
func ParseMultipartForm(r *http.Request) error {
|
||||
if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteError decodes a specific docker error and sends it in the response.
|
||||
func WriteError(w http.ResponseWriter, err error) {
|
||||
if err == nil || w == nil {
|
||||
logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
|
||||
return
|
||||
}
|
||||
|
||||
statusCode := http.StatusInternalServerError
|
||||
errMsg := err.Error()
|
||||
|
||||
// Based on the type of error we get we need to process things
|
||||
// slightly differently to extract the error message.
|
||||
// In the 'errcode.*' cases there are two different type of
|
||||
// error that could be returned. errocode.ErrorCode is the base
|
||||
// type of error object - it is just an 'int' that can then be
|
||||
// used as the look-up key to find the message. errorcode.Error
|
||||
// extends errorcode.Error by adding error-instance specific
|
||||
// data, like 'details' or variable strings to be inserted into
|
||||
// the message.
|
||||
//
|
||||
// Ideally, we should just be able to call err.Error() for all
|
||||
// cases but the errcode package doesn't support that yet.
|
||||
//
|
||||
// Additionally, in both errcode cases, there might be an http
|
||||
// status code associated with it, and if so use it.
|
||||
switch err.(type) {
|
||||
case errcode.ErrorCode:
|
||||
daError, _ := err.(errcode.ErrorCode)
|
||||
statusCode = daError.Descriptor().HTTPStatusCode
|
||||
errMsg = daError.Message()
|
||||
|
||||
case errcode.Error:
|
||||
// For reference, if you're looking for a particular error
|
||||
// then you can do something like :
|
||||
// import ( derr "github.com/docker/docker/errors" )
|
||||
// if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... }
|
||||
|
||||
daError, _ := err.(errcode.Error)
|
||||
statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode
|
||||
errMsg = daError.Message
|
||||
|
||||
default:
|
||||
// This part of will be removed once we've
|
||||
// converted everything over to use the errcode package
|
||||
|
||||
// FIXME: this is brittle and should not be necessary.
|
||||
// If we need to differentiate between different possible error types,
|
||||
// we should create appropriate error types with clearly defined meaning
|
||||
errStr := strings.ToLower(err.Error())
|
||||
for keyword, status := range map[string]int{
|
||||
"not found": http.StatusNotFound,
|
||||
"no such": http.StatusNotFound,
|
||||
"bad parameter": http.StatusBadRequest,
|
||||
"conflict": http.StatusConflict,
|
||||
"impossible": http.StatusNotAcceptable,
|
||||
"wrong login/password": http.StatusUnauthorized,
|
||||
"hasn't been activated": http.StatusForbidden,
|
||||
} {
|
||||
if strings.Contains(errStr, keyword) {
|
||||
statusCode = status
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if statusCode == 0 {
|
||||
statusCode = http.StatusInternalServerError
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": utils.GetErrorMessage(err)}).Error("HTTP Error")
|
||||
http.Error(w, errMsg, statusCode)
|
||||
}
|
||||
|
||||
// WriteJSON writes the value v to the http response stream as json with standard json encoding.
|
||||
func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
return json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
|
||||
// VersionFromContext returns an API version from the context using APIVersionKey.
|
||||
// It panics if the context value does not have version.Version type.
|
||||
func VersionFromContext(ctx context.Context) string {
|
||||
func VersionFromContext(ctx context.Context) (ver version.Version) {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
return
|
||||
}
|
||||
|
||||
if val := ctx.Value(APIVersionKey{}); val != nil {
|
||||
return val.(string)
|
||||
val := ctx.Value(APIVersionKey)
|
||||
if val == nil {
|
||||
return
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// matchesContentType validates the content type against the expected one
|
||||
func matchesContentType(contentType, expectedType string) bool {
|
||||
mimetype, _, err := mime.ParseMediaType(contentType)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error parsing media type: %s error: %v", contentType, err)
|
||||
}
|
||||
return err == nil && mimetype == expectedType
|
||||
return val.(version.Version)
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package httputils // import "github.com/docker/docker/api/server/httputils"
|
||||
|
||||
import "testing"
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package httputils // import "github.com/docker/docker/api/server/httputils"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// WriteJSON writes the value v to the http response stream as json with standard json encoding.
|
||||
func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetEscapeHTML(false)
|
||||
return enc.Encode(v)
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package httputils // import "github.com/docker/docker/api/server/httputils"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
)
|
||||
|
||||
// WriteLogStream writes an encoded byte stream of log messages from the
|
||||
// messages channel, multiplexing them with a stdcopy.Writer if mux is true
|
||||
func WriteLogStream(_ context.Context, w io.Writer, msgs <-chan *backend.LogMessage, config *types.ContainerLogsOptions, mux bool) {
|
||||
wf := ioutils.NewWriteFlusher(w)
|
||||
defer wf.Close()
|
||||
|
||||
wf.Flush()
|
||||
|
||||
outStream := io.Writer(wf)
|
||||
errStream := outStream
|
||||
sysErrStream := errStream
|
||||
if mux {
|
||||
sysErrStream = stdcopy.NewStdWriter(outStream, stdcopy.Systemerr)
|
||||
errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
|
||||
outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
|
||||
}
|
||||
|
||||
for {
|
||||
msg, ok := <-msgs
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// check if the message contains an error. if so, write that error
|
||||
// and exit
|
||||
if msg.Err != nil {
|
||||
fmt.Fprintf(sysErrStream, "Error grabbing logs: %v\n", msg.Err)
|
||||
continue
|
||||
}
|
||||
logLine := msg.Line
|
||||
if config.Details {
|
||||
logLine = append(attrsByteSlice(msg.Attrs), ' ')
|
||||
logLine = append(logLine, msg.Line...)
|
||||
}
|
||||
if config.Timestamps {
|
||||
logLine = append([]byte(msg.Timestamp.Format(jsonmessage.RFC3339NanoFixed)+" "), logLine...)
|
||||
}
|
||||
if msg.Source == "stdout" && config.ShowStdout {
|
||||
_, _ = outStream.Write(logLine)
|
||||
}
|
||||
if msg.Source == "stderr" && config.ShowStderr {
|
||||
_, _ = errStream.Write(logLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type byKey []backend.LogAttr
|
||||
|
||||
func (b byKey) Len() int { return len(b) }
|
||||
func (b byKey) Less(i, j int) bool { return b[i].Key < b[j].Key }
|
||||
func (b byKey) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
||||
|
||||
func attrsByteSlice(a []backend.LogAttr) []byte {
|
||||
// Note this sorts "a" in-place. That is fine here - nothing else is
|
||||
// going to use Attrs or care about the order.
|
||||
sort.Sort(byKey(a))
|
||||
|
||||
var ret []byte
|
||||
for i, pair := range a {
|
||||
k, v := url.QueryEscape(pair.Key), url.QueryEscape(pair.Value)
|
||||
ret = append(ret, []byte(k)...)
|
||||
ret = append(ret, '=')
|
||||
ret = append(ret, []byte(v)...)
|
||||
if i != len(a)-1 {
|
||||
ret = append(ret, ',')
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
@@ -1,24 +1,118 @@
|
||||
package server // import "github.com/docker/docker/api/server"
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/server/middleware"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/docker/docker/autogen/dockerversion"
|
||||
"github.com/docker/docker/errors"
|
||||
"github.com/docker/docker/pkg/version"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// handlerWithGlobalMiddlewares wraps the handler function for a request with
|
||||
// the server's global middlewares. The order of the middlewares is backwards,
|
||||
// meaning that the first in the list will be evaluated last.
|
||||
func (s *Server) handlerWithGlobalMiddlewares(handler httputils.APIFunc) httputils.APIFunc {
|
||||
next := handler
|
||||
// middleware is an adapter to allow the use of ordinary functions as Docker API filters.
|
||||
// Any function that has the appropriate signature can be register as a middleware.
|
||||
type middleware func(handler httputils.APIFunc) httputils.APIFunc
|
||||
|
||||
for _, m := range s.middlewares {
|
||||
next = m.WrapHandler(next)
|
||||
// loggingMiddleware logs each request when logging is enabled.
|
||||
func (s *Server) loggingMiddleware(handler httputils.APIFunc) httputils.APIFunc {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if s.cfg.Logging {
|
||||
logrus.Infof("%s %s", r.Method, r.RequestURI)
|
||||
}
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
|
||||
if s.cfg.Logging && logrus.GetLevel() == logrus.DebugLevel {
|
||||
next = middleware.DebugRequestMiddleware(next)
|
||||
}
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
// userAgentMiddleware checks the User-Agent header looking for a valid docker client spec.
|
||||
func (s *Server) userAgentMiddleware(handler httputils.APIFunc) httputils.APIFunc {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
|
||||
dockerVersion := version.Version(s.cfg.Version)
|
||||
|
||||
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
|
||||
|
||||
// v1.20 onwards includes the GOOS of the client after the version
|
||||
// such as Docker/1.7.0 (linux)
|
||||
if len(userAgent) == 2 && strings.Contains(userAgent[1], " ") {
|
||||
userAgent[1] = strings.Split(userAgent[1], " ")[0]
|
||||
}
|
||||
|
||||
if len(userAgent) == 2 && !dockerVersion.Equal(version.Version(userAgent[1])) {
|
||||
logrus.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion)
|
||||
}
|
||||
}
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
}
|
||||
|
||||
// corsMiddleware sets the CORS header expectations in the server.
|
||||
func (s *Server) corsMiddleware(handler httputils.APIFunc) httputils.APIFunc {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
// If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*"
|
||||
// otherwise, all head values will be passed to HTTP handler
|
||||
corsHeaders := s.cfg.CorsHeaders
|
||||
if corsHeaders == "" && s.cfg.EnableCors {
|
||||
corsHeaders = "*"
|
||||
}
|
||||
|
||||
if corsHeaders != "" {
|
||||
writeCorsHeaders(w, r, corsHeaders)
|
||||
}
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
}
|
||||
|
||||
// versionMiddleware checks the api version requirements before passing the request to the server handler.
|
||||
func versionMiddleware(handler httputils.APIFunc) httputils.APIFunc {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
apiVersion := version.Version(vars["version"])
|
||||
if apiVersion == "" {
|
||||
apiVersion = api.Version
|
||||
}
|
||||
|
||||
if apiVersion.GreaterThan(api.Version) {
|
||||
return errors.ErrorCodeNewerClientVersion.WithArgs(apiVersion, api.Version)
|
||||
}
|
||||
if apiVersion.LessThan(api.MinVersion) {
|
||||
return errors.ErrorCodeOldClientVersion.WithArgs(apiVersion, api.Version)
|
||||
}
|
||||
|
||||
w.Header().Set("Server", "Docker/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
|
||||
ctx = context.WithValue(ctx, httputils.APIVersionKey, apiVersion)
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
}
|
||||
|
||||
// handleWithGlobalMiddlwares wraps the handler function for a request with
|
||||
// the server's global middlewares. The order of the middlewares is backwards,
|
||||
// meaning that the first in the list will be evaludated last.
|
||||
//
|
||||
// Example: handleWithGlobalMiddlewares(s.getContainersName)
|
||||
//
|
||||
// s.loggingMiddleware(
|
||||
// s.userAgentMiddleware(
|
||||
// s.corsMiddleware(
|
||||
// versionMiddleware(s.getContainersName)
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
func (s *Server) handleWithGlobalMiddlewares(handler httputils.APIFunc) httputils.APIFunc {
|
||||
middlewares := []middleware{
|
||||
versionMiddleware,
|
||||
s.corsMiddleware,
|
||||
s.userAgentMiddleware,
|
||||
s.loggingMiddleware,
|
||||
}
|
||||
|
||||
h := handler
|
||||
for _, m := range middlewares {
|
||||
h = m(h)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package middleware // import "github.com/docker/docker/api/server/middleware"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// CORSMiddleware injects CORS headers to each request
|
||||
// when it's configured.
|
||||
type CORSMiddleware struct {
|
||||
defaultHeaders string
|
||||
}
|
||||
|
||||
// NewCORSMiddleware creates a new CORSMiddleware with default headers.
|
||||
func NewCORSMiddleware(d string) CORSMiddleware {
|
||||
return CORSMiddleware{defaultHeaders: d}
|
||||
}
|
||||
|
||||
// WrapHandler returns a new handler function wrapping the previous one in the request chain.
|
||||
func (c CORSMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
// If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*"
|
||||
// otherwise, all head values will be passed to HTTP handler
|
||||
corsHeaders := c.defaultHeaders
|
||||
if corsHeaders == "" {
|
||||
corsHeaders = "*"
|
||||
}
|
||||
|
||||
logrus.Debugf("CORS header is enabled and set to: %s", corsHeaders)
|
||||
w.Header().Add("Access-Control-Allow-Origin", corsHeaders)
|
||||
w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth")
|
||||
w.Header().Add("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS")
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package middleware // import "github.com/docker/docker/api/server/middleware"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// DebugRequestMiddleware dumps the request to logger
|
||||
func DebugRequestMiddleware(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
logrus.Debugf("Calling %s %s", r.Method, r.RequestURI)
|
||||
|
||||
if r.Method != http.MethodPost {
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
if err := httputils.CheckForJSON(r); err != nil {
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
maxBodySize := 4096 // 4KB
|
||||
if r.ContentLength > int64(maxBodySize) {
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
|
||||
body := r.Body
|
||||
bufReader := bufio.NewReaderSize(body, maxBodySize)
|
||||
r.Body = ioutils.NewReadCloserWrapper(bufReader, func() error { return body.Close() })
|
||||
|
||||
b, err := bufReader.Peek(maxBodySize)
|
||||
if err != io.EOF {
|
||||
// either there was an error reading, or the buffer is full (in which case the request is too large)
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
|
||||
var postForm map[string]interface{}
|
||||
if err := json.Unmarshal(b, &postForm); err == nil {
|
||||
maskSecretKeys(postForm)
|
||||
formStr, errMarshal := json.Marshal(postForm)
|
||||
if errMarshal == nil {
|
||||
logrus.Debugf("form data: %s", string(formStr))
|
||||
} else {
|
||||
logrus.Debugf("form data: %q", postForm)
|
||||
}
|
||||
}
|
||||
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
}
|
||||
|
||||
func maskSecretKeys(inp interface{}) {
|
||||
if arr, ok := inp.([]interface{}); ok {
|
||||
for _, f := range arr {
|
||||
maskSecretKeys(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if form, ok := inp.(map[string]interface{}); ok {
|
||||
scrub := []string{
|
||||
// Note: The Data field contains the base64-encoded secret in 'secret'
|
||||
// and 'config' create and update requests. Currently, no other POST
|
||||
// API endpoints use a data field, so we scrub this field unconditionally.
|
||||
// Change this handling to be conditional if a new endpoint is added
|
||||
// in future where this field should not be scrubbed.
|
||||
"data",
|
||||
"jointoken",
|
||||
"password",
|
||||
"secret",
|
||||
"signingcakey",
|
||||
"unlockkey",
|
||||
}
|
||||
loop0:
|
||||
for k, v := range form {
|
||||
for _, m := range scrub {
|
||||
if strings.EqualFold(m, k) {
|
||||
form[k] = "*****"
|
||||
continue loop0
|
||||
}
|
||||
}
|
||||
maskSecretKeys(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package middleware // import "github.com/docker/docker/api/server/middleware"
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestMaskSecretKeys(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
input map[string]interface{}
|
||||
expected map[string]interface{}
|
||||
}{
|
||||
{
|
||||
doc: "secret/config create and update requests",
|
||||
input: map[string]interface{}{"Data": "foo", "Name": "name", "Labels": map[string]interface{}{}},
|
||||
expected: map[string]interface{}{"Data": "*****", "Name": "name", "Labels": map[string]interface{}{}},
|
||||
},
|
||||
{
|
||||
doc: "masking other fields (recursively)",
|
||||
input: map[string]interface{}{
|
||||
"password": "pass",
|
||||
"secret": "secret",
|
||||
"jointoken": "jointoken",
|
||||
"unlockkey": "unlockkey",
|
||||
"signingcakey": "signingcakey",
|
||||
"other": map[string]interface{}{
|
||||
"password": "pass",
|
||||
"secret": "secret",
|
||||
"jointoken": "jointoken",
|
||||
"unlockkey": "unlockkey",
|
||||
"signingcakey": "signingcakey",
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"password": "*****",
|
||||
"secret": "*****",
|
||||
"jointoken": "*****",
|
||||
"unlockkey": "*****",
|
||||
"signingcakey": "*****",
|
||||
"other": map[string]interface{}{
|
||||
"password": "*****",
|
||||
"secret": "*****",
|
||||
"jointoken": "*****",
|
||||
"unlockkey": "*****",
|
||||
"signingcakey": "*****",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
doc: "case insensitive field matching",
|
||||
input: map[string]interface{}{
|
||||
"PASSWORD": "pass",
|
||||
"other": map[string]interface{}{
|
||||
"PASSWORD": "pass",
|
||||
},
|
||||
},
|
||||
expected: map[string]interface{}{
|
||||
"PASSWORD": "*****",
|
||||
"other": map[string]interface{}{
|
||||
"PASSWORD": "*****",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range tests {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
maskSecretKeys(testcase.input)
|
||||
assert.Check(t, is.DeepEqual(testcase.expected, testcase.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package middleware // import "github.com/docker/docker/api/server/middleware"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ExperimentalMiddleware is a the middleware in charge of adding the
|
||||
// 'Docker-Experimental' header to every outgoing request
|
||||
type ExperimentalMiddleware struct {
|
||||
experimental string
|
||||
}
|
||||
|
||||
// NewExperimentalMiddleware creates a new ExperimentalMiddleware
|
||||
func NewExperimentalMiddleware(experimentalEnabled bool) ExperimentalMiddleware {
|
||||
if experimentalEnabled {
|
||||
return ExperimentalMiddleware{"true"}
|
||||
}
|
||||
return ExperimentalMiddleware{"false"}
|
||||
}
|
||||
|
||||
// WrapHandler returns a new handler function wrapping the previous one in the request chain.
|
||||
func (e ExperimentalMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
w.Header().Set("Docker-Experimental", e.experimental)
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user