Compare commits

...

170 Commits

Author SHA1 Message Date
Solomon Hykes
9fe8bfb2bc Bump version to 0.4.6 2013-06-22 13:36:45 -07:00
Solomon Hykes
f9acd605dc - Runtime: add regression test for issue #995 2013-06-22 13:33:43 -07:00
Solomon Hykes
290b1973a9 Fix a bug which caused creation of empty images (and volumes) to crash. FIxes #995. 2013-06-22 12:29:42 -07:00
Solomon Hykes
ecd1fff9b0 Fix formatting in Changelog 2013-06-21 20:22:14 -06:00
Daniel Mizyrycki
b0f12bd5e8 Merge pull request #988 from dotcloud/526-packaging-ubuntu
Packaging|Ubuntu, issue #526: Follow init permission pattern for docker.conf
2013-06-21 18:03:12 -07:00
Daniel Mizyrycki
5d61ec11e3 Packaging|Ubuntu, issue #526: Follow init permission pattern for docker.conf 2013-06-21 17:57:45 -07:00
Guillaume J. Charmes
c792c0a6c9 Merge pull request #986 from dotcloud/bump_0.4.5
Bump version to 0.4.5
2013-06-21 17:13:38 -07:00
Solomon Hykes
d9d2540162 Small copy improvements in Changelog 2013-06-21 17:09:49 -07:00
Guillaume J. Charmes
f5d08fc49c Bump version to 0.4.5 2013-06-21 17:01:01 -07:00
Victor Vieux
b24759af1c Merge pull request #959 from dotcloud/947-add_ps_size_option
+ Client: Add docker ps -s option to display containers' sizes
2013-06-21 16:10:45 -07:00
Victor Vieux
4d1692726b merge master and add doc 2013-06-22 01:08:20 +02:00
Victor Vieux
1581ed52ba consistent codebase fix 2013-06-21 22:55:33 +00:00
Guillaume J. Charmes
de1a5a75cc Merge pull request #848 from dotcloud/builder_server-3
Improve Docker build
2013-06-21 14:55:08 -07:00
Guillaume J. Charmes
169ef21de7 Remove deprecated INSERT from documentation 2013-06-21 14:12:52 -07:00
Guillaume J. Charmes
d0fa6927f8 Update api docs 2013-06-21 13:51:48 -07:00
Victor Vieux
63e8a4ac74 Merge pull request #970 from titanous/go1.1-unreachable
Remove code unreachable using Go 1.1
2013-06-21 10:44:40 -07:00
Victor Vieux
459230d3f9 Merge pull request #980 from ToothlessGear/fix-progressbar
* Client: fix progressbar output
2013-06-21 10:42:14 -07:00
Guillaume J. Charmes
070e1aec7e Merge pull request #975 from unclejack/891-mark_command_as_optional
891 mark command as optional for docker run
2013-06-21 10:22:52 -07:00
Marcus Farkas
3ac68f1966 Client: better progressbar output 2013-06-21 17:19:27 +02:00
Victor Vieux
5ccde4dffc Merge pull request #801 from dotcloud/build_docker_static
* Makefile: Add link flags in order to link statically and without debug symbols
2013-06-21 02:30:57 -07:00
Victor Vieux
dc847001a5 Merge pull request #888 from dotcloud/fix-auth
* Auth: fix auth small issue in case you change your password on index.io
2013-06-21 02:22:01 -07:00
Victor Vieux
639833aaf5 fix tests 2013-06-21 09:20:57 +00:00
Victor Vieux
8f2a80804c Merge branch 'master' into fix-auth 2013-06-21 09:18:03 +00:00
Victor Vieux
78842970cf Merge pull request #976 from dotcloud/fix_doc_post
fix typo in documentation
2013-06-21 02:14:39 -07:00
Victor Vieux
8e7d4cda07 fix doc post 2013-06-21 11:13:13 +02:00
Victor Vieux
5b3ad0023b inverse if 2013-06-21 09:06:09 +00:00
unclejack
6a1279fb90 docs: mark command as optional for docker run 2013-06-21 11:07:14 +03:00
unclejack
66910a7602 mark command as optional for docker run 2013-06-21 11:06:41 +03:00
Solomon Hykes
d9bce2defd - Builder: return an error when the build fails 2013-06-20 22:15:19 -07:00
Solomon Hykes
352991bdf4 Merge branch 'simpler-build-upload' (#900) into builder_server-3 (#848) 2013-06-20 22:02:36 -07:00
Solomon Hykes
4383d7b603 Merge pull request #953 from rhysh/952-swapaccount-docs
- Documentation: fix inconsistent/outdated instructions for setting up "swapaccount"
2013-06-20 20:49:44 -07:00
Solomon Hykes
89ae56820a Merge pull request #965 from freegenie/patch-1
* Documentation: use sudo for quick install script
2013-06-20 20:48:42 -07:00
Solomon Hykes
17489cac1a Merge pull request #972 from titanous/update-authors
Update AUTHORS
2013-06-20 20:42:52 -07:00
Solomon Hykes
c1a5318d8e Merge branch 'build-add-file' into simpler-build-upload
Conflicts:
	buildfile_test.go
2013-06-20 20:42:19 -07:00
Jonathan Rudenberg
b0b690cf23 Update AUTHORS 2013-06-20 23:29:20 -04:00
Solomon Hykes
86e83186b5 Merge branch 'master' into simpler-build-upload
Conflicts:
	commands.go
2013-06-20 20:25:59 -07:00
Solomon Hykes
36d610a388 - Builder: fixed a regression in ADD. Improved regression tests for build behavior. 2013-06-20 20:20:16 -07:00
Jonathan Rudenberg
50b70eeb68 Remove code unreachable using Go 1.1 2013-06-20 23:19:44 -04:00
Solomon Hykes
cc0f59742f * Builder: simplified unit tests. The tests are now embedded in the build itself. Yeah baby. 2013-06-20 20:16:39 -07:00
Solomon Hykes
09dd7f14de Merge pull request #969 from dotcloud/fix_logs
- Remote API: Fix a bug which caused 'docker logs' to fail
2013-06-20 18:33:33 -07:00
Guillaume J. Charmes
b419699ab8 Use hijack for logs instead of stream 2013-06-20 18:18:36 -07:00
Guillaume J. Charmes
08825fa611 remove unused files 2013-06-20 16:31:11 -07:00
Solomon Hykes
02f0c1e46d Bump version to 0.4.4 2013-06-20 14:33:59 -07:00
Solomon Hykes
dbfb3eb923 - Builder: hotfix for bug introduced in 3adf9ce04e 2013-06-20 14:29:34 -07:00
Solomon Hykes
e43323221b Merge branch 'master' into simpler-build-upload
Conflicts:
	api.go
	builder_client.go
	commands.go
2013-06-20 14:19:09 -07:00
Fabrizio Regini
da06349723 Update README.md 2013-06-20 23:15:38 +02:00
Solomon Hykes
cff2187a4c Fixed API version numbers in api docs 2013-06-20 12:30:02 -07:00
Guillaume J. Charmes
a078d3c872 Merge pull request #950 from dotcloud/bump_0.4.3
Bumped version to 0.4.3
2013-06-20 12:24:55 -07:00
Guillaume J. Charmes
da5bb4db96 Bumped version to 0.4.3 2013-06-20 12:23:14 -07:00
Daniel Mizyrycki
1b19939742 Merge pull request #946 from unclejack/speed_up_dockerbuilder_image_creation
dockerbuilder : batch apt-get install operations for speed
2013-06-20 11:51:43 -07:00
Guillaume J. Charmes
930e1d8830 Merge pull request #941 from dotcloud/makefile_test_subpackages
gofmt and test sub directories in makefile
2013-06-20 11:18:37 -07:00
Guillaume J. Charmes
fa68fe6ff3 Merge pull request #938 from dotcloud/add_unix_socket-feature
* Runtime: Add unix socket and multiple -H
2013-06-20 11:17:16 -07:00
Guillaume J. Charmes
21a5a6202d Merge pull request #907 from dotcloud/go1.1_cookie_jar-feature
* Runtime: use go 1.1 cookiejar and remove ResetClient
2013-06-20 10:48:36 -07:00
Solomon Hykes
db60337598 Makefile: added missing -a option 2013-06-20 10:39:09 -07:00
Guillaume J. Charmes
c5be64fec4 Add link flags in order to link statically and without debug symbols 2013-06-20 10:33:54 -07:00
Guillaume J. Charmes
659e846006 Merge branch 'master' into builder_server-3
Conflicts:
	docs/sources/use/builder.rst
2013-06-20 10:27:12 -07:00
Solomon Hykes
d8f56352da Merge pull request #961 from dotcloud/933-warning_rm-running
* Runtime: refuse to remove a running container
2013-06-20 10:25:02 -07:00
Guillaume J. Charmes
d1a3d020aa Merge pull request #913 from dotcloud/fix_detach_eof
- Runtime: Impossible to detach from attached container fix
2013-06-20 10:21:19 -07:00
Guillaume J. Charmes
8807b7dd46 Merge pull request #909 from dotcloud/fix_auth_tests
Fix the auth tests
2013-06-20 10:14:55 -07:00
Daniel Mizyrycki
cd155a1f25 Merge pull request #962 from dotcloud/960-packaging-ubuntu
Packaging|ubuntu, issue #960: Add docker PPA staging in release process
2013-06-20 09:03:15 -07:00
Daniel Mizyrycki
d8887f3488 Packaging|ubuntu, issue #960: Add docker PPA staging in release process 2013-06-20 08:57:28 -07:00
Victor Vieux
1c841d4fee add warning when you rm a running container 2013-06-20 15:45:30 +00:00
Victor Vieux
da199846d2 use strconv.ParseBool in getBoolParam 2013-06-20 14:34:58 +00:00
Victor Vieux
bd04d7d475 add ps -s 2013-06-20 14:19:50 +00:00
Victor Vieux
5f93aa0ecf rebase master 2013-06-20 13:56:36 +00:00
Victor Vieux
05796bed57 update docs 2013-06-20 12:34:08 +00:00
Solomon Hykes
8a131dffb6 Merge pull request #948 from dotcloud/registry_pathencode
* Registry: Use opaque requests when we need to preserve urlencoding in registry requests
2013-06-19 22:41:16 -07:00
Solomon Hykes
79efcb545d Merge branch 'master' into simpler-build-upload 2013-06-19 18:48:19 -07:00
Daniel Mizyrycki
88dcba3482 Packaging|ubuntu, issue #954: Generate debian/changelog from main CHANGELOG.md 2013-06-19 16:32:51 -07:00
Guillaume J. Charmes
754609ab69 Merge pull request #945 from globalcitizen/master
Security warnings in LXC configuration
2013-06-19 15:06:48 -07:00
Solomon Hykes
d6ab71f450 * Remote API: updated docs for 1.3 2013-06-19 15:03:33 -07:00
Solomon Hykes
55edbcd02f * Builder: remove duplicate unit test 2013-06-19 14:59:42 -07:00
Solomon Hykes
90dde9beab *Builder: warn pre-1.3 clients that they need to upgrade. This breaks semver, but our API should still be in 0.X versioning, in which case semver allows breaking changes. 2013-06-19 14:59:28 -07:00
Rhys Hiltner
5fc1329b2f the kernel needs "swapaccount=1" set - some docs are updated, this one seems to have slipped through
Fixes #952
2013-06-19 14:45:03 -07:00
Solomon Hykes
9c8085a0aa Merge pull request #951 from dotcloud/add-fix
* Builder: correct the behavior of ADD when copying directories.
2013-06-19 14:36:09 -07:00
Solomon Hykes
507ea757a5 * Builder: correct the behavior of ADD when copying directories. 2013-06-19 14:26:11 -07:00
Guillaume J. Charmes
7e065aaacd Merge pull request #917 from dotcloud/pull_pool
- Runtime: Forbid parralel push/pull for a single image/repo. Fixes #311
2013-06-19 14:11:29 -07:00
shin-
0312bbc535 Use opaque requests when we need to preserve urlencoding in registry requests 2013-06-19 13:49:45 -07:00
Solomon Hykes
a056f1deec Merge pull request #924 from eliasp/remove-ifconfig-usage
* Documentation: replace `ifconfig` in docs with `iproute`
2013-06-19 13:20:35 -07:00
Solomon Hykes
fdaefe6997 Merge pull request #944 from hansent/patch-1
* Documentation: use https repo url to clone for dev setup instructions
2013-06-19 13:17:08 -07:00
unclejack
88279439af batch apt-get install operations for speed
The dockerbuilder Dockerfile was installing one package per apt-get
install operation.

This changes it so that consecutive run apt-get install operations are
batched into a single operation.
2013-06-19 22:07:56 +03:00
Victor Vieux
2d6a49215c add testall rule 2013-06-19 18:21:53 +00:00
Guillaume J. Charmes
a7e14a3065 hotfix: nil pointer uppon some registry error 2013-06-19 11:08:19 -07:00
Guillaume J. Charmes
a660cc0d01 Merge pull request #934 from dotcloud/fix-add-behavior
* Build: Stabilize ADD behavior
2013-06-19 10:56:39 -07:00
globalcitizen
788d66f409 Add note about lxc.cap.keep > lxc.cap.drop 2013-06-20 00:39:35 +07:00
globalcitizen
96988a37f5 Add healthy procfs/sysfs warnings 2013-06-20 00:37:08 +07:00
Solomon Hykes
b368d21568 Merge branch 'master' into fix-add-behavior 2013-06-19 10:31:50 -07:00
Thomas Hansen
c88b763e80 use https repo url to clone for dev setup instructions
the git clone line in the dev setup instructions does not work as is, unless the user has write access
2013-06-19 11:38:58 -05:00
Victor Vieux
ec3c89e57c Merge pull request #849 from dotcloud/improve_progressbar_pull
* Client: HumanReadable ProgressBar sizes in pull
2013-06-19 08:02:40 -07:00
Victor Vieux
5dcab2d361 gofmt and test sub directories in makefile 2013-06-19 14:50:58 +00:00
Victor Vieux
5f7e98be20 Merge pull request #930 from andrewmunsell/patch-1
Fix Mac OS X installation instructions URL
2013-06-19 07:31:24 -07:00
Victor Vieux
d52af3f58f Merge branch 'master' into add_unix_socket-feature 2013-06-19 12:49:27 +00:00
Victor Vieux
063c838c92 update docs 2013-06-19 12:48:50 +00:00
Victor Vieux
9632bf2287 add tests 2013-06-19 12:40:01 +00:00
Victor Vieux
dede1585ee add the possibility to use multiple -H 2013-06-19 12:31:54 +00:00
Solomon Hykes
5be7b9af3e * Builder: fixed the behavior of ADD to be (mostly) reverse-compatible, predictable and well-documented. 2013-06-18 20:28:49 -07:00
Daniel Mizyrycki
0ae778c881 Merge pull request #788 from samjsharpe/master
Vagrantfile: Add support for VMWare Fusion provider
2013-06-18 18:35:25 -07:00
Andrew Munsell
1f8b679b18 Fix Mac OS X installation instructions URL 2013-06-18 19:19:07 -06:00
Guillaume J. Charmes
ee5df76579 Merge pull request #885 from dotcloud/remove_bsdtar
* Runtime: Remove bsdtar dependency
2013-06-18 17:24:26 -07:00
Guillaume J. Charmes
b431720dac Merge branch 'remove_bsdtar' of https://github.com/dotcloud/docker into remove_bsdtar 2013-06-18 17:23:22 -07:00
Guillaume J. Charmes
42ce68894a Fix issue within TestDelete. The archive is now consumed by graph functions 2013-06-18 17:22:32 -07:00
Guillaume J. Charmes
c063fc0238 Merge branch 'master' into fix_detach_eof
Conflicts:
	commands.go
2013-06-18 17:15:31 -07:00
Guillaume J. Charmes
0a9ac63a05 Merge pull request #916 from dotcloud/race_attach-fix
- Runtime: Fix race condition within Run command when attaching.
2013-06-18 17:13:38 -07:00
Guillaume J. Charmes
6dccdd657f remove offline mode from auth unit tests 2013-06-18 17:09:47 -07:00
Guillaume J. Charmes
34a434616a Merge branch 'master' into builder_server-3
Conflicts:
	buildfile.go
2013-06-18 16:12:30 -07:00
Elias Probst
bc9b91e501 Use the canonical 'ip' commands to make it easier for new 'iproute2' users to understand the usage. 2013-06-19 00:57:43 +02:00
Solomon Hykes
edbd3da33a Merge pull request #927 from dotcloud/nicer-build-output
* Builder: nicer output for 'docker build'
2013-06-18 15:48:00 -07:00
Guillaume J. Charmes
32e8f9beca Merge pull request #918 from dotcloud/hisotry_lookup
Add image lookup to history command
2013-06-18 15:36:05 -07:00
Guillaume J. Charmes
84ceeaa870 Update documentaiton 2013-06-18 14:36:35 -07:00
Solomon Hykes
cdeaba2acf Updated FIXME 2013-06-18 13:02:12 -07:00
Solomon Hykes
c0b82bd807 Fix incorrect docs for 'docker build' 2013-06-18 12:52:37 -07:00
Solomon Hykes
88e35b6f80 Merge pull request #926 from josephholsten/fix-znc-example
- Documentation: fix missing command in irc bouncer example
2013-06-18 12:37:21 -07:00
Guillaume J. Charmes
6e17cc45ea Fix merge issue 2013-06-18 12:33:06 -07:00
Solomon Hykes
cb9d0fd3bc Nicer output for 'docker build' 2013-06-18 12:26:56 -07:00
Victor Vieux
3adf9ce04e add basic support for unix sockets 2013-06-18 18:59:56 +00:00
Elias Probst
c2e95997d4 Fixed #923 by replacing the usage of 'ifconfig' with 'ip a' where appropriate and added a note to use 'ip a' instead of 'ifconfig' for a screencast transscript. 2013-06-18 19:55:59 +02:00
Guillaume J. Charmes
808faa6371 * API: Send all tags on History API call 2013-06-18 10:31:07 -07:00
Solomon Hykes
6f511ac29b Remove bsdtar dependency in various install scripts 2013-06-18 10:23:45 -07:00
Guillaume J. Charmes
3dc93e390a Remove useless goroutine 2013-06-18 10:10:03 -07:00
Guillaume J. Charmes
e2d034e488 Remove useless goroutine 2013-06-18 10:06:26 -07:00
Guillaume J. Charmes
86205540d8 Merge branch 'master' into race_attach-fix 2013-06-18 10:03:34 -07:00
Andy Rothfusz
702c3538a4 Merge pull request #921 from dhrp/docs-redirects-and-fixes
Fixes on documentation. LGTM
2013-06-18 09:58:24 -07:00
Victor Vieux
069a7c1e99 Merge pull request #914 from ToothlessGear/fix-version-output
* Client: Fix docker version's git commit output
2013-06-18 05:56:42 -07:00
Sam J Sharpe
8281a0fa1c Vagrantfile: Add support for VMWare Fusion provider
As a user who has blown $150 on VMWare Fusion and vagrant-vmware, I
would like to use my new shiny to hack on Docker. Docker already has a
multi-provider Vagrantfile, so adding another one presents little risk.

Known Issues:

- The docker install of a new kernel breaks the Vagrant shared folder.
    - This seems to be because the VMWare hgfs module doesn't build
      against a 3.8 kernel.
    - I don't believe that shared folder support is actually in use
2013-06-18 06:19:14 +01:00
Thatcher Peskens
3491d7d2f1 Fixed on documentation.
* replaced previously removed concepts/containers and concepts/introcution by a redirect
* moved orphan index/varable to workingwiththerepository
* added favicon to the layout.html
* added redirect_home which is a http refresh redirect. It works like a 301 for google
* fixed an issue in the layout that would make it break when on small screens
2013-06-17 20:16:56 -07:00
Solomon Hykes
0809f649d3 * Builder: upload progress bar
Fix progress bar
2013-06-17 18:49:16 -07:00
Guillaume J. Charmes
02a002d264 Update documentation 2013-06-17 18:41:13 -07:00
Guillaume J. Charmes
3bfc822578 * API: Add tag lookup to history command. Fixes #882 2013-06-17 18:39:30 -07:00
Guillaume J. Charmes
02c291d13b Fix bug on compression detection when chunck < 10bytes 2013-06-17 18:11:58 -07:00
Marcus Farkas
b25bcf1a66 fix docker version git output 2013-06-17 23:32:48 +00:00
Guillaume J. Charmes
fe204e6f48 - Runtime: Forbid parralel push/pull for a single image/repo. Fixes #311 2013-06-17 16:10:00 -07:00
Guillaume J. Charmes
2b6ca38728 Remove Run race condition 2013-06-17 15:45:08 -07:00
Guillaume J. Charmes
c106ed32ea Move the attach prevention from server to client 2013-06-17 15:40:04 -07:00
Joseph Anthony Pasquale Holsten
2626d88a21 fix missing command in irc bouncer example 2013-06-17 14:50:58 -07:00
Guillaume J. Charmes
3a0ffbc772 - Runtime: Fixes #884 enforce stdout/err sync by merging the stream 2013-06-17 14:44:35 -07:00
Guillaume J. Charmes
555552340d Merge branch 'master' into builder_server-3
Conflicts:
	buildfile_test.go
2013-06-17 14:01:32 -07:00
Guillaume J. Charmes
13e03a6911 Fix the auth tests and add the offline mode 2013-06-17 11:29:02 -07:00
Victor Vieux
fde82f448f use go 1.1 cookiejar and revome ResetClient 2013-06-17 18:13:40 +00:00
Guillaume J. Charmes
79b3265ef1 Merge branch 'master' into builder_server-3
Conflicts:
	buildfile.go
2013-06-17 11:09:53 -07:00
Solomon Hykes
fe88b5068d Merge branch 'master' into simpler-build-upload 2013-06-15 12:35:26 -07:00
Solomon Hykes
f50e40008f * Builder: added a regression test for #895 2013-06-15 11:35:56 -07:00
Solomon Hykes
061f8d12e0 * Builder: reorganized unit tests for better code reuse, and to test non-empty contexts 2013-06-15 11:07:49 -07:00
Solomon Hykes
38554fc2a7 * Builder: simplify the upload of the build context. Simply stream a tarball instead of multipart upload with 4 intermediary buffers. Simpler, less memory usage, less disk usage, and faster. 2013-06-15 09:38:18 -07:00
Solomon Hykes
cc7de8df75 Removed deprecated file builder_client.go 2013-06-15 09:30:52 -07:00
Guillaume J. Charmes
78f86ea502 Merge branch 'master' into builder_server-3
Conflicts:
	utils/utils.go
2013-06-14 17:08:39 -07:00
Guillaume J. Charmes
76a568fc97 Fix merge issue 2013-06-14 16:08:08 -07:00
Guillaume J. Charmes
abf85b2508 Merge branch 'master' into remove_bsdtar
Conflicts:
	docs/sources/contributing/devenvironment.rst
2013-06-14 14:34:30 -07:00
Guillaume J. Charmes
60fd7d686d Merge branch 'master' into improve_progressbar_pull 2013-06-14 12:01:40 -07:00
Guillaume J. Charmes
79fe864d9a Update docs 2013-06-14 10:58:16 -07:00
Guillaume J. Charmes
6f7de49aa8 Add unit tests for tar/untar with multiple compression + detection 2013-06-14 10:47:49 -07:00
Victor Vieux
90f6bdd6e4 update docs, remove config file on 401 2013-06-14 13:38:51 +00:00
Victor Vieux
9cc72ff1a9 fix auth in case you change your password on index.io 2013-06-14 09:53:48 +00:00
Guillaume J. Charmes
2f14dae83f Add build UT 2013-06-13 18:52:41 -07:00
Guillaume J. Charmes
f03ebc20aa Fix issue with ADD 2013-06-13 18:42:27 -07:00
Guillaume J. Charmes
4b4918f2a7 Merge branch 'master' into builder_server-3
Conflicts:
	buildfile.go
	commands.go
	docs/sources/api/docker_remote_api.rst
2013-06-13 18:11:22 -07:00
Guillaume J. Charmes
0425f65e63 Remove bsdtar by checking magic 2013-06-13 17:53:38 -07:00
Guillaume J. Charmes
f5fe3ce34e Remove run from non-running commmands 2013-06-13 15:08:53 -07:00
Guillaume J. Charmes
d0084ce5f2 Remove run from the ADD instruction 2013-06-13 14:57:50 -07:00
Guillaume J. Charmes
b38c6929be Updated build usage 2013-06-13 10:50:55 -07:00
Guillaume J. Charmes
8984aef899 Fix typo in docs 2013-06-10 09:32:31 -07:00
Guillaume J. Charmes
b103ac70bf Allow multiple tab/spaces between instructions and arguments 2013-06-10 09:31:59 -07:00
Guillaume J. Charmes
1e0738f63f Make the progressbar human readable 2013-06-06 18:42:52 -07:00
Guillaume J. Charmes
f355d33b5f Make the progressbar take the image size into consideration 2013-06-06 18:16:16 -07:00
Guillaume J. Charmes
2cc22de696 Update documentation for docker build 2013-06-06 16:48:36 -07:00
Guillaume J. Charmes
12c9b9b3c9 Implement build from git 2013-06-06 16:41:41 -07:00
Guillaume J. Charmes
a11e61677c Move the docker build URL form client to server, prepare for GIT support 2013-06-06 16:09:46 -07:00
Guillaume J. Charmes
01f446e908 Allow to docker build URL 2013-06-06 15:56:09 -07:00
Guillaume J. Charmes
f4a4cfd2cc Move isUrl to utils.IsURL 2013-06-06 15:50:09 -07:00
Guillaume J. Charmes
eaa2183d77 Fix issue EXPOSE override CMD within builder 2013-06-06 15:48:12 -07:00
Guillaume J. Charmes
31d2b258c1 Allow remote url to be passed to the ADD instruction within the builder 2013-06-06 15:40:46 -07:00
65 changed files with 2270 additions and 1307 deletions

View File

@@ -19,3 +19,7 @@ Andy Smith <github@anarkystic.com>
<victor.vieux@dotcloud.com> <dev@vvieux.com>
<dominik@honnef.co> <dominikh@fork-bomb.org>
Thatcher Peskens <thatcher@dotcloud.com>
<ehanchrow@ine.com> <eric.hanchrow@gmail.com>
Walter Stanish <walter@pratyeka.org>
<daniel@gasienica.ch> <dgasienica@zynga.com>
Roberto Hashioka <roberto_hashioka@hotmail.com>

23
AUTHORS
View File

@@ -6,6 +6,8 @@
Al Tobey <al@ooyala.com>
Alexey Shamrin <shamrin@gmail.com>
Andrea Luzzardi <aluzzardi@gmail.com>
Andreas Tiefenthaler <at@an-ti.eu>
Andrew Munsell <andrew@wizardapps.net>
Andy Rothfusz <github@metaliveblog.com>
Andy Smith <github@anarkystic.com>
Antony Messerli <amesserl@rackspace.com>
@@ -14,7 +16,9 @@ Brandon Liu <bdon@bdon.org>
Brian McCallister <brianm@skife.org>
Bruno Bigras <bigras.bruno@gmail.com>
Caleb Spare <cespare@gmail.com>
Calen Pennington <cale@edx.org>
Charles Hooper <charles.hooper@dotcloud.com>
Christopher Currie <codemonkey+github@gmail.com>
Daniel Gasienica <daniel@gasienica.ch>
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
Daniel Robinson <gottagetmac@gmail.com>
@@ -22,11 +26,14 @@ Daniel Von Fange <daniel@leancoder.com>
Dominik Honnef <dominik@honnef.co>
Don Spaulding <donspauldingii@gmail.com>
Dr Nic Williams <drnicwilliams@gmail.com>
Elias Probst <mail@eliasprobst.eu>
Eric Hanchrow <ehanchrow@ine.com>
Evan Wies <evan@neomantra.net>
ezbercih <cem.ezberci@gmail.com>
Flavio Castelli <fcastelli@suse.com>
Francisco Souza <f@souza.cc>
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
Gareth Rushgrove <gareth@morethanseven.net>
Guillaume J. Charmes <guillaume.charmes@dotcloud.com>
Harley Laue <losinggeneration@gmail.com>
Hunter Blanks <hunter@twilio.com>
@@ -34,14 +41,21 @@ Jeff Lindsay <progrium@gmail.com>
Jeremy Grosser <jeremy@synack.me>
Joffrey F <joffrey@dotcloud.com>
John Costa <john.costa@gmail.com>
Jon Wedaman <jweede@gmail.com>
Jonas Pfenniger <jonas@pfenniger.name>
Jonathan Rudenberg <jonathan@titanous.com>
Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
Julien Barbier <write0@gmail.com>
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
Ken Cochrane <kencochrane@gmail.com>
Kevin J. Lynagh <kevin@keminglabs.com>
kim0 <email.ahmedkamal@googlemail.com>
Kiran Gangadharan <kiran.daredevil@gmail.com>
Louis Opter <kalessin@kalessin.fr>
Marcus Farkas <toothlessgear@finitebox.com>
Mark McGranaghan <mmcgrana@gmail.com>
Maxim Treskin <zerthurd@gmail.com>
meejah <meejah@meejah.ca>
Michael Crosby <crosby.michael@gmail.com>
Mikhail Sobolev <mss@mawhrin.net>
Nate Jones <nate@endot.org>
@@ -50,18 +64,25 @@ Niall O'Higgins <niallo@unworkable.org>
odk- <github@odkurzacz.org>
Paul Bowsher <pbowsher@globalpersonals.co.uk>
Paul Hammond <paul@paulhammond.org>
Phil Spitler <pspitler@gmail.com>
Piotr Bogdan <ppbogdan@gmail.com>
Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
Robert Obryk <robryk@gmail.com>
Roberto Hashioka <roberto_hashioka@hotmail.com>
Sam Alba <sam.alba@gmail.com>
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
Shawn Siefkas <shawn.siefkas@meredith.com>
Silas Sewell <silas@sewell.org>
Solomon Hykes <solomon@dotcloud.com>
Sridhar Ratnakumar <sridharr@activestate.com>
Thatcher Peskens <thatcher@dotcloud.com>
Thomas Bikeev <thomas.bikeev@mac.com>
Thomas Hansen <thomas.hansen@gmail.com>
Tianon Gravi <admwiggin@gmail.com>
Tim Terhorst <mynamewastaken+git@gmail.com>
Troy Howard <thoward37@gmail.com>
Tobias Bieniek <Tobias.Bieniek@gmx.de>
unclejack <unclejacksons@gmail.com>
Victor Vieux <victor.vieux@dotcloud.com>
Vivek Agarwal <me@vivek.im>
Walter Stanish <walter@pratyeka.org>
Will Dietz <w@wdtz.org>

View File

@@ -1,5 +1,38 @@
# Changelog
## 0.4.6 (2013-06-22)
- Runtime: fix a bug which caused creation of empty images (and volumes) to crash.
## 0.4.5 (2013-06-21)
+ Builder: 'docker build git://URL' fetches and builds a remote git repository
* Runtime: 'docker ps -s' optionally prints container size
* Tests: Improved and simplified
- Runtime: fix a regression introduced in 0.4.3 which caused the logs command to fail.
- Builder: fix a regression when using ADD with single regular file.
## 0.4.4 (2013-06-19)
- Builder: fix a regression introduced in 0.4.3 which caused builds to fail on new clients.
## 0.4.3 (2013-06-19)
+ Builder: ADD of a local file will detect tar archives and unpack them
* Runtime: Remove bsdtar dependency
* Runtime: Add unix socket and multiple -H support
* Runtime: Prevent rm of running containers
* Runtime: Use go1.1 cookiejar
* Builder: ADD improvements: use tar for copy + automatically unpack local archives
* Builder: ADD uses tar/untar for copies instead of calling 'cp -ar'
* Builder: nicer output for 'docker build'
* Builder: fixed the behavior of ADD to be (mostly) reverse-compatible, predictable and well-documented.
* Client: HumanReadable ProgressBar sizes in pull
* Client: Fix docker version's git commit output
* API: Send all tags on History API call
* API: Add tag lookup to history command. Fixes #882
- Runtime: Fix issue detaching from running TTY container
- Runtime: Forbid parralel push/pull for a single image/repo. Fixes #311
- Runtime: Fix race condition within Run command when attaching.
- Builder: fix a bug which caused builds to fail if ADD was the first command
- Documentation: fix missing command in irc bouncer example
## 0.4.2 (2013-06-17)
- Packaging: Bumped version to work around an Ubuntu bug

2
FIXME
View File

@@ -16,7 +16,6 @@ to put them - so we put them here :)
* Unify build commands and regular commands
* Move source code into src/ subdir for clarity
* Clean up the Makefile, it's a mess
* docker buidl: show short IDs
* docker build: on non-existent local path for ADD, don't show full absolute path on the host
* mount into /dockerinit rather than /sbin/init
* docker tag foo REPO:TAG
@@ -34,3 +33,4 @@ to put them - so we put them here :)
* Caching after an ADD
* entry point config
* bring back git revision info, looks like it was lost
* Clean up the ProgressReader api, it's a PITA to use

View File

@@ -17,7 +17,7 @@ endif
GIT_COMMIT = $(shell git rev-parse --short HEAD)
GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES")
BUILD_OPTIONS = -ldflags "-X main.GIT_COMMIT $(GIT_COMMIT)$(GIT_STATUS)"
BUILD_OPTIONS = -a -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS) -d -w"
SRC_DIR := $(GOPATH)/src
@@ -33,7 +33,7 @@ all: $(DOCKER_BIN)
$(DOCKER_BIN): $(DOCKER_DIR)
@mkdir -p $(dir $@)
@(cd $(DOCKER_MAIN); go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@)
@(cd $(DOCKER_MAIN); CGO_ENABLED=0 go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@)
@echo $(DOCKER_BIN_RELATIVE) is created.
$(DOCKER_DIR):
@@ -74,6 +74,9 @@ endif
test: all
@(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS))
testall: all
@(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS))
fmt:
@gofmt -s -l -w .

View File

@@ -97,7 +97,7 @@ Quick install on Ubuntu 12.04 and 12.10
---------------------------------------
```bash
curl get.docker.io | sh -x
curl get.docker.io | sudo sh -x
```
Binary installs
@@ -108,7 +108,7 @@ Note that some methods are community contributions and not yet officially suppor
* [Ubuntu 12.04 and 12.10 (officially supported)](http://docs.docker.io/en/latest/installation/ubuntulinux/)
* [Arch Linux](http://docs.docker.io/en/latest/installation/archlinux/)
* [MacOS X (with Vagrant)](http://docs.docker.io/en/latest/installation/macos/)
* [Mac OS X (with Vagrant)](http://docs.docker.io/en/latest/installation/vagrant/)
* [Windows (with Vagrant)](http://docs.docker.io/en/latest/installation/windows/)
* [Amazon EC2 (with Vagrant)](http://docs.docker.io/en/latest/installation/amazon/)
@@ -181,7 +181,7 @@ Running an irc bouncer
----------------------
```bash
BOUNCER_ID=$(docker run -d -p 6667 -u irc shykes/znc $USER $PASSWORD)
BOUNCER_ID=$(docker run -d -p 6667 -u irc shykes/znc zncrun $USER $PASSWORD)
echo "Configure your irc client to connect to port $(docker port $BOUNCER_ID 6667) of this machine"
```
@@ -216,7 +216,8 @@ PORT=$(docker port $JOB 4444)
# Connect to the public port via the host's public address
# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { print $1 }')
# Replace *eth0* according to your local interface name.
IP=$(ip -o -4 addr list eth0 | perl -n -e 'if (m{inet\s([\d\.]+)\/\d+\s}xms) { print $1 }')
echo hello world | nc $IP $PORT
# Verify that the network connection worked
@@ -262,14 +263,14 @@ Setting up a dev environment
Instructions that have been verified to work on Ubuntu 12.10,
```bash
sudo apt-get -y install lxc wget bsdtar curl golang git
sudo apt-get -y install lxc curl xz-utils golang git
export GOPATH=~/go/
export PATH=$GOPATH/bin:$PATH
mkdir -p $GOPATH/src/github.com/dotcloud
cd $GOPATH/src/github.com/dotcloud
git clone git@github.com:dotcloud/docker.git
git clone https://github.com/dotcloud/docker.git
cd docker
go get -v github.com/dotcloud/docker/...

8
Vagrantfile vendored
View File

@@ -3,6 +3,7 @@
BOX_NAME = ENV['BOX_NAME'] || "ubuntu"
BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
VF_BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64_vmware_fusion.box"
AWS_REGION = ENV['AWS_REGION'] || "us-east-1"
AWS_AMI = ENV['AWS_AMI'] || "ami-d0f89fb9"
FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS']
@@ -67,6 +68,13 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
rs.image = /Ubuntu/
end
config.vm.provider :vmware_fusion do |f, override|
override.vm.box = BOX_NAME
override.vm.box_url = VF_BOX_URI
override.vm.synced_folder ".", "/vagrant", disabled: true
f.vmx["displayName"] = "docker"
end
config.vm.provider :virtualbox do |vb|
config.vm.box = BOX_NAME
config.vm.box_url = BOX_URI

112
api.go
View File

@@ -7,13 +7,19 @@ import (
"github.com/dotcloud/docker/utils"
"github.com/gorilla/mux"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
)
const APIVERSION = 1.2
const APIVERSION = 1.3
const DEFAULTHTTPHOST string = "127.0.0.1"
const DEFAULTHTTPPORT int = 4243
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
conn, _, err := w.(http.Hijacker).Hijack()
@@ -49,6 +55,10 @@ func httpError(w http.ResponseWriter, err error) {
http.Error(w, err.Error(), http.StatusConflict)
} else if strings.HasPrefix(err.Error(), "Impossible") {
http.Error(w, err.Error(), http.StatusNotAcceptable)
} else if strings.HasPrefix(err.Error(), "Wrong login/password") {
http.Error(w, err.Error(), http.StatusUnauthorized)
} else if strings.Contains(err.Error(), "hasn't been activated") {
http.Error(w, err.Error(), http.StatusForbidden)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
@@ -59,15 +69,15 @@ func writeJSON(w http.ResponseWriter, b []byte) {
w.Write(b)
}
// FIXME: Use stvconv.ParseBool() instead?
func getBoolParam(value string) (bool, error) {
if value == "1" || strings.ToLower(value) == "true" {
return true, nil
}
if value == "" || value == "0" || strings.ToLower(value) == "false" {
if value == "" {
return false, nil
}
return false, fmt.Errorf("Bad parameter")
ret, err := strconv.ParseBool(value)
if err != nil {
return false, fmt.Errorf("Bad parameter")
}
return ret, nil
}
func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
@@ -248,6 +258,10 @@ func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *h
if err != nil {
return err
}
size, err := getBoolParam(r.Form.Get("size"))
if err != nil {
return err
}
since := r.Form.Get("since")
before := r.Form.Get("before")
n, err := strconv.Atoi(r.Form.Get("limit"))
@@ -255,7 +269,7 @@ func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *h
n = -1
}
outs := srv.Containers(all, n, since, before)
outs := srv.Containers(all, size, n, since, before)
b, err := json.Marshal(outs)
if err != nil {
return err
@@ -715,34 +729,65 @@ func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r *
}
func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := r.ParseMultipartForm(4096); err != nil {
return err
if version < 1.3 {
return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.")
}
remote := r.FormValue("t")
remoteURL := r.FormValue("remote")
repoName := r.FormValue("t")
tag := ""
if strings.Contains(remote, ":") {
remoteParts := strings.Split(remote, ":")
if strings.Contains(repoName, ":") {
remoteParts := strings.Split(repoName, ":")
tag = remoteParts[1]
remote = remoteParts[0]
repoName = remoteParts[0]
}
dockerfile, _, err := r.FormFile("Dockerfile")
if err != nil {
return err
}
var context io.Reader
context, _, err := r.FormFile("Context")
if err != nil {
if err != http.ErrMissingFile {
if remoteURL == "" {
context = r.Body
} else if utils.IsGIT(remoteURL) {
if !strings.HasPrefix(remoteURL, "git://") {
remoteURL = "https://" + remoteURL
}
root, err := ioutil.TempDir("", "docker-build-git")
if err != nil {
return err
}
}
defer os.RemoveAll(root)
if output, err := exec.Command("git", "clone", remoteURL, root).CombinedOutput(); err != nil {
return fmt.Errorf("Error trying to use git: %s (%s)", err, output)
}
c, err := Tar(root, Bzip2)
if err != nil {
return err
}
context = c
} else if utils.IsURL(remoteURL) {
f, err := utils.Download(remoteURL, ioutil.Discard)
if err != nil {
return err
}
defer f.Body.Close()
dockerFile, err := ioutil.ReadAll(f.Body)
if err != nil {
return err
}
c, err := mkBuildContext(string(dockerFile), nil)
if err != nil {
return err
}
context = c
}
b := NewBuildFile(srv, utils.NewWriteFlusher(w))
if id, err := b.Build(dockerfile, context); err != nil {
id, err := b.Build(context)
if err != nil {
fmt.Fprintf(w, "Error build: %s\n", err)
} else if remote != "" {
srv.runtime.repositories.Set(remote, tag, id, false)
return err
}
if repoName != "" {
srv.runtime.repositories.Set(repoName, tag, id, false)
}
return nil
}
@@ -812,6 +857,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
localFct := fct
f := func(w http.ResponseWriter, r *http.Request) {
utils.Debugf("Calling %s %s", localMethod, localRoute)
if logging {
log.Println(r.Method, r.RequestURI)
}
@@ -832,6 +878,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
w.WriteHeader(http.StatusNotFound)
return
}
if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil {
httpError(w, err)
}
@@ -848,12 +895,21 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
return r, nil
}
func ListenAndServe(addr string, srv *Server, logging bool) error {
log.Printf("Listening for HTTP on %s\n", addr)
func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
log.Printf("Listening for HTTP on %s (%s)\n", addr, proto)
r, err := createRouter(srv, logging)
if err != nil {
return err
}
return http.ListenAndServe(addr, r)
l, e := net.Listen(proto, addr)
if e != nil {
return e
}
//as the daemon is launched as root, change to permission of the socket to allow non-root to connect
if proto == "unix" {
os.Chmod(addr, 0777)
}
httpSrv := http.Server{Addr: addr, Handler: r}
return httpSrv.Serve(l)
}

View File

@@ -1,7 +1,8 @@
package docker
type APIHistory struct {
ID string `json:"Id"`
ID string `json:"Id"`
Tags []string `json:",omitempty"`
Created int64
CreatedBy string `json:",omitempty"`
}

View File

@@ -17,6 +17,30 @@ import (
"time"
)
func TestGetBoolParam(t *testing.T) {
if ret, err := getBoolParam("true"); err != nil || !ret {
t.Fatalf("true -> true, nil | got %b %s", ret, err)
}
if ret, err := getBoolParam("True"); err != nil || !ret {
t.Fatalf("True -> true, nil | got %b %s", ret, err)
}
if ret, err := getBoolParam("1"); err != nil || !ret {
t.Fatalf("1 -> true, nil | got %b %s", ret, err)
}
if ret, err := getBoolParam(""); err != nil || ret {
t.Fatalf("\"\" -> false, nil | got %b %s", ret, err)
}
if ret, err := getBoolParam("false"); err != nil || ret {
t.Fatalf("false -> false, nil | got %b %s", ret, err)
}
if ret, err := getBoolParam("0"); err != nil || ret {
t.Fatalf("0 -> false, nil | got %b %s", ret, err)
}
if ret, err := getBoolParam("faux"); err == nil || ret {
t.Fatalf("faux -> false, err | got %b %s", ret, err)
}
}
func TestPostAuth(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {

View File

@@ -1,12 +1,18 @@
package docker
import (
"archive/tar"
"bufio"
"bytes"
"errors"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
)
type Archive io.Reader
@@ -20,6 +26,37 @@ const (
Xz
)
func DetectCompression(source []byte) Compression {
for _, c := range source[:10] {
utils.Debugf("%x", c)
}
sourceLen := len(source)
for compression, m := range map[Compression][]byte{
Bzip2: {0x42, 0x5A, 0x68},
Gzip: {0x1F, 0x8B, 0x08},
Xz: {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00},
} {
fail := false
if len(m) > sourceLen {
utils.Debugf("Len too short")
continue
}
i := 0
for _, b := range m {
if b != source[i] {
fail = true
break
}
i++
}
if !fail {
return compression
}
}
return Uncompressed
}
func (compression *Compression) Flag() string {
switch *compression {
case Bzip2:
@@ -46,15 +83,45 @@ func (compression *Compression) Extension() string {
return ""
}
// Tar creates an archive from the directory at `path`, and returns it as a
// stream of bytes.
func Tar(path string, compression Compression) (io.Reader, error) {
cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".")
return CmdStream(cmd)
return TarFilter(path, compression, nil)
}
// Tar creates an archive from the directory at `path`, only including files whose relative
// paths are included in `filter`. If `filter` is nil, then all files are included.
func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) {
args := []string{"tar", "-f", "-", "-C", path}
if filter == nil {
filter = []string{"."}
}
for _, f := range filter {
args = append(args, "-c"+compression.Flag(), f)
}
return CmdStream(exec.Command(args[0], args[1:]...))
}
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
// and unpacks it into the directory at `path`.
// The archive may be compressed with one of the following algorithgms:
// identity (uncompressed), gzip, bzip2, xz.
// FIXME: specify behavior when target path exists vs. doesn't exist.
func Untar(archive io.Reader, path string) error {
cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
cmd.Stdin = archive
if archive == nil {
return fmt.Errorf("Empty archive")
}
bufferedArchive := bufio.NewReaderSize(archive, 10)
buf, err := bufferedArchive.Peek(10)
if err != nil {
return err
}
compression := DetectCompression(buf)
utils.Debugf("Archive compression detected: %s", compression.Extension())
cmd := exec.Command("tar", "-f", "-", "-C", path, "-x"+compression.Flag())
cmd.Stdin = bufferedArchive
// Hardcode locale environment for predictable outcome regardless of host configuration.
// (see https://github.com/dotcloud/docker/issues/355)
cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"}
@@ -65,6 +132,18 @@ func Untar(archive io.Reader, path string) error {
return nil
}
// TarUntar is a convenience function which calls Tar and Untar, with
// the output of one piped into the other. If either Tar or Untar fails,
// TarUntar aborts and returns the error.
func TarUntar(src string, filter []string, dst string) error {
utils.Debugf("TarUntar(%s %s %s)", src, filter, dst)
archive, err := TarFilter(src, Uncompressed, filter)
if err != nil {
return err
}
return Untar(archive, dst)
}
// UntarPath is a convenience function which looks for an archive
// at filesystem path `src`, and unpacks it at `dst`.
func UntarPath(src, dst string) error {
@@ -82,11 +161,64 @@ func UntarPath(src, dst string) error {
// intermediary disk IO.
//
func CopyWithTar(src, dst string) error {
archive, err := Tar(src, Uncompressed)
srcSt, err := os.Stat(src)
if err != nil {
return err
}
return Untar(archive, dst)
if !srcSt.IsDir() {
return CopyFileWithTar(src, dst)
}
// Create dst, copy src's content into it
utils.Debugf("Creating dest directory: %s", dst)
if err := os.MkdirAll(dst, 0700); err != nil && !os.IsExist(err) {
return err
}
utils.Debugf("Calling TarUntar(%s, %s)", src, dst)
return TarUntar(src, nil, dst)
}
// CopyFileWithTar emulates the behavior of the 'cp' command-line
// for a single file. It copies a regular file from path `src` to
// path `dst`, and preserves all its metadata.
//
// If `dst` ends with a trailing slash '/', the final destination path
// will be `dst/base(src)`.
func CopyFileWithTar(src, dst string) error {
utils.Debugf("CopyFileWithTar(%s, %s)", src, dst)
srcSt, err := os.Stat(src)
if err != nil {
return err
}
if srcSt.IsDir() {
return fmt.Errorf("Can't copy a directory")
}
// Clean up the trailing /
if dst[len(dst)-1] == '/' {
dst = path.Join(dst, filepath.Base(src))
}
// Create the holding directory if necessary
if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) {
return err
}
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
hdr, err := tar.FileInfoHeader(srcSt, "")
if err != nil {
return err
}
hdr.Name = filepath.Base(dst)
if err := tw.WriteHeader(hdr); err != nil {
return err
}
srcF, err := os.Open(src)
if err != nil {
return err
}
if _, err := io.Copy(tw, srcF); err != nil {
return err
}
tw.Close()
return Untar(buf, filepath.Dir(dst))
}
// CmdStream executes a command, and returns its stdout as a stream.

View File

@@ -1,10 +1,13 @@
package docker
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"testing"
"time"
)
@@ -58,20 +61,58 @@ func TestCmdStreamGood(t *testing.T) {
}
}
func TestTarUntar(t *testing.T) {
archive, err := Tar(".", Uncompressed)
func tarUntar(t *testing.T, origin string, compression Compression) error {
archive, err := Tar(origin, compression)
if err != nil {
t.Fatal(err)
}
buf := make([]byte, 10)
if _, err := archive.Read(buf); err != nil {
return err
}
archive = io.MultiReader(bytes.NewReader(buf), archive)
detectedCompression := DetectCompression(buf)
if detectedCompression.Extension() != compression.Extension() {
return fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
}
tmp, err := ioutil.TempDir("", "docker-test-untar")
if err != nil {
t.Fatal(err)
return err
}
defer os.RemoveAll(tmp)
if err := Untar(archive, tmp); err != nil {
t.Fatal(err)
return err
}
if _, err := os.Stat(tmp); err != nil {
t.Fatalf("Error stating %s: %s", tmp, err.Error())
return err
}
return nil
}
func TestTarUntar(t *testing.T) {
origin, err := ioutil.TempDir("", "docker-test-untar-origin")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(origin)
if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(path.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
t.Fatal(err)
}
for _, c := range []Compression{
Uncompressed,
Gzip,
Bzip2,
Xz,
} {
if err := tarUntar(t, origin, c); err != nil {
t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
}
}
}

View File

@@ -82,7 +82,7 @@ func decodeAuth(authStr string) (*AuthConfig, error) {
func LoadConfig(rootPath string) (*AuthConfig, error) {
confFile := path.Join(rootPath, CONFIGFILE)
if _, err := os.Stat(confFile); err != nil {
return &AuthConfig{rootPath:rootPath}, ErrConfigFileMissing
return &AuthConfig{rootPath: rootPath}, ErrConfigFileMissing
}
b, err := ioutil.ReadFile(confFile)
if err != nil {
@@ -146,7 +146,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
if reqStatusCode == 201 {
status = "Account created. Please use the confirmation link we sent" +
" to your e-mail to activate it.\n"
" to your e-mail to activate it."
storeConfig = true
} else if reqStatusCode == 403 {
return "", fmt.Errorf("Login: Your account hasn't been activated. " +
@@ -165,10 +165,11 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
return "", err
}
if resp.StatusCode == 200 {
status = "Login Succeeded\n"
status = "Login Succeeded"
storeConfig = true
} else if resp.StatusCode == 401 {
if store {
authConfig.Email = ""
if err := SaveConfig(authConfig); err != nil {
return "", err
}

View File

@@ -10,8 +10,8 @@ import (
func TestEncodeAuth(t *testing.T) {
newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
authStr := EncodeAuth(newAuthConfig)
decAuthConfig, err := DecodeAuth(authStr)
authStr := encodeAuth(newAuthConfig)
decAuthConfig, err := decodeAuth(authStr)
if err != nil {
t.Fatal(err)
}
@@ -30,11 +30,11 @@ func TestLogin(t *testing.T) {
os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
defer os.Setenv("DOCKER_INDEX_URL", "")
authConfig := NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", "/tmp")
status, err := Login(authConfig)
status, err := Login(authConfig, false)
if err != nil {
t.Fatal(err)
}
if status != "Login Succeeded\n" {
if status != "Login Succeeded" {
t.Fatalf("Expected status \"Login Succeeded\", found \"%s\" instead", status)
}
}
@@ -50,17 +50,17 @@ func TestCreateAccount(t *testing.T) {
token := hex.EncodeToString(tokenBuffer)[:12]
username := "ut" + token
authConfig := NewAuthConfig(username, "test42", "docker-ut+"+token+"@example.com", "/tmp")
status, err := Login(authConfig)
status, err := Login(authConfig, false)
if err != nil {
t.Fatal(err)
}
expectedStatus := "Account created. Please use the confirmation link we sent" +
" to your e-mail to activate it.\n"
" to your e-mail to activate it."
if status != expectedStatus {
t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
}
status, err = Login(authConfig)
status, err = Login(authConfig, false)
if err == nil {
t.Fatalf("Expected error but found nil instead")
}

View File

@@ -1,314 +0,0 @@
package docker
import (
"bufio"
"encoding/json"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
"net/url"
"os"
"reflect"
"strings"
)
type builderClient struct {
cli *DockerCli
image string
maintainer string
config *Config
tmpContainers map[string]struct{}
tmpImages map[string]struct{}
needCommit bool
}
func (b *builderClient) clearTmp(containers, images map[string]struct{}) {
for i := range images {
if _, _, err := b.cli.call("DELETE", "/images/"+i, nil); err != nil {
utils.Debugf("%s", err)
}
utils.Debugf("Removing image %s", i)
}
}
func (b *builderClient) CmdFrom(name string) error {
obj, statusCode, err := b.cli.call("GET", "/images/"+name+"/json", nil)
if statusCode == 404 {
remote := name
var tag string
if strings.Contains(remote, ":") {
remoteParts := strings.Split(remote, ":")
tag = remoteParts[1]
remote = remoteParts[0]
}
var out io.Writer
if os.Getenv("DEBUG") != "" {
out = os.Stdout
} else {
out = &utils.NopWriter{}
}
if err := b.cli.stream("POST", "/images/create?fromImage="+remote+"&tag="+tag, nil, out); err != nil {
return err
}
obj, _, err = b.cli.call("GET", "/images/"+name+"/json", nil)
if err != nil {
return err
}
}
if err != nil {
return err
}
img := &APIID{}
if err := json.Unmarshal(obj, img); err != nil {
return err
}
b.image = img.ID
utils.Debugf("Using image %s", b.image)
return nil
}
func (b *builderClient) CmdMaintainer(name string) error {
b.needCommit = true
b.maintainer = name
return nil
}
func (b *builderClient) CmdRun(args string) error {
if b.image == "" {
return fmt.Errorf("Please provide a source image with `from` prior to run")
}
config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
if err != nil {
return err
}
cmd, env := b.config.Cmd, b.config.Env
b.config.Cmd = nil
MergeConfig(b.config, config)
body, statusCode, err := b.cli.call("POST", "/images/getCache", &APIImageConfig{ID: b.image, Config: b.config})
if err != nil {
if statusCode != 404 {
return err
}
}
if statusCode != 404 {
apiID := &APIID{}
if err := json.Unmarshal(body, apiID); err != nil {
return err
}
utils.Debugf("Use cached version")
b.image = apiID.ID
return nil
}
cid, err := b.run()
if err != nil {
return err
}
b.config.Cmd, b.config.Env = cmd, env
return b.commit(cid)
}
func (b *builderClient) CmdEnv(args string) error {
b.needCommit = true
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid ENV format")
}
key := strings.Trim(tmp[0], " ")
value := strings.Trim(tmp[1], " ")
for i, elem := range b.config.Env {
if strings.HasPrefix(elem, key+"=") {
b.config.Env[i] = key + "=" + value
return nil
}
}
b.config.Env = append(b.config.Env, key+"="+value)
return nil
}
func (b *builderClient) CmdCmd(args string) error {
b.needCommit = true
var cmd []string
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
b.config.Cmd = []string{"/bin/sh", "-c", args}
} else {
b.config.Cmd = cmd
}
return nil
}
func (b *builderClient) CmdExpose(args string) error {
ports := strings.Split(args, " ")
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
return nil
}
func (b *builderClient) CmdInsert(args string) error {
// tmp := strings.SplitN(args, "\t ", 2)
// sourceUrl, destPath := tmp[0], tmp[1]
// v := url.Values{}
// v.Set("url", sourceUrl)
// v.Set("path", destPath)
// body, _, err := b.cli.call("POST", "/images/insert?"+v.Encode(), nil)
// if err != nil {
// return err
// }
// apiId := &APIId{}
// if err := json.Unmarshal(body, apiId); err != nil {
// return err
// }
// FIXME: Reimplement this, we need to retrieve the resulting Id
return fmt.Errorf("INSERT not implemented")
}
func (b *builderClient) run() (string, error) {
if b.image == "" {
return "", fmt.Errorf("Please provide a source image with `from` prior to run")
}
b.config.Image = b.image
body, _, err := b.cli.call("POST", "/containers/create", b.config)
if err != nil {
return "", err
}
apiRun := &APIRun{}
if err := json.Unmarshal(body, apiRun); err != nil {
return "", err
}
for _, warning := range apiRun.Warnings {
fmt.Fprintln(os.Stderr, "WARNING: ", warning)
}
//start the container
_, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/start", nil)
if err != nil {
return "", err
}
b.tmpContainers[apiRun.ID] = struct{}{}
// Wait for it to finish
body, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/wait", nil)
if err != nil {
return "", err
}
apiWait := &APIWait{}
if err := json.Unmarshal(body, apiWait); err != nil {
return "", err
}
if apiWait.StatusCode != 0 {
return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, apiWait.StatusCode)
}
return apiRun.ID, nil
}
func (b *builderClient) commit(id string) error {
if b.image == "" {
return fmt.Errorf("Please provide a source image with `from` prior to run")
}
b.config.Image = b.image
if id == "" {
cmd := b.config.Cmd
b.config.Cmd = []string{"true"}
cid, err := b.run()
if err != nil {
return err
}
id = cid
b.config.Cmd = cmd
}
// Commit the container
v := url.Values{}
v.Set("container", id)
v.Set("author", b.maintainer)
body, _, err := b.cli.call("POST", "/commit?"+v.Encode(), b.config)
if err != nil {
return err
}
apiID := &APIID{}
if err := json.Unmarshal(body, apiID); err != nil {
return err
}
b.tmpImages[apiID.ID] = struct{}{}
b.image = apiID.ID
b.needCommit = false
return nil
}
func (b *builderClient) Build(dockerfile, context io.Reader) (string, error) {
defer b.clearTmp(b.tmpContainers, b.tmpImages)
file := bufio.NewReader(dockerfile)
for {
line, err := file.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return "", err
}
line = strings.Replace(strings.TrimSpace(line), " ", " ", 1)
// Skip comments and empty line
if len(line) == 0 || line[0] == '#' {
continue
}
tmp := strings.SplitN(line, " ", 2)
if len(tmp) != 2 {
return "", fmt.Errorf("Invalid Dockerfile format")
}
instruction := strings.ToLower(strings.Trim(tmp[0], " "))
arguments := strings.Trim(tmp[1], " ")
fmt.Fprintf(os.Stderr, "%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
if !exists {
fmt.Fprintf(os.Stderr, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
}
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
if ret != nil {
return "", ret.(error)
}
fmt.Fprintf(os.Stderr, "===> %v\n", b.image)
}
if b.needCommit {
if err := b.commit(""); err != nil {
return "", err
}
}
if b.image != "" {
// The build is successful, keep the temporary containers and images
for i := range b.tmpImages {
delete(b.tmpImages, i)
}
for i := range b.tmpContainers {
delete(b.tmpContainers, i)
}
fmt.Fprintf(os.Stderr, "Build finished. image id: %s\n", b.image)
return b.image, nil
}
return "", fmt.Errorf("An error occured during the build\n")
}
func NewBuilderClient(addr string, port int) BuildFile {
return &builderClient{
cli: NewDockerCli(addr, port),
config: &Config{},
tmpContainers: make(map[string]struct{}),
tmpImages: make(map[string]struct{}),
}
}

View File

@@ -14,7 +14,7 @@ import (
)
type BuildFile interface {
Build(io.Reader, io.Reader) (string, error)
Build(io.Reader) (string, error)
CmdFrom(string) error
CmdRun(string) error
}
@@ -101,6 +101,7 @@ func (b *buildFile) CmdRun(args string) error {
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
return err
} else if cache != nil {
fmt.Fprintf(b.out, " ---> Using cache\n")
utils.Debugf("[BUILDER] Use cached version")
b.image = cache.ID
return nil
@@ -124,8 +125,8 @@ func (b *buildFile) CmdEnv(args string) error {
if len(tmp) != 2 {
return fmt.Errorf("Invalid ENV format")
}
key := strings.Trim(tmp[0], " ")
value := strings.Trim(tmp[1], " ")
key := strings.Trim(tmp[0], " \t")
value := strings.Trim(tmp[1], " \t")
for i, elem := range b.config.Env {
if strings.HasPrefix(elem, key+"=") {
@@ -164,44 +165,28 @@ func (b *buildFile) CmdCopy(args string) error {
return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
}
func (b *buildFile) CmdAdd(args string) error {
if b.context == "" {
return fmt.Errorf("No context given. Impossible to use ADD")
}
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid ADD format")
}
orig := strings.Trim(tmp[0], " ")
dest := strings.Trim(tmp[1], " ")
cmd := b.config.Cmd
// Create the container and start it
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
b.config.Image = b.image
container, err := b.builder.Create(b.config)
func (b *buildFile) addRemote(container *Container, orig, dest string) error {
file, err := utils.Download(orig, ioutil.Discard)
if err != nil {
return err
}
b.tmpContainers[container.ID] = struct{}{}
defer file.Body.Close()
if err := container.EnsureMounted(); err != nil {
return err
}
defer container.Unmount()
return container.Inject(file.Body, dest)
}
func (b *buildFile) addContext(container *Container, orig, dest string) error {
origPath := path.Join(b.context, orig)
destPath := path.Join(container.RootfsPath(), dest)
// Preserve the trailing '/'
if dest[len(dest)-1] == '/' {
destPath = destPath + "/"
}
fi, err := os.Stat(origPath)
if err != nil {
return err
}
if fi.IsDir() {
if err := os.MkdirAll(destPath, 0700); err != nil {
return err
}
if err := CopyWithTar(origPath, destPath); err != nil {
return err
}
@@ -216,6 +201,46 @@ func (b *buildFile) CmdAdd(args string) error {
return err
}
}
return nil
}
func (b *buildFile) CmdAdd(args string) error {
if b.context == "" {
return fmt.Errorf("No context given. Impossible to use ADD")
}
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid ADD format")
}
orig := strings.Trim(tmp[0], " \t")
dest := strings.Trim(tmp[1], " \t")
cmd := b.config.Cmd
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
b.config.Image = b.image
// Create the container and start it
container, err := b.builder.Create(b.config)
if err != nil {
return err
}
b.tmpContainers[container.ID] = struct{}{}
if err := container.EnsureMounted(); err != nil {
return err
}
defer container.Unmount()
if utils.IsURL(orig) {
if err := b.addRemote(container, orig, dest); err != nil {
return err
}
} else {
if err := b.addContext(container, orig, dest); err != nil {
return err
}
}
if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
return err
}
@@ -235,6 +260,7 @@ func (b *buildFile) run() (string, error) {
return "", err
}
b.tmpContainers[c.ID] = struct{}{}
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID))
//start the container
if err := c.Start(); err != nil {
@@ -256,31 +282,31 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
}
b.config.Image = b.image
if id == "" {
cmd := b.config.Cmd
b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
return err
} else if cache != nil {
fmt.Fprintf(b.out, " ---> Using cache\n")
utils.Debugf("[BUILDER] Use cached version")
b.image = cache.ID
return nil
} else {
utils.Debugf("[BUILDER] Cache miss")
}
// Create the container and start it
container, err := b.builder.Create(b.config)
if err != nil {
return err
}
b.tmpContainers[container.ID] = struct{}{}
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
id = container.ID
if err := container.EnsureMounted(); err != nil {
return err
}
defer container.Unmount()
id = container.ID
}
container := b.runtime.Get(id)
@@ -301,19 +327,25 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
return nil
}
func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
if context != nil {
name, err := ioutil.TempDir("/tmp", "docker-build")
if err != nil {
return "", err
}
if err := Untar(context, name); err != nil {
return "", err
}
defer os.RemoveAll(name)
b.context = name
func (b *buildFile) Build(context io.Reader) (string, error) {
// FIXME: @creack any reason for using /tmp instead of ""?
// FIXME: @creack "name" is a terrible variable name
name, err := ioutil.TempDir("/tmp", "docker-build")
if err != nil {
return "", err
}
if err := Untar(context, name); err != nil {
return "", err
}
defer os.RemoveAll(name)
b.context = name
dockerfile, err := os.Open(path.Join(name, "Dockerfile"))
if err != nil {
return "", fmt.Errorf("Can't build a directory with no Dockerfile")
}
// FIXME: "file" is also a terrible variable name ;)
file := bufio.NewReader(dockerfile)
stepN := 0
for {
line, err := file.ReadString('\n')
if err != nil {
@@ -323,7 +355,7 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
return "", err
}
}
line = strings.Replace(strings.TrimSpace(line), " ", " ", 1)
line = strings.Trim(strings.Replace(line, "\t", " ", -1), " \t\r\n")
// Skip comments and empty line
if len(line) == 0 || line[0] == '#' {
continue
@@ -334,12 +366,13 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
}
instruction := strings.ToLower(strings.Trim(tmp[0], " "))
arguments := strings.Trim(tmp[1], " ")
fmt.Fprintf(b.out, "%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)
stepN += 1
// FIXME: only count known instructions as build steps
fmt.Fprintf(b.out, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments)
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
if !exists {
fmt.Fprintf(b.out, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
fmt.Fprintf(b.out, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction))
continue
}
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
@@ -347,10 +380,10 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
return "", ret.(error)
}
fmt.Fprintf(b.out, "===> %v\n", b.image)
fmt.Fprintf(b.out, " ---> %v\n", utils.TruncateID(b.image))
}
if b.image != "" {
fmt.Fprintf(b.out, "Build successful.\n===> %s\n", b.image)
fmt.Fprintf(b.out, "Successfully built %s\n", utils.TruncateID(b.image))
return b.image, nil
}
return "", fmt.Errorf("An error occured during the build\n")

View File

@@ -1,37 +1,91 @@
package docker
import (
"github.com/dotcloud/docker/utils"
"strings"
"io/ioutil"
"testing"
)
const Dockerfile = `
# VERSION 0.1
# DOCKER-VERSION 0.2
// mkTestContext generates a build context from the contents of the provided dockerfile.
// This context is suitable for use as an argument to BuildFile.Build()
func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive {
context, err := mkBuildContext(dockerfile, files)
if err != nil {
t.Fatal(err)
}
return context
}
from ` + unitTestImageName + `
// A testContextTemplate describes a build context and how to test it
type testContextTemplate struct {
// Contents of the Dockerfile
dockerfile string
// Additional files in the context, eg [][2]string{"./passwd", "gordon"}
files [][2]string
}
// A table of all the contexts to build and test.
// A new docker runtime will be created and torn down for each context.
var testContexts []testContextTemplate = []testContextTemplate{
{
`
from docker-ut
run sh -c 'echo root:testpass > /tmp/passwd'
run mkdir -p /var/run/sshd
`
run [ "$(cat /tmp/passwd)" = "root:testpass" ]
run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
`,
nil,
},
const DockerfileNoNewLine = `
# VERSION 0.1
# DOCKER-VERSION 0.2
{
`
from docker-ut
add foo /usr/lib/bla/bar
run [ "$(cat /usr/lib/bla/bar)" = 'hello world!' ]
`,
[][2]string{{"foo", "hello world!"}},
},
from ` + unitTestImageName + `
run sh -c 'echo root:testpass > /tmp/passwd'
run mkdir -p /var/run/sshd`
{
`
from docker-ut
add f /
run [ "$(cat /f)" = "hello" ]
add f /abc
run [ "$(cat /abc)" = "hello" ]
add f /x/y/z
run [ "$(cat /x/y/z)" = "hello" ]
add f /x/y/d/
run [ "$(cat /x/y/d/f)" = "hello" ]
add d /
run [ "$(cat /ga)" = "bu" ]
add d /somewhere
run [ "$(cat /somewhere/ga)" = "bu" ]
add d /anotherplace/
run [ "$(cat /anotherplace/ga)" = "bu" ]
add d /somewheeeere/over/the/rainbooow
run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ]
`,
[][2]string{
{"f", "hello"},
{"d/ga", "bu"},
},
},
// FIXME: test building with a context
// FIXME: test building with a local ADD as first command
{
`
from docker-ut
env FOO BAR
run [ "$FOO" = "BAR" ]
`,
nil,
},
}
// FIXME: test building with 2 successive overlapping ADD commands
func TestBuild(t *testing.T) {
dockerfiles := []string{Dockerfile, DockerfileNoNewLine}
for _, Dockerfile := range dockerfiles {
for _, ctx := range testContexts {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
@@ -40,50 +94,9 @@ func TestBuild(t *testing.T) {
srv := &Server{runtime: runtime}
buildfile := NewBuildFile(srv, &utils.NopWriter{})
imgID, err := buildfile.Build(strings.NewReader(Dockerfile), nil)
if err != nil {
buildfile := NewBuildFile(srv, ioutil.Discard)
if _, err := buildfile.Build(mkTestContext(ctx.dockerfile, ctx.files, t)); err != nil {
t.Fatal(err)
}
builder := NewBuilder(runtime)
container, err := builder.Create(
&Config{
Image: imgID,
Cmd: []string{"cat", "/tmp/passwd"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
output, err := container.Output()
if err != nil {
t.Fatal(err)
}
if string(output) != "root:testpass\n" {
t.Fatalf("Unexpected output. Read '%s', expected '%s'", output, "root:testpass\n")
}
container2, err := builder.Create(
&Config{
Image: imgID,
Cmd: []string{"ls", "-d", "/var/run/sshd"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container2)
output, err = container2.Output()
if err != nil {
t.Fatal(err)
}
if string(output) != "/var/run/sshd\n" {
t.Fatal("/var/run/sshd has not been created")
}
}
}

View File

@@ -1,6 +1,7 @@
package docker
import (
"archive/tar"
"bytes"
"encoding/json"
"flag"
@@ -10,14 +11,12 @@ import (
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"mime/multipart"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"os/signal"
"path"
"path/filepath"
"reflect"
"regexp"
@@ -29,7 +28,7 @@ import (
"unicode"
)
const VERSION = "0.4.2"
const VERSION = "0.4.6"
var (
GITCOMMIT string
@@ -40,8 +39,8 @@ func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
return reflect.TypeOf(cli).MethodByName(methodName)
}
func ParseCommands(addr string, port int, args ...string) error {
cli := NewDockerCli(addr, port)
func ParseCommands(proto, addr string, args ...string) error {
cli := NewDockerCli(proto, addr)
if len(args) > 0 {
method, exists := cli.getMethod(args[0])
@@ -74,7 +73,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
return nil
}
}
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.host, cli.port)
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[tcp://%s:%d]: tcp://host:port to bind/connect to or unix://path/to/socker to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTHTTPHOST, DEFAULTHTTPPORT)
for _, command := range [][2]string{
{"attach", "Attach to a running container"},
{"build", "Build a container from a Dockerfile"},
@@ -131,8 +130,33 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
return nil
}
// mkBuildContext returns an archive of an empty context with the contents
// of `dockerfile` at the path ./Dockerfile
func mkBuildContext(dockerfile string, files [][2]string) (Archive, error) {
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
files = append(files, [2]string{"Dockerfile", dockerfile})
for _, file := range files {
name, content := file[0], file[1]
hdr := &tar.Header{
Name: name,
Size: int64(len(content)),
}
if err := tw.WriteHeader(hdr); err != nil {
return nil, err
}
if _, err := tw.Write([]byte(content)); err != nil {
return nil, err
}
}
if err := tw.Close(); err != nil {
return nil, err
}
return buf, nil
}
func (cli *DockerCli) CmdBuild(args ...string) error {
cmd := Subcmd("build", "[OPTIONS] PATH | -", "Build a new container image from the source code at PATH")
cmd := Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH")
tag := cmd.String("t", "", "Tag to be applied to the resulting image in case of success")
if err := cmd.Parse(args); err != nil {
return nil
@@ -143,76 +167,55 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
}
var (
multipartBody io.Reader
file io.ReadCloser
contextPath string
context Archive
isRemote bool
err error
)
// Init the needed component for the Multipart
buff := bytes.NewBuffer([]byte{})
multipartBody = buff
w := multipart.NewWriter(buff)
boundary := strings.NewReader("\r\n--" + w.Boundary() + "--\r\n")
compression := Bzip2
if cmd.Arg(0) == "-" {
file = os.Stdin
// As a special case, 'docker build -' will build from an empty context with the
// contents of stdin as a Dockerfile
dockerfile, err := ioutil.ReadAll(os.Stdin)
if err != nil {
return err
}
context, err = mkBuildContext(string(dockerfile), nil)
} else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) {
isRemote = true
} else {
// Send Dockerfile from arg/Dockerfile (deprecate later)
f, err := os.Open(path.Join(cmd.Arg(0), "Dockerfile"))
if err != nil {
return err
}
file = f
// Send context from arg
// Create a FormFile multipart for the context if needed
// FIXME: Use NewTempArchive in order to have the size and avoid too much memory usage?
context, err := Tar(cmd.Arg(0), compression)
if err != nil {
return err
}
// NOTE: Do this in case '.' or '..' is input
absPath, err := filepath.Abs(cmd.Arg(0))
if err != nil {
return err
}
wField, err := w.CreateFormFile("Context", filepath.Base(absPath)+"."+compression.Extension())
if err != nil {
return err
}
// FIXME: Find a way to have a progressbar for the upload too
context, err = Tar(cmd.Arg(0), Uncompressed)
}
var body io.Reader
// Setup an upload progress bar
// FIXME: ProgressReader shouldn't be this annoyning to use
if context != nil {
sf := utils.NewStreamFormatter(false)
io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, sf.FormatProgress("Caching Context", "%v/%v (%v)"), sf))
multipartBody = io.MultiReader(multipartBody, boundary)
body = utils.ProgressReader(ioutil.NopCloser(context), 0, os.Stderr, sf.FormatProgress("Uploading context", "%v bytes%0.0s%0.0s"), sf)
}
// Create a FormFile multipart for the Dockerfile
wField, err := w.CreateFormFile("Dockerfile", "Dockerfile")
if err != nil {
return err
}
io.Copy(wField, file)
multipartBody = io.MultiReader(multipartBody, boundary)
// Upload the build context
v := &url.Values{}
v.Set("t", *tag)
// Send the multipart request with correct content-type
req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), multipartBody)
if isRemote {
v.Set("remote", cmd.Arg(0))
}
req, err := http.NewRequest("POST", fmt.Sprintf("/v%g/build?%s", APIVERSION, v.Encode()), body)
if err != nil {
return err
}
req.Header.Set("Content-Type", w.FormDataContentType())
if contextPath != "" {
req.Header.Set("X-Docker-Context-Compression", compression.Flag())
fmt.Println("Uploading Context...")
if context != nil {
req.Header.Set("Content-Type", "application/tar")
}
resp, err := http.DefaultClient.Do(req)
dial, err := net.Dial(cli.proto, cli.addr)
if err != nil {
return err
}
clientconn := httputil.NewClientConn(dial, nil)
resp, err := clientconn.Do(req)
defer clientconn.Close()
if err != nil {
return err
}
defer resp.Body.Close()
// Check for errors
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
body, err := ioutil.ReadAll(resp.Body)
@@ -311,6 +314,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
email = cli.authConfig.Email
}
} else {
password = cli.authConfig.Password
email = cli.authConfig.Email
}
term.RestoreTerminal(oldState)
@@ -319,7 +323,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
cli.authConfig.Password = password
cli.authConfig.Email = email
body, _, err := cli.call("POST", "/auth", cli.authConfig)
body, statusCode, err := cli.call("POST", "/auth", cli.authConfig)
if statusCode == 401 {
cli.authConfig.Username = ""
cli.authConfig.Password = ""
cli.authConfig.Email = ""
auth.SaveConfig(cli.authConfig)
return err
}
if err != nil {
return err
}
@@ -332,7 +343,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
}
auth.SaveConfig(cli.authConfig)
if out2.Status != "" {
fmt.Print(out2.Status)
fmt.Println(out2.Status)
}
return nil
}
@@ -627,7 +638,10 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
fmt.Fprintln(w, "ID\tCREATED\tCREATED BY")
for _, out := range outs {
fmt.Fprintf(w, "%s\t%s ago\t%s\n", out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.CreatedBy)
if out.Tags != nil {
out.ID = out.Tags[0]
}
fmt.Fprintf(w, "%s \t%s ago\t%s\n", out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.CreatedBy)
}
w.Flush()
return nil
@@ -864,6 +878,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
func (cli *DockerCli) CmdPs(args ...string) error {
cmd := Subcmd("ps", "[OPTIONS]", "List containers")
quiet := cmd.Bool("q", false, "Only display numeric IDs")
size := cmd.Bool("s", false, "Display sizes")
all := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.")
noTrunc := cmd.Bool("notrunc", false, "Don't truncate output")
nLatest := cmd.Bool("l", false, "Show only the latest created container, include non-running ones.")
@@ -890,6 +905,9 @@ func (cli *DockerCli) CmdPs(args ...string) error {
if *before != "" {
v.Set("before", *before)
}
if *size {
v.Set("size", "1")
}
body, _, err := cli.call("GET", "/containers/json?"+v.Encode(), nil)
if err != nil {
@@ -903,7 +921,12 @@ func (cli *DockerCli) CmdPs(args ...string) error {
}
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
if !*quiet {
fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tSIZE")
fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS")
if *size {
fmt.Fprintln(w, "\tSIZE")
} else {
fmt.Fprint(w, "\n")
}
}
for _, out := range outs {
@@ -913,10 +936,14 @@ func (cli *DockerCli) CmdPs(args ...string) error {
} else {
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
}
if out.SizeRootFs > 0 {
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
if *size {
if out.SizeRootFs > 0 {
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
} else {
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.SizeRw))
}
} else {
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.SizeRw))
fmt.Fprint(w, "\n")
}
} else {
if *noTrunc {
@@ -1028,10 +1055,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
return nil
}
if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", nil, os.Stdout); err != nil {
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", false, nil, os.Stdout); err != nil {
return err
}
if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", nil, os.Stderr); err != nil {
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", false, nil, os.Stderr); err != nil {
return err
}
return nil
@@ -1058,37 +1085,22 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
return err
}
splitStderr := container.Config.Tty
connections := 1
if splitStderr {
connections += 1
if !container.State.Running {
return fmt.Errorf("Impossible to attach to a stopped container, start it first")
}
chErrors := make(chan error, connections)
if container.Config.Tty {
cli.monitorTtySize(cmd.Arg(0))
}
if splitStderr {
go func() {
chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?stream=1&stderr=1", false, nil, os.Stderr)
}()
}
v := url.Values{}
v.Set("stream", "1")
v.Set("stdin", "1")
v.Set("stdout", "1")
if !splitStderr {
v.Set("stderr", "1")
}
go func() {
chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout)
}()
for connections > 0 {
err := <-chErrors
if err != nil {
return err
}
connections -= 1
v.Set("stderr", "1")
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout); err != nil {
return err
}
return nil
}
@@ -1257,16 +1269,6 @@ func (cli *DockerCli) CmdRun(args ...string) error {
fmt.Fprintln(os.Stderr, "WARNING: ", warning)
}
splitStderr := !config.Tty
connections := 0
if config.AttachStdin || config.AttachStdout || (!splitStderr && config.AttachStderr) {
connections += 1
}
if splitStderr && config.AttachStderr {
connections += 1
}
//start the container
_, _, err = cli.call("POST", "/containers/"+out.ID+"/start", nil)
if err != nil {
@@ -1275,19 +1277,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
if !config.AttachStdout && !config.AttachStderr {
fmt.Println(out.ID)
}
if connections > 0 {
chErrors := make(chan error, connections)
} else {
if config.Tty {
cli.monitorTtySize(out.ID)
}
if splitStderr && config.AttachStderr {
go func() {
chErrors <- cli.hijack("POST", "/containers/"+out.ID+"/attach?logs=1&stream=1&stderr=1", config.Tty, nil, os.Stderr)
}()
}
v := url.Values{}
v.Set("logs", "1")
v.Set("stream", "1")
@@ -1298,19 +1292,12 @@ func (cli *DockerCli) CmdRun(args ...string) error {
if config.AttachStdout {
v.Set("stdout", "1")
}
if !splitStderr && config.AttachStderr {
if config.AttachStderr {
v.Set("stderr", "1")
}
go func() {
chErrors <- cli.hijack("POST", "/containers/"+out.ID+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout)
}()
for connections > 0 {
err := <-chErrors
if err != nil {
utils.Debugf("Error hijack: %s", err)
return err
}
connections -= 1
if err := cli.hijack("POST", "/containers/"+out.ID+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout); err != nil {
utils.Debugf("Error hijack: %s", err)
return err
}
}
return nil
@@ -1339,7 +1326,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
params = bytes.NewBuffer(buf)
}
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), params)
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params)
if err != nil {
return nil, -1, err
}
@@ -1349,7 +1336,13 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
} else if method == "POST" {
req.Header.Set("Content-Type", "plain/text")
}
resp, err := http.DefaultClient.Do(req)
dial, err := net.Dial(cli.proto, cli.addr)
if err != nil {
return nil, -1, err
}
clientconn := httputil.NewClientConn(dial, nil)
resp, err := clientconn.Do(req)
defer clientconn.Close()
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
@@ -1374,7 +1367,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
if (method == "POST" || method == "PUT") && in == nil {
in = bytes.NewReader([]byte{})
}
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), in)
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in)
if err != nil {
return err
}
@@ -1382,7 +1375,13 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
if method == "POST" {
req.Header.Set("Content-Type", "plain/text")
}
resp, err := http.DefaultClient.Do(req)
dial, err := net.Dial(cli.proto, cli.addr)
if err != nil {
return err
}
clientconn := httputil.NewClientConn(dial, nil)
resp, err := clientconn.Do(req)
defer clientconn.Close()
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
@@ -1390,6 +1389,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
@@ -1427,19 +1427,24 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
}
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error {
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
if err != nil {
return err
}
req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
req.Header.Set("Content-Type", "plain/text")
dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.host, cli.port))
dial, err := net.Dial(cli.proto, cli.addr)
if err != nil {
return err
}
clientconn := httputil.NewClientConn(dial, nil)
clientconn.Do(req)
defer clientconn.Close()
// Server hijacks the connection, error 'connection closed' expected
clientconn.Do(req)
rwc, br := clientconn.Hijack()
defer rwc.Close()
@@ -1515,13 +1520,13 @@ func Subcmd(name, signature, description string) *flag.FlagSet {
return flags
}
func NewDockerCli(addr string, port int) *DockerCli {
func NewDockerCli(proto, addr string) *DockerCli {
authConfig, _ := auth.LoadConfig(os.Getenv("HOME"))
return &DockerCli{addr, port, authConfig}
return &DockerCli{proto, addr, authConfig}
}
type DockerCli struct {
host string
port int
proto string
addr string
authConfig *auth.AuthConfig
}

View File

@@ -76,7 +76,7 @@ type Config struct {
}
func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet, error) {
cmd := Subcmd("run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
if len(args) > 0 && args[0] != "--help" {
cmd.SetOutput(ioutil.Discard)
}
@@ -632,7 +632,6 @@ func (container *Container) waitLxc() error {
}
time.Sleep(500 * time.Millisecond)
}
panic("Unreachable")
}
func (container *Container) monitor() {
@@ -821,8 +820,6 @@ func (container *Container) WaitTimeout(timeout time.Duration) error {
case <-done:
return nil
}
panic("Unreachable")
}
func (container *Container) EnsureMounted() error {

View File

@@ -556,6 +556,30 @@ func TestKillDifferentUser(t *testing.T) {
}
}
// Test that creating a container with a volume doesn't crash. Regression test for #995.
func TestCreateVolume(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
config, _, err := ParseRun([]string{"-v", "/var/lib/data", GetTestImage(runtime).ID, "echo", "hello", "world"}, nil)
if err != nil {
t.Fatal(err)
}
c, err := NewBuilder(runtime).Create(config)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(c)
if err := c.Start(); err != nil {
t.Fatal(err)
}
c.WaitTimeout(500 * time.Millisecond)
c.Wait()
}
func TestKill(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {

View File

@@ -116,7 +116,6 @@ func crashTest() error {
return err
}
}
return nil
}
func main() {

View File

@@ -8,7 +8,7 @@
echo "Ensuring basic dependencies are installed..."
apt-get -qq update
apt-get -qq install lxc wget bsdtar
apt-get -qq install lxc wget
echo "Looking in /proc/filesystems to see if we have AUFS support..."
if grep -q aufs /proc/filesystems

View File

@@ -24,40 +24,29 @@ func main() {
docker.SysInit()
return
}
host := "127.0.0.1"
port := 4243
// FIXME: Switch d and D ? (to be more sshd like)
flDaemon := flag.Bool("d", false, "Daemon mode")
flDebug := flag.Bool("D", false, "Debug mode")
flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to")
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
flDns := flag.String("dns", "", "Set custom dns servers")
flHosts := docker.ListOpts{fmt.Sprintf("tcp://%s:%d", docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT)}
flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
flag.Parse()
if len(flHosts) > 1 {
flHosts = flHosts[1:len(flHosts)] //trick to display a nice defaul value in the usage
}
for i, flHost := range flHosts {
flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
}
if *bridgeName != "" {
docker.NetworkBridgeIface = *bridgeName
} else {
docker.NetworkBridgeIface = docker.DefaultNetworkBridge
}
if strings.Contains(*flHost, ":") {
hostParts := strings.Split(*flHost, ":")
if len(hostParts) != 2 {
log.Fatal("Invalid bind address format.")
os.Exit(-1)
}
if hostParts[0] != "" {
host = hostParts[0]
}
if p, err := strconv.Atoi(hostParts[1]); err == nil {
port = p
}
} else {
host = *flHost
}
if *flDebug {
os.Setenv("DEBUG", "1")
}
@@ -67,12 +56,17 @@ func main() {
flag.Usage()
return
}
if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors, *flDns); err != nil {
if err := daemon(*pidfile, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil {
log.Fatal(err)
os.Exit(-1)
}
} else {
if err := docker.ParseCommands(host, port, flag.Args()...); err != nil {
if len(flHosts) > 1 {
log.Fatal("Please specify only one -H")
return
}
protoAddrParts := strings.SplitN(flHosts[0], "://", 2)
if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil {
log.Fatal(err)
os.Exit(-1)
}
@@ -106,10 +100,7 @@ func removePidFile(pidfile string) {
}
}
func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns string) error {
if addr != "127.0.0.1" {
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
}
func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error {
if err := createPidFile(pidfile); err != nil {
log.Fatal(err)
}
@@ -131,6 +122,28 @@ func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns
if err != nil {
return err
}
return docker.ListenAndServe(fmt.Sprintf("%s:%d", addr, port), server, true)
chErrors := make(chan error, len(protoAddrs))
for _, protoAddr := range protoAddrs {
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
if protoAddrParts[0] == "unix" {
syscall.Unlink(protoAddrParts[1])
} else if protoAddrParts[0] == "tcp" {
if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") {
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
}
} else {
log.Fatal("Invalid protocol format.")
os.Exit(-1)
}
go func() {
chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true)
}()
}
for i := 0; i < len(protoAddrs); i += 1 {
err := <-chErrors
if err != nil {
return err
}
}
return nil
}

View File

@@ -19,13 +19,35 @@ Docker Remote API
2. Versions
===========
The current verson of the API is 1.2
Calling /images/<name>/insert is the same as calling /v1.2/images/<name>/insert
The current verson of the API is 1.3
Calling /images/<name>/insert is the same as calling /v1.3/images/<name>/insert
You can still call an old version of the api using /v1.0/images/<name>/insert
:doc:`docker_remote_api_v1.3`
*****************************
What's new
----------
Builder (/build):
- Simplify the upload of the build context
- Simply stream a tarball instead of multipart upload with 4 intermediary buffers
- Simpler, less memory usage, less disk usage and faster
.. Note::
The /build improvements are not reverse-compatible. Pre 1.3 clients will break on /build.
List containers (/containers/json):
- You can use size=1 to get the size of the containers
:doc:`docker_remote_api_v1.2`
*****************************
docker v0.4.2 2e7649b_
What's new
----------
@@ -36,6 +58,7 @@ The client should send it's authConfig as POST on each call of /images/(name)/pu
.. http:post:: /auth only checks the configuration but doesn't store it on the server
Deleting an image is now improved, will only untag the image if it has chidrens and remove all the untagged parents if has any.
.. http:post:: /images/<name>/delete now returns a JSON with the list of images deleted/untagged
@@ -64,6 +87,9 @@ Uses json stream instead of HTML hijack, it looks like this:
...
:doc:`docker_remote_api_v1.0`
*****************************
docker v0.3.4 8d73740_
What's new
@@ -74,6 +100,7 @@ Initial version
.. _a8ae398: https://github.com/dotcloud/docker/commit/a8ae398bf52e97148ee7bd0d5868de2e15bd297f
.. _8d73740: https://github.com/dotcloud/docker/commit/8d73740343778651c09160cde9661f5f387b36f4
.. _2e7649b: https://github.com/dotcloud/docker/commit/2e7649beda7c820793bd46766cbc2cfeace7b168
==================================
Docker Remote API Client Libraries

View File

@@ -691,6 +691,7 @@ Get the history of an image
[
{
"Id":"b750fe79269d",
"Tag":["base:latest"],
"Created":1364102658,
"CreatedBy":"/bin/bash"
},
@@ -846,7 +847,7 @@ Build an image from Dockerfile via stdin
.. http:post:: /build
Build an image from Dockerfile via stdin
Build an image from Dockerfile
**Example request**:
@@ -865,9 +866,12 @@ Build an image from Dockerfile via stdin
{{ STREAM }}
:query t: tag to be applied to the resulting image in case of success
:query remote: resource to fetch, as URI
:statuscode 200: no error
:statuscode 500: server error
{{ STREAM }} is the raw text output of the build command. It uses the HTTP Hijack method in order to stream.
Check auth configuration
************************
@@ -894,9 +898,16 @@ Check auth configuration
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
{
"Status": "Login Succeeded"
}
:statuscode 200: no error
:statuscode 204: no error
:statuscode 401: unauthorized
:statuscode 403: forbidden
:statuscode 500: server error
@@ -1026,5 +1037,5 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a
To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode.
docker -d -H="192.168.1.9:4243" -api-enable-cors
docker -d -H="tcp://192.168.1.9:4243" -api-enable-cors

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ To list available commands, either run ``docker`` with no parameters or execute
$ docker
Usage: docker [OPTIONS] COMMAND [arg...]
-H="127.0.0.1:4243": Host:port to bind/connect to
-H=[tcp://127.0.0.1:4243]: tcp://host:port to bind/connect to or unix://path/to/socket to use
A self-sufficient runtime for linux containers.

View File

@@ -8,9 +8,11 @@
::
Usage: docker build [OPTIONS] PATH | -
Usage: docker build [OPTIONS] PATH | URL | -
Build a new container image from the source code at PATH
-t="": Tag to be applied to the resulting image in case of success.
When a single Dockerfile is given as URL, then no context is set. When a git repository is set as URL, the repository is used as context
Examples
--------
@@ -27,7 +29,15 @@ Examples
.. code-block:: bash
docker build -
docker build - < Dockerfile
| This will read a Dockerfile from Stdin without context. Due to the lack of a context, no contents of any local directory will be sent to the docker daemon.
| ADD doesn't work when running in this mode due to the absence of the context, thus having no source files to copy to the container.
.. code-block:: bash
docker build github.com/creack/docker-firefox
| This will clone the github repository and use it as context. The Dockerfile at the root of the repository is used as Dockerfile.
| Note that you can specify an arbitrary git repository by using the 'git://' schema.

View File

@@ -8,7 +8,7 @@
::
Usage: docker run [OPTIONS] IMAGE COMMAND [ARG...]
Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
Run a command in a new container

View File

@@ -1,8 +0,0 @@
:title: Introduction
:description: An introduction to docker and standard containers?
:keywords: containers, lxc, concepts, explanation, docker, documentation
:note: This version of the introduction is temporary, just to make sure we don't break the links from the website when the documentation is updated
This document has been moved to :ref:`introduction`, please update your bookmarks.

View File

@@ -1,125 +0,0 @@
:title: Introduction
:description: An introduction to docker and standard containers?
:keywords: containers, lxc, concepts, explanation
Introduction
============
Docker -- The Linux container runtime
-------------------------------------
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.
Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
- **Heterogeneous payloads** Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.
- **Any server** Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.
- **Isolation** docker isolates processes from each other and from the underlying host, using lightweight containers.
- **Repeatability** Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.
.. image:: images/lego_docker.jpg
What is a Standard Container?
-----------------------------
Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in
a format that is self-describing and portable, so that any compliant runtime can run it without extra dependency, regardless of the underlying machine and the contents of the container.
The spec for Standard Containers is currently work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment.
A great analogy for this is the shipping container. Just like Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery.
Standard operations
~~~~~~~~~~~~~~~~~~~
Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, standard containers can be started, stopped, copied, snapshotted, downloaded, uploaded and tagged.
Content-agnostic
~~~~~~~~~~~~~~~~~~~
Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffee or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts.
Infrastructure-agnostic
~~~~~~~~~~~~~~~~~~~~~~~~~~
Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions.
Designed for automation
~~~~~~~~~~~~~~~~~~~~~~~~~~
Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterpart, are extremely well-suited for automation. In fact, you could say automation is their secret weapon.
Many things that once required time-consuming and error-prone human effort can now be programmed. Before shipping containers, a bag of powder coffee was hauled, dragged, dropped, rolled and stacked by 10 different people in 10 different locations by the time it reached its destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The process was slow, inefficient and cost a fortune - and was entirely different depending on the facility and the type of goods.
Similarly, before Standard Containers, by the time a software component ran in production, it had been individually built, configured, bundled, documented, patched, vendored, templated, tweaked and instrumented by 10 different people on 10 different computers. Builds failed, libraries conflicted, mirrors crashed, post-it notes were lost, logs were misplaced, cluster updates were half-broken. The process was slow, inefficient and cost a fortune - and was entirely different depending on the language and infrastructure provider.
Industrial-grade delivery
~~~~~~~~~~~~~~~~~~~~~~~~~~
There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded on the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away.
With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality.
Standard Container Specification
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(TODO)
Image format
~~~~~~~~~~~~
Standard operations
~~~~~~~~~~~~~~~~~~~
- Copy
- Run
- Stop
- Wait
- Commit
- Attach standard streams
- List filesystem changes
- ...
Execution environment
~~~~~~~~~~~~~~~~~~~~~
Root filesystem
^^^^^^^^^^^^^^^
Environment variables
^^^^^^^^^^^^^^^^^^^^^
Process arguments
^^^^^^^^^^^^^^^^^
Networking
^^^^^^^^^^
Process namespacing
^^^^^^^^^^^^^^^^^^^
Resource limits
^^^^^^^^^^^^^^^
Process monitoring
^^^^^^^^^^^^^^^^^^
Logging
^^^^^^^
Signals
^^^^^^^
Pseudo-terminal allocation
^^^^^^^^^^^^^^^^^^^^^^^^^^
Security
^^^^^^^^

View File

@@ -20,6 +20,20 @@ import sys, os
# -- General configuration -----------------------------------------------------
# Additional templates that should be rendered to pages, maps page names to
# template names.
# the 'redirect_home.html' page redirects using a http meta refresh which, according
# to official sources is more or less equivalent of a 301.
html_additional_pages = {
'concepts/containers': 'redirect_home.html',
'concepts/introduction': 'redirect_home.html',
}
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
@@ -120,7 +134,11 @@ html_theme_path = ['../theme']
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# We use a png favicon. This is not compatible with internet explorer, but looks
# much better on all other browsers. However, sphynx doesn't like it (it likes
# .ico better) so we have just put it in the template rather than used this setting
# html_favicon = 'favicon.png'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
@@ -138,10 +156,6 @@ html_static_path = ['static_files']
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True

View File

@@ -33,7 +33,7 @@ Installation
sudo apt-get install python-software-properties
sudo add-apt-repository ppa:gophers/go
sudo apt-get update
sudo apt-get -y install lxc wget bsdtar curl golang-stable git aufs-tools
sudo apt-get -y install lxc xz-utils curl golang-stable git aufs-tools
export GOPATH=~/go/
export PATH=$GOPATH/bin:$PATH

View File

@@ -72,7 +72,7 @@ Connect to the host os with the redis-cli.
docker ps # grab the new container id
docker port <container_id> 6379 # grab the external port
ifconfig # grab the host ip address
ip addr show # grab the host ip address
redis-cli -h <host ipaddress> -p <external port>
redis 192.168.0.1:49153> set docker awesome
OK

View File

@@ -59,6 +59,7 @@ The password is 'screencast'
# it has now given us a port to connect to
# we have to connect using a public ip of our host
$ hostname
# *ifconfig* is deprecated, better use *ip addr show* now
$ ifconfig
$ ssh root@192.168.33.10 -p 49153
# Ah! forgot to set root passwd
@@ -70,6 +71,7 @@ The password is 'screencast'
$ docker commit 9e863f0ca0af31c8b951048ba87641d67c382d08d655c2e4879c51410e0fedc1 dhrp/sshd
$ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
$ docker port a0aaa9558c90cf5c7782648df904a82365ebacce523e4acc085ac1213bfe2206 22
# *ifconfig* is deprecated, better use *ip addr show* now
$ ifconfig
$ ssh root@192.168.33.10 -p 49154
# Thanks for watching, Thatcher thatcher@dotcloud.com

View File

@@ -1,27 +0,0 @@
:title: Index Environment Variable
:description: Setting this environment variable on the docker server will change the URL docker index.
:keywords: docker, index environment variable, documentation
=================================
Docker Index Environment Variable
=================================
Variable
--------
.. code-block:: sh
DOCKER_INDEX_URL
Setting this environment variable on the docker server will change the URL docker index.
This address is used in commands such as ``docker login``, ``docker push`` and ``docker pull``.
The docker daemon doesn't need to be restarted for this parameter to take effect.
Example
-------
.. code-block:: sh
docker -d &
export DOCKER_INDEX_URL="https://index.docker.io"

View File

@@ -30,8 +30,7 @@ Dependencies:
* 3.8 Kernel (read more about :ref:`kernel`)
* AUFS filesystem support
* lxc
* bsdtar
* xz-utils
Get the docker binary:
----------------------

View File

@@ -100,7 +100,7 @@ Memory and Swap Accounting on Debian/Ubuntu
If you use Debian or Ubuntu kernels, and want to enable memory and swap
accounting, you must add the following command-line parameters to your kernel::
cgroup_enable=memory swapaccount
cgroup_enable=memory swapaccount=1
On Debian or Ubuntu systems, if you use the default GRUB bootloader, you can
add those parameters by editing ``/etc/default/grub`` and extending
@@ -110,6 +110,6 @@ add those parameters by editing ``/etc/default/grub`` and extending
And replace it by the following one::
GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount"
GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"
Then run ``update-grub``, and reboot.

View File

@@ -33,11 +33,20 @@ Running an interactive shell
# allocate a tty, attach stdin and stdout
docker run -i -t base /bin/bash
Bind Docker to another host/port
--------------------------------
Bind Docker to another host/port or a unix socket
-------------------------------------------------
If you want Docker to listen to another port and bind to another ip
use -host and -port on both deamon and client
With -H it is possible to make the Docker daemon to listen on a specific ip and port. By default, it will listen on 127.0.0.1:4243 to allow only local connections but you can set it to 0.0.0.0:4243 or a specific host ip to give access to everybody.
Similarly, the Docker client can use -H to connect to a custom port.
-H accepts host and port assignment in the following format: tcp://[host][:port] or unix://path
For example:
* tcp://host -> tcp connection on host:4243
* tcp://host:port -> tcp connection on host:port
* tcp://:port -> tcp connection on 127.0.0.1:port
* unix://path/to/socket -> unix socket located at path/to/socket
.. code-block:: bash
@@ -46,6 +55,17 @@ use -host and -port on both deamon and client
# Download a base image
docker -H :5555 pull base
You can use multiple -H, for example, if you want to listen
on both tcp and a unix socket
.. code-block:: bash
# Run docker in daemon mode
sudo <path to>/docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock
# Download a base image
docker pull base
# OR
docker -H unix:///var/run/docker.sock pull base
Starting a long-running worker process
--------------------------------------
@@ -82,7 +102,8 @@ Expose a service on a TCP port
# Connect to the public port via the host's public address
# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { print $1 }')
# Replace *eth0* according to your local interface name.
IP=$(ip -o -4 addr list eth0 | perl -n -e 'if (m{inet\s([\d\.]+)\/\d+\s}xms) { print $1 }')
echo hello world | nc $IP $PORT
# Verify that the network connection worked

View File

@@ -15,10 +15,18 @@ steps and commit them along the way, giving you a final image.
1. Usage
========
To use Docker Builder, assemble the steps into a text file (commonly referred to
as a Dockerfile) and supply this to `docker build` on STDIN, like so:
To build an image from a source repository, create a description file called `Dockerfile`
at the root of your repository. This file will describe the steps to assemble
the image.
``docker build - < Dockerfile``
Then call `docker build` with the path of your source repository as argument:
``docker build .``
You can specify a repository and tag at which to save the new image if the
build succeeds:
``docker build -t shykes/myapp .``
Docker will run your steps one-by-one, committing the result if necessary,
before finally outputting the ID of your new image.
@@ -113,26 +121,37 @@ functionally equivalent to prefixing the command with `<key>=<value>`
.. note::
The environment variables will persist when a container is run from the resulting image.
2.7 INSERT
----------
``INSERT <file url> <path>``
The `INSERT` instruction will download the file from the given url to the given
path within the image. It is similar to `RUN curl -o <path> <url>`, assuming
curl was installed within the image.
.. note::
The path must include the file name.
2.8 ADD
2.7 ADD
-------
``ADD <src> <dest>``
The `ADD` instruction will insert the files from the `<src>` path of the context into `<dest>` path
of the container.
The context must be set in order to use this instruction. (see examples)
The `ADD` instruction will copy new files from <src> and add them to the container's filesystem at path `<dest>`.
`<src>` must be the path to a file or directory relative to the source directory being built (also called the
context of the build) or a remote file URL.
`<dest>` is the path at which the source will be copied in the destination container.
The copy obeys the following rules:
If `<src>` is a directory, the entire directory is copied, including filesystem metadata.
If `<src>` is a tar archive in a recognized compression format (identity, gzip, bzip2 or xz), it
is unpacked as a directory.
When a directory is copied or unpacked, it has the same behavior as 'tar -x': the result is the union of
a) whatever existed at the destination path and b) the contents of the source tree, with conflicts resolved
in favor of b on a file-by-file basis.
If `<src>` is any other kind of file, it is copied individually along with its metadata. In this case,
if `<dst>` ends with a trailing slash '/', it will be considered a directory and the contents of `<src>`
will be written at `<dst>/base(<src>)`.
If `<dst>` does not end with a trailing slash, it will be considered a regular file and the contents
of `<src>` will be written at `<dst>`.
If `<dest>` doesn't exist, it is created along with all missing directories in its path. All new
files and directories are created with mode 0700, uid and gid 0.
3. Dockerfile Examples
======================
@@ -151,7 +170,6 @@ The context must be set in order to use this instruction. (see examples)
RUN apt-get update
RUN apt-get install -y inotify-tools nginx apache2 openssh-server
INSERT https://raw.github.com/creack/docker-vps/master/nginx-wrapper.sh /usr/sbin/nginx-wrapper
.. code-block:: bash

View File

@@ -77,3 +77,28 @@ Now you can commit this image to the repository
# for example docker push dhrp/kickassapp
docker push <image-name>
Changing the server to connect to
----------------------------------
When you are running your own index and/or registry, You can change the server the docker client will connect to.
Variable
^^^^^^^^
.. code-block:: sh
DOCKER_INDEX_URL
Setting this environment variable on the docker server will change the URL docker index.
This address is used in commands such as ``docker login``, ``docker push`` and ``docker pull``.
The docker daemon doesn't need to be restarted for this parameter to take effect.
Example
^^^^^^^
.. code-block:: sh
docker -d &
export DOCKER_INDEX_URL="https://index.docker.io"

View File

@@ -40,6 +40,8 @@
{%- set script_files = script_files + ['_static/js/docs.js'] %}
<link rel="canonical" href="http://docs.docker.io/en/latest/{{ pagename }}/">
{%- for cssfile in css_files %}
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
{%- endfor %}
@@ -48,9 +50,8 @@
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
{%- endfor %}
{%- if favicon %}
<link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
{%- endif %}
<link rel="shortcut icon" href="{{ pathto('_static/favicon.png', 1) }}"/>
{%- block extrahead %}{% endblock %}
@@ -104,11 +105,8 @@
<!-- Docs nav
================================================== -->
<div class="row" style="position: relative">
<div class="span3" style="height:100%;" >
</div>
<div class="span3 sidebar bs-docs-sidebar" style="position: absolute">
<div class="span3 sidebar bs-docs-sidebar">
{{ toctree(collapse=False, maxdepth=3) }}
</div>

12
docs/theme/docker/redirect_home.html vendored Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Moved</title>
<meta http-equiv="refresh" content="0; url=http://docks.docker.io/en/latest/">
</head>
<body>
This page has moved. Perhaps you should visit the <a href="http://docs.docker.io/" title="documentation homepage">Documentation Homepage</a>
</body>
</html>

View File

@@ -168,10 +168,13 @@ section.header {
.sidebar {
font-weight: normal;
float: left;
min-height: 475px;
/* min-height: 475px;*/
background: #ececec;
border-left: 1px solid #bbbbbb;
border-right: 1px solid #cccccc;
/* border-left: 1px solid #bbbbbb;*/
/* border-right: 1px solid #cccccc;*/
position: relative;
}
.sidebar ul {
@@ -357,7 +360,6 @@ section.header {
#global {
/* TODO: Fix this to be relative to the navigation size */
padding-top: 600px;
}
#fork-us {
display: none;

View File

@@ -226,20 +226,21 @@ section.header {
}
.sidebar {
// font-family: "Maven Pro";
font-weight: normal;
// margin-top: 38px;
float: left;
// width: 220px;
/* min-height: 475px;*/
// margin-bottom: 28px;
// padding-bottom: 120px;
background: #ececec;
/* border-left: 1px solid #bbbbbb;*/
/* border-right: 1px solid #cccccc;*/
position: relative;
.sidebar {
// font-family: "Maven Pro";
font-weight: normal;
// margin-top: 38px;
float: left;
// width: 220px;
min-height: 475px;
// margin-bottom: 28px;
// padding-bottom: 120px;
background: #ececec;
border-left: 1px solid #bbbbbb;
border-right: 1px solid #cccccc;
position: relative;
ul {
padding: 0px;
@@ -471,7 +472,7 @@ section.header {
}
#global {
/* TODO: Fix this to be relative to the navigation size */
padding-top: 600px;
// padding-top: 600px;
}
#fork-us {
display: none;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

BIN
docs/theme/docker/static/favicon.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

View File

@@ -1,10 +0,0 @@
package docker
import (
"fmt"
"github.com/dotcloud/docker/utils"
)
func getKernelVersion() (*utils.KernelVersionInfo, error) {
return nil, fmt.Errorf("Kernel version detection is not available on darwin")
}

View File

@@ -1,71 +0,0 @@
package docker
import (
"bytes"
"github.com/dotcloud/docker/utils"
"strconv"
"strings"
"syscall"
)
// FIXME: Move this to utils package
func getKernelVersion() (*utils.KernelVersionInfo, error) {
var (
uts syscall.Utsname
flavor string
kernel, major, minor int
err error
)
if err := syscall.Uname(&uts); err != nil {
return nil, err
}
release := make([]byte, len(uts.Release))
i := 0
for _, c := range uts.Release {
release[i] = byte(c)
i++
}
// Remove the \x00 from the release for Atoi to parse correctly
release = release[:bytes.IndexByte(release, 0)]
tmp := strings.SplitN(string(release), "-", 2)
tmp2 := strings.SplitN(tmp[0], ".", 3)
if len(tmp2) > 0 {
kernel, err = strconv.Atoi(tmp2[0])
if err != nil {
return nil, err
}
}
if len(tmp2) > 1 {
major, err = strconv.Atoi(tmp2[1])
if err != nil {
return nil, err
}
}
if len(tmp2) > 2 {
minor, err = strconv.Atoi(tmp2[2])
if err != nil {
return nil, err
}
}
if len(tmp) == 2 {
flavor = tmp[1]
} else {
flavor = ""
}
return &utils.KernelVersionInfo{
Kernel: kernel,
Major: major,
Minor: minor,
Flavor: flavor,
}, nil
}

View File

@@ -192,11 +192,19 @@ func TestDelete(t *testing.T) {
}
assertNImages(graph, t, 0)
archive, err = fakeTar()
if err != nil {
t.Fatal(err)
}
// Test 2 create (same name) / 1 delete
img1, err := graph.Create(archive, nil, "Testing", "", nil)
if err != nil {
t.Fatal(err)
}
archive, err = fakeTar()
if err != nil {
t.Fatal(err)
}
if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil {
t.Fatal(err)
}
@@ -212,6 +220,10 @@ func TestDelete(t *testing.T) {
}
assertNImages(graph, t, 1)
archive, err = fakeTar()
if err != nil {
t.Fatal(err)
}
// Test delete twice (pull -> rm -> pull -> rm)
if err := graph.Register(archive, false, img1); err != nil {
t.Fatal(err)

2
hack/Vagrantfile vendored
View File

@@ -22,7 +22,7 @@ Vagrant::Config.run do |config|
pkg_cmd = "touch #{DOCKER_PATH}; "
# Install docker dependencies
pkg_cmd << "export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; " \
"apt-get install -q -y lxc bsdtar git aufs-tools golang make linux-image-extra-3.8.0-19-generic; " \
"apt-get install -q -y lxc git aufs-tools golang make linux-image-extra-3.8.0-19-generic; " \
"chown -R #{USER}.#{USER} #{GOPATH}; " \
"install -m 0664 #{CFG_PATH}/bash_profile /home/#{USER}/.bash_profile"
config.vm.provision :shell, :inline => pkg_cmd

View File

@@ -19,19 +19,14 @@ run add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc)
run add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu
run apt-get update
# Packages required to checkout, build and upload docker
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd curl
run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz
run tar -C /usr/local -xzf /go.tar.gz
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git build-essential
# Packages required to build an ubuntu package
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev
run apt-get install -y -q devscripts
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable debhelper autotools-dev devscripts
# Copy dockerbuilder files into the container
add . /src
run cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder

View File

@@ -92,9 +92,11 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error {
defer file.Close()
layerData = file
}
if err := Untar(layerData, layer); err != nil {
return err
// If layerData is not nil, unpack it into the new layer
if layerData != nil {
if err := Untar(layerData, layer); err != nil {
return err
}
}
return StoreSize(img, root)

View File

@@ -67,7 +67,11 @@ lxc.cgroup.devices.allow = c 10:200 rwm
# standard mount point
# WARNING: procfs is a known attack vector and should probably be disabled
# if your userspace allows it. eg. see http://blog.zx2c4.com/749
lxc.mount.entry = proc {{$ROOTFS}}/proc proc nosuid,nodev,noexec 0 0
# WARNING: sysfs is a known attack vector and should probably be disabled
# if your userspace allows it. eg. see http://bit.ly/T9CkqJ
lxc.mount.entry = sysfs {{$ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0
lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0
#lxc.mount.entry = varrun {{$ROOTFS}}/var/run tmpfs mode=755,size=4096k,nosuid,nodev,noexec 0 0
@@ -86,6 +90,9 @@ lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,rw 0 0
{{end}}
# drop linux capabilities (apply mainly to the user root in the container)
# (Note: 'lxc.cap.keep' is coming soon and should replace this under the
# security principle 'deny all unless explicitly permitted', see
# http://sourceforge.net/mailarchive/message.php?msg_id=31054627 )
lxc.cap.drop = audit_control audit_write mac_admin mac_override mknod setfcap setpcap sys_admin sys_boot sys_module sys_nice sys_pacct sys_rawio sys_resource sys_time sys_tty_config
# limits

View File

@@ -257,7 +257,6 @@ func proxy(listener net.Listener, proto, address string) error {
utils.Debugf("Connected to backend, splicing")
splice(src, dst)
}
panic("Unreachable")
}
func halfSplice(dst, src net.Conn) error {

View File

@@ -1,24 +1,21 @@
# Ubuntu package Makefile
#
# Dependencies: debhelper autotools-dev devscripts golang
# Dependencies: debhelper autotools-dev devscripts golang-stable
# Notes:
# Use 'make ubuntu' to create the ubuntu package
# GPG_KEY environment variable needs to contain a GPG private key for package to be signed
# and uploaded to docker PPA.
# If GPG_KEY is not defined, make ubuntu will create docker package and exit with
# status code 2
# Use 'make ubuntu' to create the ubuntu package and push it to stating PPA by
# default. To push to production, set PUBLISH_PPA=1 before doing 'make ubuntu'
# GPG_KEY environment variable needs to contain a GPG private key for package
# to be signed and uploaded to docker PPA. If GPG_KEY is not defined,
# make ubuntu will create docker package and exit with status code 2
PKG_NAME=lxc-docker
VERSION=$(shell head -1 changelog | sed 's/^.\+(\(.\+\)..).\+$$/\1/')
GITHUB_PATH=github.com/dotcloud/docker
DOCKER_VERSION=${PKG_NAME}_${VERSION}
DOCKER_FVERSION=${PKG_NAME}_$(shell head -1 changelog | sed 's/^.\+(\(.\+\)).\+$$/\1/')
BUILD_SRC=${CURDIR}/../../build_src
VERSION_TAG=v$(shell head -1 changelog | sed 's/^.\+(\(.\+\)-[0-9]\+).\+$$/\1/')
VERSION=$(shell sed -En '0,/^\#\# /{s/^\#\# ([^ ]+).+/\1/p}' ../../CHANGELOG.md)
all:
# Compile docker. Used by dpkg-buildpackage.
cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} go build
cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} CGO_ENABLED=0 go build -a -ldflags '-d -w'
install:
# Used by dpkg-buildpackage
@@ -26,7 +23,7 @@ install:
mkdir -p ${DESTDIR}/etc/init
mkdir -p ${DESTDIR}/DEBIAN
install -m 0755 src/${GITHUB_PATH}/docker/docker ${DESTDIR}/usr/bin
install -o root -m 0755 debian/docker.upstart ${DESTDIR}/etc/init/docker.conf
install -o root -m 0644 debian/docker.upstart ${DESTDIR}/etc/init/docker.conf
install debian/lxc-docker.prerm ${DESTDIR}/DEBIAN/prerm
install debian/lxc-docker.postinst ${DESTDIR}/DEBIAN/postinst
@@ -35,18 +32,19 @@ ubuntu:
# Retrieve docker project and its go structure from internet
rm -rf ${BUILD_SRC}
git clone $(shell git rev-parse --show-toplevel) ${BUILD_SRC}/${GITHUB_PATH}
cd ${BUILD_SRC}/${GITHUB_PATH}; git checkout ${VERSION_TAG} && GOPATH=${BUILD_SRC} go get -d
cd ${BUILD_SRC}/${GITHUB_PATH}; git checkout v${VERSION} && GOPATH=${BUILD_SRC} go get -d
# Add debianization
mkdir ${BUILD_SRC}/debian
cp Makefile ${BUILD_SRC}
cp -r * ${BUILD_SRC}/debian
cp ../../README.md ${BUILD_SRC}
./parse_changelog.py < ../../CHANGELOG.md > ${BUILD_SRC}/debian/changelog
# Cleanup
for d in `find ${BUILD_SRC} -name '.git*'`; do rm -rf $$d; done
rm -rf ${BUILD_SRC}/../${DOCKER_VERSION}.orig.tar.gz
rm -rf ${BUILD_SRC}/../${PKG_NAME}_${VERSION}.orig.tar.gz
rm -rf ${BUILD_SRC}/pkg
# Create docker debian files
cd ${BUILD_SRC}; tar czf ../${DOCKER_VERSION}.orig.tar.gz .
cd ${BUILD_SRC}; tar czf ../${PKG_NAME}_${VERSION}.orig.tar.gz .
cd ${BUILD_SRC}; dpkg-buildpackage -us -uc
rm -rf ${BUILD_SRC}
# Sign package and upload it to PPA if GPG_KEY environment variable
@@ -54,9 +52,11 @@ ubuntu:
if /usr/bin/test "$${GPG_KEY}" == ""; then exit 2; fi
mkdir ${BUILD_SRC}
# Import gpg signing key
echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import
echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import || true
# Sign the package
cd ${BUILD_SRC}; dpkg-source -x ${BUILD_SRC}/../${DOCKER_FVERSION}.dsc
cd ${BUILD_SRC}; dpkg-source -x ${BUILD_SRC}/../${PKG_NAME}_${VERSION}-1.dsc
cd ${BUILD_SRC}/${PKG_NAME}-${VERSION}; debuild -S -sa
cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${DOCKER_FVERSION}_source.changes
# Upload to PPA
if [ "${PUBLISH_PPA}" = "1" ]; then cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${PKG_NAME}_${VERSION}-1_source.changes; fi
if [ "${PUBLISH_PPA}" != "1" ]; then cd ${BUILD_SRC};dput ppa:dotcloud/docker-staging ${PKG_NAME}_${VERSION}-1_source.changes; fi
rm -rf ${BUILD_SRC}

View File

@@ -1,246 +0,0 @@
lxc-docker (0.4.2-1) precise; urgency=low
- Packaging: Bumped version to work around an Ubuntu bug
-- dotCloud <ops@dotcloud.com> Mon, 17 Jun 2013 00:00:00 -0700
lxc-docker (0.4.1-1) precise; urgency=low
- Builder: don't ignore last line in Dockerfile when it doesn't end with \n
- Client: allow multiple params in inspect
- Client: Print the container id before the hijack in `docker run`
- Remote Api: Add flag to enable cross domain requests
- Remote Api/Client: Add images and containers sizes in docker ps and docker images
- Registry: add regexp check on repo's name
- Registry: Move auth to the client
- Registry: Remove login check on pull
- Runtime: Configure dns configuration host-wide with 'docker -d -dns'
- Runtime: Detect faulty DNS configuration and replace it with a public default
- Runtime: allow docker run <name>:<id>
- Runtime: you can now specify public port (ex: -p 80:4500)
- Runtime: improved image removal to garbage-collect unreferenced parents
- Vagrantfile: Add the rest api port to vagrantfile's port_forward
- Upgrade to Go 1.1
-- dotCloud <ops@dotcloud.com> Mon, 17 Jun 2013 00:00:00 -0700
lxc-docker (0.4.0-1) precise; urgency=low
- Introducing Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
- Introducing Remote API: control Docker programmatically using a simple HTTP/json API
- Runtime: various reliability and usability improvements
-- dotCloud <ops@dotcloud.com> Mon, 03 Jun 2013 00:00:00 -0700
lxc-docker (0.3.4-1) precise; urgency=low
- Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
- Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.
- Runtime: interactive TTYs correctly handle window resize
- Runtime: fix how configuration is merged between layers
- Remote API: split stdout and stderr on 'docker run'
- Remote API: optionally listen on a different IP and port (use at your own risk)
- Documentation: improved install instructions.
-- dotCloud <ops@dotcloud.com> Thu, 30 May 2013 00:00:00 -0700
lxc-docker (0.3.3-1) precise; urgency=low
- Registry: Fix push regression
- Various bugfixes
-- dotCloud <ops@dotcloud.com> Thu, 23 May 2013 00:00:00 -0700
lxc-docker (0.3.2-1) precise; urgency=low
- Runtime: Store the actual archive on commit
- Registry: Improve the checksum process
- Registry: Use the size to have a good progress bar while pushing
- Registry: Use the actual archive if it exists in order to speed up the push
- Registry: Fix error 400 on push
-- dotCloud <ops@dotcloud.com> Fri, 9 May 2013 00:00:00 -0700
lxc-docker (0.3.1-1) precise; urgency=low
- Builder: Implement the autorun capability within docker builder
- Builder: Add caching to docker builder
- Builder: Add support for docker builder with native API as top level command
- Runtime: Add go version to debug infos
- Builder: Implement ENV within docker builder
- Registry: Add docker search top level command in order to search a repository
- Images: output graph of images to dot (graphviz)
- Documentation: new introduction and high-level overview
- Documentation: Add the documentation for docker builder
- Website: new high-level overview
- Makefile: Swap "go get" for "go get -d", especially to compile on go1.1rc
- Images: fix ByParent function
- Builder: Check the command existance prior create and add Unit tests for the case
- Registry: Fix pull for official images with specific tag
- Registry: Fix issue when login in with a different user and trying to push
- Documentation: CSS fix for docker documentation to make REST API docs look better.
- Documentation: Fixed CouchDB example page header mistake
- Documentation: fixed README formatting
- Registry: Improve checksum - async calculation
- Runtime: kernel version - don't show the dash if flavor is empty
- Documentation: updated www.docker.io website.
- Builder: use any whitespaces instead of tabs
- Packaging: packaging ubuntu; issue #510: Use goland-stable PPA package to build docker
-- dotCloud <ops@dotcloud.com> Fri, 8 May 2013 00:00:00 -0700
lxc-docker (0.3.0-1) precise; urgency=low
- Registry: Implement the new registry
- Documentation: new example: sharing data between 2 couchdb databases
- Runtime: Fix the command existance check
- Runtime: strings.Split may return an empty string on no match
- Runtime: Fix an index out of range crash if cgroup memory is not
- Documentation: Various improvments
- Vagrant: Use only one deb line in /etc/apt
-- dotCloud <ops@dotcloud.com> Fri, 5 May 2013 00:00:00 -0700
lxc-docker (0.2.2-1) precise; urgency=low
- Support for data volumes ('docker run -v=PATH')
- Share data volumes between containers ('docker run -volumes-from')
- Improved documentation
- Upgrade to Go 1.0.3
- Various upgrades to the dev environment for contributors
-- dotCloud <ops@dotcloud.com> Fri, 3 May 2013 00:00:00 -0700
lxc-docker (0.2.1-1) precise; urgency=low
- 'docker commit -run' bundles a layer with default runtime options: command, ports etc.
- Improve install process on Vagrant
- New Dockerfile operation: "maintainer"
- New Dockerfile operation: "expose"
- New Dockerfile operation: "cmd"
- Contrib script to build a Debian base layer
- 'docker -d -r': restart crashed containers at daemon startup
- Runtime: improve test coverage
-- dotCloud <ops@dotcloud.com> Wed, 1 May 2013 00:00:00 -0700
lxc-docker (0.2.0-1) precise; urgency=low
- Runtime: ghost containers can be killed and waited for
- Documentation: update install intructions
- Packaging: fix Vagrantfile
- Development: automate releasing binaries and ubuntu packages
- Add a changelog
- Various bugfixes
-- dotCloud <ops@dotcloud.com> Mon, 23 Apr 2013 00:00:00 -0700
lxc-docker (0.1.8-1) precise; urgency=low
- Dynamically detect cgroup capabilities
- Issue stability warning on kernels <3.8
- 'docker push' buffers on disk instead of memory
- Fix 'docker diff' for removed files
- Fix 'docker stop' for ghost containers
- Fix handling of pidfile
- Various bugfixes and stability improvements
-- dotCloud <ops@dotcloud.com> Mon, 22 Apr 2013 00:00:00 -0700
lxc-docker (0.1.7-1) precise; urgency=low
- Container ports are available on localhost
- 'docker ps' shows allocated TCP ports
- Contributors can run 'make hack' to start a continuous integration VM
- Streamline ubuntu packaging & uploading
- Various bugfixes and stability improvements
-- dotCloud <ops@dotcloud.com> Thu, 18 Apr 2013 00:00:00 -0700
lxc-docker (0.1.6-1) precise; urgency=low
- Record the author an image with 'docker commit -author'
-- dotCloud <ops@dotcloud.com> Wed, 17 Apr 2013 00:00:00 -0700
lxc-docker (0.1.5-1) precise; urgency=low
- Disable standalone mode
- Use a custom DNS resolver with 'docker -d -dns'
- Detect ghost containers
- Improve diagnosis of missing system capabilities
- Allow disabling memory limits at compile time
- Add debian packaging
- Documentation: installing on Arch Linux
- Documentation: running Redis on docker
- Fixed lxc 0.9 compatibility
- Automatically load aufs module
- Various bugfixes and stability improvements
-- dotCloud <ops@dotcloud.com> Wed, 17 Apr 2013 00:00:00 -0700
lxc-docker (0.1.4-1) precise; urgency=low
- Full support for TTY emulation
- Detach from a TTY session with the escape sequence `C-p C-q`
- Various bugfixes and stability improvements
- Minor UI improvements
- Automatically create our own bridge interface 'docker0'
-- dotCloud <ops@dotcloud.com> Tue, 9 Apr 2013 00:00:00 -0700
lxc-docker (0.1.3-1) precise; urgency=low
- Choose TCP frontend port with '-p :PORT'
- Layer format is versioned
- Major reliability improvements to the process manager
- Various bugfixes and stability improvements
-- dotCloud <ops@dotcloud.com> Thu, 4 Apr 2013 00:00:00 -0700
lxc-docker (0.1.2-1) precise; urgency=low
- Set container hostname with 'docker run -h'
- Selective attach at run with 'docker run -a [stdin[,stdout[,stderr]]]'
- Various bugfixes and stability improvements
- UI polish
- Progress bar on push/pull
- Use XZ compression by default
- Make IP allocator lazy
-- dotCloud <ops@dotcloud.com> Wed, 3 Apr 2013 00:00:00 -0700
lxc-docker (0.1.1-1) precise; urgency=low
- Display shorthand IDs for convenience
- Stabilize process management
- Layers can include a commit message
- Simplified 'docker attach'
- Fixed support for re-attaching
- Various bugfixes and stability improvements
- Auto-download at run
- Auto-login on push
- Beefed up documentation
-- dotCloud <ops@dotcloud.com> Sun, 31 Mar 2013 00:00:00 -0700
lxc-docker (0.1.0-1) precise; urgency=low
- First release
- Implement registry in order to push/pull images
- TCP port allocation
- Fix termcaps on Linux
- Add documentation
- Add Vagrant support with Vagrantfile
- Add unit tests
- Add repository/tags to ease image management
- Improve the layer implementation
-- dotCloud <ops@dotcloud.com> Sat, 23 Mar 2013 00:00:00 -0700

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env python
'Parse main CHANGELOG.md from stdin outputing on stdout the ubuntu changelog'
import sys,re, datetime
on_block=False
for line in sys.stdin.readlines():
line = line.strip()
if line.startswith('# ') or len(line) == 0:
continue
if line.startswith('## '):
if on_block:
print '\n -- dotCloud <ops@dotcloud.com> {0}\n'.format(date)
version, date = line[3:].split()
date = datetime.datetime.strptime(date, '(%Y-%m-%d)').strftime(
'%a, %d %b %Y 00:00:00 -0700')
on_block = True
print 'lxc-docker ({0}-1) precise; urgency=low'.format(version)
continue
if on_block:
print ' ' + line
print '\n -- dotCloud <ops@dotcloud.com> {0}'.format(date)

View File

@@ -7,11 +7,12 @@ import (
"fmt"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/utils"
"github.com/shin-/cookiejar"
"io"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/url"
"strconv"
"strings"
)
@@ -106,40 +107,45 @@ func (r *Registry) getImagesInRepository(repository string, authConfig *auth.Aut
}
// Retrieve an image from the Registry.
// Returns the Image object as well as the layer as an Archive (io.Reader)
func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([]byte, error) {
func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([]byte, int, error) {
// Get the JSON
req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
if err != nil {
return nil, fmt.Errorf("Failed to download json: %s", err)
return nil, -1, fmt.Errorf("Failed to download json: %s", err)
}
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
res, err := r.client.Do(req)
if err != nil {
return nil, fmt.Errorf("Failed to download json: %s", err)
return nil, -1, fmt.Errorf("Failed to download json: %s", err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
return nil, fmt.Errorf("HTTP code %d", res.StatusCode)
return nil, -1, fmt.Errorf("HTTP code %d", res.StatusCode)
}
imageSize, err := strconv.Atoi(res.Header.Get("X-Docker-Size"))
if err != nil {
return nil, -1, err
}
jsonString, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("Failed to parse downloaded json: %s (%s)", err, jsonString)
return nil, -1, fmt.Errorf("Failed to parse downloaded json: %s (%s)", err, jsonString)
}
return jsonString, nil
return jsonString, imageSize, nil
}
func (r *Registry) GetRemoteImageLayer(imgId, registry string, token []string) (io.ReadCloser, int, error) {
func (r *Registry) GetRemoteImageLayer(imgId, registry string, token []string) (io.ReadCloser, error) {
req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/layer", nil)
if err != nil {
return nil, -1, fmt.Errorf("Error while getting from the server: %s\n", err)
return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
}
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
res, err := r.client.Do(req)
if err != nil {
return nil, -1, err
return nil, err
}
return res.Body, int(res.ContentLength), nil
return res.Body, nil
}
func (r *Registry) GetRemoteTags(registries []string, repository string, token []string) (map[string]string, error) {
@@ -150,16 +156,16 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
}
for _, host := range registries {
endpoint := fmt.Sprintf("https://%s/v1/repositories/%s/tags", host, repository)
req, err := http.NewRequest("GET", endpoint, nil)
req, err := r.opaqueRequest("GET", endpoint, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
res, err := r.client.Do(req)
utils.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
if err != nil {
return nil, err
}
utils.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
defer res.Body.Close()
if res.StatusCode != 200 && res.StatusCode != 404 {
@@ -184,7 +190,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
repositoryTarget := auth.IndexServerAddress() + "/repositories/" + remote + "/images"
req, err := http.NewRequest("GET", repositoryTarget, nil)
req, err := r.opaqueRequest("GET", repositoryTarget, nil)
if err != nil {
return nil, err
}
@@ -303,6 +309,15 @@ func (r *Registry) PushImageLayerRegistry(imgId string, layer io.Reader, registr
return nil
}
func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
req, err := http.NewRequest(method, urlStr, body)
if err != nil {
return nil, err
}
req.URL.Opaque = strings.Replace(urlStr, req.URL.Scheme+":", "", 1)
return req, err
}
// push a tag on the registry.
// Remote has the format '<user>/<repo>
func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token []string) error {
@@ -310,7 +325,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token
revision = "\"" + revision + "\""
registry = "https://" + registry + "/v1"
req, err := http.NewRequest("PUT", registry+"/repositories/"+remote+"/tags/"+tag, strings.NewReader(revision))
req, err := r.opaqueRequest("PUT", registry+"/repositories/"+remote+"/tags/"+tag, strings.NewReader(revision))
if err != nil {
return err
}
@@ -340,7 +355,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
utils.Debugf("Image list pushed to index:\n%s\n", imgListJSON)
req, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJSON))
req, err := r.opaqueRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJSON))
if err != nil {
return nil, err
}
@@ -360,7 +375,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
// Redirect if necessary
for res.StatusCode >= 300 && res.StatusCode < 400 {
utils.Debugf("Redirected to %s\n", res.Header.Get("Location"))
req, err = http.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJSON))
req, err = r.opaqueRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJSON))
if err != nil {
return nil, err
}
@@ -438,11 +453,6 @@ func (r *Registry) SearchRepositories(term string) (*SearchResults, error) {
return result, err
}
func (r *Registry) ResetClient(authConfig *auth.AuthConfig) {
r.authConfig = authConfig
r.client.Jar = cookiejar.NewCookieJar()
}
func (r *Registry) GetAuthConfig(withPasswd bool) *auth.AuthConfig {
password := ""
if withPasswd {
@@ -478,18 +488,18 @@ type Registry struct {
authConfig *auth.AuthConfig
}
func NewRegistry(root string, authConfig *auth.AuthConfig) *Registry {
func NewRegistry(root string, authConfig *auth.AuthConfig) (r *Registry, err error) {
httpTransport := &http.Transport{
DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment,
Proxy: http.ProxyFromEnvironment,
}
r := &Registry{
r = &Registry{
authConfig: authConfig,
client: &http.Client{
Transport: httpTransport,
},
}
r.client.Jar = cookiejar.NewCookieJar()
return r
r.client.Jar, err = cookiejar.New(nil)
return r, err
}

View File

@@ -65,7 +65,11 @@ func init() {
// Create the "Server"
srv := &Server{
runtime: runtime,
runtime: runtime,
enableCors: false,
lock: &sync.Mutex{},
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
}
// Retrieve the Image
if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil {

121
server.go
View File

@@ -15,6 +15,7 @@ import (
"path"
"runtime"
"strings"
"sync"
)
func (srv *Server) DockerVersion() APIVersion {
@@ -54,8 +55,11 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
}
func (srv *Server) ImagesSearch(term string) ([]APISearch, error) {
results, err := registry.NewRegistry(srv.runtime.root, nil).SearchRepositories(term)
r, err := registry.NewRegistry(srv.runtime.root, nil)
if err != nil {
return nil, err
}
results, err := r.SearchRepositories(term)
if err != nil {
return nil, err
}
@@ -218,12 +222,24 @@ func (srv *Server) ImageHistory(name string) ([]APIHistory, error) {
return nil, err
}
lookupMap := make(map[string][]string)
for name, repository := range srv.runtime.repositories.Repositories {
for tag, id := range repository {
// If the ID already has a reverse lookup, do not update it unless for "latest"
if _, exists := lookupMap[id]; !exists {
lookupMap[id] = []string{}
}
lookupMap[id] = append(lookupMap[id], name+":"+tag)
}
}
outs := []APIHistory{} //produce [] when empty instead of 'null'
err = image.WalkHistory(func(img *Image) error {
var out APIHistory
out.ID = srv.runtime.repositories.ImageName(img.ShortID())
out.Created = img.Created.Unix()
out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ")
out.Tags = lookupMap[img.ID]
outs = append(outs, out)
return nil
})
@@ -238,7 +254,7 @@ func (srv *Server) ContainerChanges(name string) ([]Change, error) {
return nil, fmt.Errorf("No such container: %s", name)
}
func (srv *Server) Containers(all bool, n int, since, before string) []APIContainers {
func (srv *Server) Containers(all, size bool, n int, since, before string) []APIContainers {
var foundBefore bool
var displayed int
retContainers := []APIContainers{}
@@ -272,8 +288,9 @@ func (srv *Server) Containers(all bool, n int, since, before string) []APIContai
c.Created = container.Created.Unix()
c.Status = container.State.String()
c.Ports = container.NetworkSettings.PortMappingHuman()
c.SizeRw, c.SizeRootFs = container.GetSize()
if size {
c.SizeRw, c.SizeRootFs = container.GetSize()
}
retContainers = append(retContainers, c)
}
return retContainers
@@ -309,7 +326,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
for _, id := range history {
if !srv.runtime.graph.Exists(id) {
out.Write(sf.FormatStatus("Pulling %s metadata", id))
imgJSON, err := r.GetRemoteImageJSON(id, endpoint, token)
imgJSON, imgSize, err := r.GetRemoteImageJSON(id, endpoint, token)
if err != nil {
// FIXME: Keep goging in case of error?
return err
@@ -321,12 +338,12 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
// Get the layer
out.Write(sf.FormatStatus("Pulling %s fs layer", id))
layer, contentLength, err := r.GetRemoteImageLayer(img.ID, endpoint, token)
layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token)
if err != nil {
return err
}
defer layer.Close()
if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), false, img); err != nil {
if err := srv.runtime.graph.Register(utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), false, img); err != nil {
return err
}
}
@@ -401,8 +418,51 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re
return nil
}
func (srv *Server) poolAdd(kind, key string) error {
srv.lock.Lock()
defer srv.lock.Unlock()
if _, exists := srv.pullingPool[key]; exists {
return fmt.Errorf("%s %s is already in progress", key, kind)
}
switch kind {
case "pull":
srv.pullingPool[key] = struct{}{}
break
case "push":
srv.pushingPool[key] = struct{}{}
break
default:
return fmt.Errorf("Unkown pool type")
}
return nil
}
func (srv *Server) poolRemove(kind, key string) error {
switch kind {
case "pull":
delete(srv.pullingPool, key)
break
case "push":
delete(srv.pushingPool, key)
break
default:
return fmt.Errorf("Unkown pool type")
}
return nil
}
func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
r := registry.NewRegistry(srv.runtime.root, authConfig)
r, err := registry.NewRegistry(srv.runtime.root, authConfig)
if err != nil {
return err
}
if err := srv.poolAdd("pull", name+":"+tag); err != nil {
return err
}
defer srv.poolRemove("pull", name+":"+tag)
out = utils.NewWriteFlusher(out)
if endpoint != "" {
if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
@@ -418,7 +478,6 @@ func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *util
if err := srv.pullRepository(r, out, name, remote, tag, sf); err != nil {
return err
}
return nil
}
@@ -520,7 +579,7 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
// FIXME: Continue on error?
return err
}
out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"/users/"+srvName+"/"+elem.Tag))
out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"/repositories/"+srvName+"/tags/"+elem.Tag))
if err := r.PushRegistryTag(srvName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil {
return err
}
@@ -593,11 +652,19 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId,
return nil
}
// FIXME: Allow to interupt current push when new push of same image is done.
func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
if err := srv.poolAdd("push", name); err != nil {
return err
}
defer srv.poolRemove("push", name)
out = utils.NewWriteFlusher(out)
img, err := srv.runtime.graph.Get(name)
r := registry.NewRegistry(srv.runtime.root, authConfig)
r, err2 := registry.NewRegistry(srv.runtime.root, authConfig)
if err2 != nil {
return err2
}
if err != nil {
out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name])))
// If it fails, try to get the repository
@@ -693,6 +760,9 @@ func (srv *Server) ContainerRestart(name string, t int) error {
func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
if container := srv.runtime.Get(name); container != nil {
if container.State.Running {
return fmt.Errorf("Impossible to remove a running container, please stop it first")
}
volumes := make(map[string]struct{})
// Store all the deleted containers volumes
for _, volumeId := range container.Volumes {
@@ -910,17 +980,17 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
if stdout {
cLog, err := container.ReadLog("stdout")
if err != nil {
utils.Debugf(err.Error())
utils.Debugf("Error reading logs (stdout): %s", err)
} else if _, err := io.Copy(out, cLog); err != nil {
utils.Debugf(err.Error())
utils.Debugf("Error streaming logs (stdout): %s", err)
}
}
if stderr {
cLog, err := container.ReadLog("stderr")
if err != nil {
utils.Debugf(err.Error())
utils.Debugf("Error reading logs (stderr): %s", err)
} else if _, err := io.Copy(out, cLog); err != nil {
utils.Debugf(err.Error())
utils.Debugf("Error streaming logs (stderr): %s", err)
}
}
}
@@ -930,9 +1000,6 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
if container.State.Ghost {
return fmt.Errorf("Impossible to attach to a ghost container")
}
if !container.State.Running {
return fmt.Errorf("Impossible to attach to a stopped container, start it first")
}
var (
cStdin io.ReadCloser
@@ -991,14 +1058,20 @@ func NewServer(autoRestart, enableCors bool, dns ListOpts) (*Server, error) {
return nil, err
}
srv := &Server{
runtime: runtime,
enableCors: enableCors,
runtime: runtime,
enableCors: enableCors,
lock: &sync.Mutex{},
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
}
runtime.srv = srv
return srv, nil
}
type Server struct {
runtime *Runtime
enableCors bool
runtime *Runtime
enableCors bool
lock *sync.Mutex
pullingPool map[string]struct{}
pushingPool map[string]struct{}
}

View File

@@ -9,16 +9,16 @@ const (
getTermios = syscall.TIOCGETA
setTermios = syscall.TIOCSETA
ECHO = 0x00000008
ONLCR = 0x2
ISTRIP = 0x20
INLCR = 0x40
ISIG = 0x80
IGNCR = 0x80
ICANON = 0x100
ICRNL = 0x100
IXOFF = 0x400
IXON = 0x200
ECHO = 0x00000008
ONLCR = 0x2
ISTRIP = 0x20
INLCR = 0x40
ISIG = 0x80
IGNCR = 0x80
ICANON = 0x100
ICRNL = 0x100
IXOFF = 0x400
IXON = 0x200
)
type Termios struct {

2
testing/Vagrantfile vendored
View File

@@ -30,7 +30,7 @@ Vagrant::Config.run do |config|
# Install docker dependencies
pkg_cmd << "apt-get install -q -y python-software-properties; " \
"add-apt-repository -y ppa:gophers/go/ubuntu; apt-get update -qq; " \
"DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc bsdtar git golang-stable aufs-tools make; "
"DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc git golang-stable aufs-tools make; "
# Activate new kernel
pkg_cmd << "shutdown -r +1; "
config.vm.provision :shell, :inline => pkg_cmd

View File

@@ -10,6 +10,7 @@ import (
"index/suffixarray"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
@@ -86,7 +87,7 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
}
if r.readProgress-r.lastUpdate > updateEvery || err != nil {
if r.readTotal > 0 {
fmt.Fprintf(r.output, r.template, r.readProgress, r.readTotal, fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100))
fmt.Fprintf(r.output, r.template, HumanSize(int64(r.readProgress)), HumanSize(int64(r.readTotal)), fmt.Sprintf("%2.0f%%",float64(r.readProgress)/float64(r.readTotal)*100))
} else {
fmt.Fprintf(r.output, r.template, r.readProgress, "?", "n/a")
}
@@ -146,7 +147,7 @@ func HumanSize(size int64) string {
sizef = sizef / 1000.0
i++
}
return fmt.Sprintf("%.4g %s", sizef, units[i])
return fmt.Sprintf("%5.4g %s", sizef, units[i])
}
func Trunc(s string, maxlen int) string {
@@ -235,7 +236,6 @@ func (r *bufReader) Read(p []byte) (n int, err error) {
}
r.wait.Wait()
}
panic("unreachable")
}
func (r *bufReader) Close() error {
@@ -636,6 +636,14 @@ func (sf *StreamFormatter) Used() bool {
return sf.used
}
func IsURL(str string) bool {
return strings.HasPrefix(str, "http://") || strings.HasPrefix(str, "https://")
}
func IsGIT(str string) bool {
return strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "github.com/")
}
func CheckLocalDns() bool {
resolv, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
@@ -652,3 +660,28 @@ func CheckLocalDns() bool {
}
return false
}
func ParseHost(host string, port int, addr string) string {
if strings.HasPrefix(addr, "unix://") {
return addr
}
if strings.HasPrefix(addr, "tcp://") {
addr = strings.TrimPrefix(addr, "tcp://")
}
if strings.Contains(addr, ":") {
hostParts := strings.Split(addr, ":")
if len(hostParts) != 2 {
log.Fatal("Invalid bind address format.")
os.Exit(-1)
}
if hostParts[0] != "" {
host = hostParts[0]
}
if p, err := strconv.Atoi(hostParts[1]); err == nil {
port = p
}
} else {
host = addr
}
return fmt.Sprintf("tcp://%s:%d", host, port)
}

View File

@@ -274,3 +274,21 @@ func TestHumanSize(t *testing.T) {
t.Errorf("1024 -> expected 1.024 kB, got %s", size1024)
}
}
func TestParseHost(t *testing.T) {
if addr := ParseHost("127.0.0.1", 4243, "0.0.0.0"); addr != "tcp://0.0.0.0:4243" {
t.Errorf("0.0.0.0 -> expected tcp://0.0.0.0:4243, got %s", addr)
}
if addr := ParseHost("127.0.0.1", 4243, "0.0.0.1:5555"); addr != "tcp://0.0.0.1:5555" {
t.Errorf("0.0.0.1:5555 -> expected tcp://0.0.0.1:5555, got %s", addr)
}
if addr := ParseHost("127.0.0.1", 4243, ":6666"); addr != "tcp://127.0.0.1:6666" {
t.Errorf(":6666 -> expected tcp://127.0.0.1:6666, got %s", addr)
}
if addr := ParseHost("127.0.0.1", 4243, "tcp://:7777"); addr != "tcp://127.0.0.1:7777" {
t.Errorf("tcp://:7777 -> expected tcp://127.0.0.1:7777, got %s", addr)
}
if addr := ParseHost("127.0.0.1", 4243, "unix:///var/run/docker.sock"); addr != "unix:///var/run/docker.sock" {
t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr)
}
}