Compare commits

..

227 Commits

Author SHA1 Message Date
Tianon Gravi 3c20a8cdc7 Clean out remaining cruft from 'tianon-separate-dockerinit' 2013-10-18 18:19:54 -06:00
Guillaume J. Charmes d5b3299673 Merge branch 'tianon-separate-dockerinit' into dm
Conflicts:
	runtime_test.go
2013-10-18 16:40:15 -07:00
Guillaume J. Charmes 27140018c5 Merge branch 'links' into dm
Conflicts:
	Dockerfile
	docker-init/docker-init.go
	hack/make.sh
	hack/make/binary
	hack/make/test
	runtime.go
	runtime_test.go
	utils.go
2013-10-18 15:40:50 -07:00
Victor Vieux fbf85e2e0a fix merge issue and gofmt 2013-10-18 14:15:24 -07:00
Victor Vieux d596a8a72c no more name encoding 2013-10-18 14:05:53 -07:00
Victor Vieux bf25342e11 replace == by HasPrefix for names and improve error message 2013-10-18 14:05:53 -07:00
Victor Vieux 4f51ad7d0e refactor notrunc in commands.go to add names support 2013-10-18 14:05:53 -07:00
Victor Vieux 2e181c5635 fix typo in docs 2013-10-18 14:05:53 -07:00
Michael Crosby 1db7cb2f01 Updated fixes post rebase from master
Removed test cases that are no longer
applicable with links and the port changes.
Remove test case where a test was hitting an
external ip.
2013-10-18 14:05:53 -07:00
Alexander Larsson e6e6d89cc1 Move SysInit to a submodule
This allows docker-init to not import the main docker
module, which means it won't e.g. pick up any sqlite
dependencies.
2013-10-18 14:05:53 -07:00
Alexander Larsson 1484434196 Move ListOps to utils submodule
This will be needed for later use in docker-init without a docker
dependency
2013-10-18 14:05:53 -07:00
Alexander Larsson 34c7fb0c43 Add a network test
This just tries pinging first 127.0.0.1 and then 8.8.8.8.

We can't ping via a dns name, because libresolv.so is missing
from the test image, so dns resolving doesn't work.
2013-10-18 14:05:53 -07:00
Alexander Larsson 9268e5e42f network: Stop shelling out to /sbin/ip
We have our own netlink version of all the required calls now, so
we can just skip the /sbin/ip dependency.
2013-10-18 14:05:53 -07:00
Alexander Larsson 04f8ffa140 Use netlink directly instead of /bin/ip in Sysinit
The sysinit code only uses /bin/ip to set a default gateway. This
is pretty easy to do via netlink directly, so we can avoid
the ip dependency.
2013-10-18 14:05:53 -07:00
Alexander Larsson 7e3e662ba5 Add netlink submodule
This contains various operations that can replace the use of
shelling out to "ip".
2013-10-18 14:05:52 -07:00
Michael Crosby 2b17d79e5b Add flag for inter-container communication 2013-10-18 14:05:52 -07:00
Solomon Hykes 0fae30e8c3 Typos and FIXMEs 2013-10-18 14:05:52 -07:00
Solomon Hykes 7fff719cf3 gograph: fix a bug which caused the unicity of (parent, name) to not be enforced 2013-10-18 14:05:52 -07:00
Michael Crosby 6f8d479324 Remove docker ls from docs and cli usage 2013-10-18 14:05:52 -07:00
Michael Crosby 4c27690cdd Add links for container relationships and introspection 2013-10-18 14:04:41 -07:00
Guillaume J. Charmes bc92933384 Merge branch 'separate-dockerinit' of https://github.com/tianon/docker into tianon-separate-dockerinit 2013-10-18 12:45:28 -07:00
Guillaume J. Charmes e9ee860c91 Merge branch 'master' into dm
Conflicts:
	Dockerfile
	buildfile.go
	container.go
	hack/make/test
	runtime_test.go
	utils/utils.go
2013-10-18 12:38:29 -07:00
Guillaume J. Charmes c2175ae736 Switch back some Errorf to Debugf. 2013-10-18 12:29:16 -07:00
Tianon Gravi 34365ececa Rename all cases of "docker-init" to "dockerinit" for consistency 2013-10-18 10:32:19 -06:00
Alexander Larsson 7591a69108 Add a separate docker-init binary
This may be used for the .dockerinit case if the main binary is not
statically linked.

Conflicts:
	docker-init/docker-init.go
2013-10-18 10:23:45 -06:00
Alexander Larsson 764ce78c1d Move SysInit to a submodule
This allows docker-init to not import the main docker
module, which means it won't e.g. pick up any sqlite
dependencies.
2013-10-18 10:22:28 -06:00
Alexander Larsson 2812baf395 devmapper: Fix prefix name to work with udev
Udev escapes "," used in device names to 0\x2c which breaks libdevmapper.
Instead use : to escape minor and minor which works.
2013-10-18 11:39:47 +02:00
Alexander Larsson 572b1fd9be devmapper: Fix major/minor extraction
The way devices are mapped is a bit more complex than before.
This implements the method from new_decode_dev in:
https://github.com/mirrors/linux-2.6/blob/master/include/linux/kdev_t.h

Which is what is needed on kernels > 2.6
2013-10-18 11:38:21 +02:00
Solomon Hykes 4756ad248a devmapper: use major,minor of underlying device in devmapper device name, for readability. 2013-10-18 08:30:53 +00:00
Solomon Hykes dfb77274ce devmapper: clearer make the 'unknown base hash' error message more understandable 2013-10-18 08:22:42 +00:00
Solomon Hykes 73545199a8 Merge remote-tracking branch 'alexlarsson/dm-status' into dm
Conflicts:
	devmapper/deviceset.go
2013-10-18 07:17:13 +00:00
Solomon Hykes 0ad35c6746 devmapper: cleanup error reporting 2013-10-18 07:09:13 +00:00
Solomon Hykes 5e363072f5 Merge branch 'dm' of ssh://github.com/dotcloud/docker into dm 2013-10-18 06:51:50 +00:00
Solomon Hykes cad913c57b devmapper: the tests are fast enough that we no longer need a workaround to avoid timeouts 2013-10-18 06:48:20 +00:00
Solomon Hykes 5f58a1fbe4 hack: encode the name of the current test in temporary directories, for easier tracking 2013-10-18 06:47:08 +00:00
Solomon Hykes 07e09d57af Increase readbility of unit tests by using mkRuntime everywhere 2013-10-18 06:44:30 +00:00
Solomon Hykes 9f85a967bb Merge pull request #2269 from alexlarsson/dm-blocksize
devmapper: Use a smaller blocksize for the thin-pool
2013-10-17 22:50:33 -07:00
Solomon Hykes 85a36b3b53 devmapper: rename deviceset_devmapper.go to deviceset.go 2013-10-18 05:19:22 +00:00
Solomon Hykes d3505d836a Merge remote-tracking branch 'alexlarsson/dm-readonly-images' into dm
Conflicts:
	devmapper/deviceset_devmapper.go
2013-10-18 03:40:46 +00:00
Solomon Hykes c47e93fcbe Merge remote-tracking branch 'alexlarsson/fix-tar-leak' into dm-correct 2013-10-18 03:32:47 +00:00
Solomon Hykes 5cd7de5de8 hack: fail tests if there are leftover temp files before or after 2013-10-18 03:31:21 +00:00
Solomon Hykes d034aafac7 devicemapper: Add fixme 2013-10-18 00:58:20 +00:00
Solomon Hykes 4bd6021806 devmapper: Rename DeviceSetDM to DeviceSet 2013-10-18 00:07:56 +00:00
Solomon Hykes ed03dbfe82 Don't add /.docker-id to the container filesystem 2013-10-18 00:07:46 +00:00
Solomon Hykes 330062ef72 Remove race condition caused by double-destroy in 2 tests 2013-10-17 23:59:59 +00:00
Guillaume J. Charmes 31b883b076 Add error checking and error messages 2013-10-17 15:04:14 -07:00
Guillaume J. Charmes bdb3b2a88c Merge pull request #2262 from alexlarsson/dm-dind-use-root-dir
devicemapper: Use device/inode of the root dir, not the image
2013-10-17 11:54:40 -07:00
Alexander Larsson a14496ce89 Devmapper: Mount images readonly when calculating changes
There is no need to have this be writable, and there is a chance
that e.g. atime updates will cause writes to the image which is
bad for disk use wrt sharing between all containers.
2013-10-17 16:14:53 +02:00
Alexander Larsson 8abcc8e713 devmapper: Use a smaller blocksize for the thin-pool
As per the thin provisioning docs for creating the pool:

   $data_block_size gives the smallest unit of disk space that can be
   allocated at a time expressed in units of 512-byte sectors.
   $data_block_size must be between 128 (64KB) and 2097152 (1GB) and a
   multiple of 128 (64KB). $data_block_size cannot be changed after the
   thin-pool is created. People primarily interested in thin provisioning
   may want to use a value such as 1024 (512KB). People doing lots of
   snapshotting may want a smaller value such as 128 (64KB).

The switch from 512 (which we used before) to 128 (recommended above
for lots of snapshoting) means a simple container creation (based on the
mattdm/fedora:f19 image) adds 1 MB of diskspace rather than 3.6.
This seems more in tune with how docker is typically used.
2013-10-17 15:33:00 +02:00
Alexander Larsson d733cdcebb Show devicemapper status in "docker info"
This shows the current global diskspace use in "docker info"
2013-10-17 15:32:07 +02:00
Alexander Larsson 0aee096fd7 TarFilter: Fix leak of tmpfiles
We were leaking the temporary directory that we create in TarFilter,
because the "tmpDir, err := ioutil.TempDir()" call overrides the
tmpDir in the outer scope with a new locally scoped variable.
This means tmpDir is always "" when the cleanup function is called.

Also, we did not call the atExit() function if CmdStream had an
error early on.

On errors in CmdStream(),
2013-10-17 14:46:58 +02:00
Alexander Larsson e6a73e65a2 devicemapper: Use device/inode of the root dir, not the image
This way the devicemapper prefix stays stable even if we're not
using loopback mounted devices.
2013-10-17 09:53:09 +02:00
Solomon Hykes ad2fbd9e87 devmapper: error reporting workaround in waitRemove() 2013-10-17 01:49:51 +00:00
Solomon Hykes e5d7472a0d devmapper: small fixes in error reporting 2013-10-17 01:49:27 +00:00
Solomon Hykes 7d3c7e2b29 devmapper: debug messages 2013-10-17 01:47:03 +00:00
Solomon Hykes f1d07e2dbe devmapper: prefix device names with the dev_id+inode of the data image 2013-10-17 01:46:28 +00:00
Solomon Hykes ad968ef3ef devmapper: add useful comments 2013-10-17 01:42:05 +00:00
Solomon Hykes c688e9b5a6 devmapper: debug messages 2013-10-16 23:27:33 +00:00
Solomon Hykes f3e6d34df2 hack: cleanup devicemapper at the last test 2013-10-16 23:27:00 +00:00
Solomon Hykes ea04f3de72 devmapper: wait for devices to be effectively removed before returning a successful remove 2013-10-16 23:26:37 +00:00
Solomon Hykes 153248b60f devicemapper: remove unused code 2013-10-16 23:23:35 +00:00
Solomon Hykes 1711de4b09 devmapper: wait for devices to be effectively unmounted before removing them 2013-10-16 23:06:07 +00:00
Solomon Hykes 11d695a297 Add debug messages while testing devicemapper 2013-10-16 20:45:59 +00:00
Solomon Hykes acf58362cb Hack: fix tests which didn't cleanup properly 2013-10-16 20:44:15 +00:00
Guillaume J. Charmes 3a246ac3d1 Change default values for devicemapper as variable instead of env 2013-10-16 20:42:50 +00:00
Solomon Hykes 1da335f784 Hack: don't run integration tests in /var/lib/docker/unit-tests; add missing cleanups in a few tests 2013-10-16 20:10:20 +00:00
Solomon Hykes cd61fb2e6f WIP: debugging dm-base-hash + dm-refactor-init 2013-10-15 23:56:04 +00:00
Solomon Hykes fdba1aeed8 Merge branch 'dm-refactor-init' into dm
Conflicts:
	runtime_test.go
2013-10-15 23:27:28 +00:00
Solomon Hykes c9f3e54c31 Merge branch 'dm-base-hash' into dm 2013-10-15 23:09:26 +00:00
Solomon Hykes 3997b8a923 hack: don't set DEBUG when running tests 2013-10-15 23:07:26 +00:00
Solomon Hykes ec885d9180 hack: only run certain tests with TESTFLAGS='-run TestName' make.sh 2013-10-15 23:06:41 +00:00
Solomon Hykes 54b0cd7cd1 Merge branch 'dm-missing-mapper' into dm 2013-10-15 22:51:20 +00:00
Solomon Hykes 66db2ac9d8 Merge branch 'dm-fix-test-2' into dm 2013-10-15 22:42:49 +00:00
Alexander Larsson 5ebaca7e55 devmapper: create device nodes 'on create' instead of 'on resume' 2013-10-15 21:27:47 +00:00
Solomon Hykes 432ff7e3c3 Merge pull request #2227 from dotcloud/dm-rename-loopback
Rename loopback dir to devicemapper
2013-10-15 12:23:43 -07:00
Michael Crosby 80bd64245f Add filesystemtype for containers
If no type is specified then assume aufs.
2013-10-15 11:49:13 -07:00
Michael Crosby 4431e9edb7 Rename loopback dir to devicemapper 2013-10-15 11:30:06 -07:00
Solomon Hykes 7093411a8d Initialize devicemapper in NewRuntimeFromDIrectory 2013-10-15 03:53:48 +00:00
Guillaume J. Charmes 5778ed7db2 Make sure the base device is well created before running the tests 2013-10-14 18:36:18 -07:00
Michael Crosby 3455c1a098 Use incrementing prefix on pool and loopback to allow dind 2013-10-14 17:48:43 -07:00
Michael Crosby 5dd12ba20a Ignore cleanup with /dev/mapper does not exist 2013-10-14 14:23:58 -07:00
Solomon Hykes 5892c8e469 Merge pull request #2199 from alexlarsson/dm-docs
Add some docs for newly exported functions
2013-10-14 13:33:21 -07:00
Solomon Hykes 20e3e8c07d Merge pull request #2200 from alexlarsson/dm-remove-unused
Runtime: Remove unused funtion hasFilesystemSupport()
2013-10-14 13:24:53 -07:00
Solomon Hykes 79c0c4470f Merge pull request #2182 from dotcloud/cleanup-dm-unittests
Clean up better from previous unit-test runs
2013-10-14 12:29:59 -07:00
Solomon Hykes 4dedd9a9aa Merge pull request #2183 from dotcloud/migrate-aufs
Migrate aufs to new device mapper backend
2013-10-14 12:28:28 -07:00
Solomon Hykes 063ebbab68 Merge pull request #2184 from dotcloud/dm-rmi
Do not allow image to be deleted when containers are dependent
2013-10-14 12:27:19 -07:00
Guillaume J. Charmes ea92dc2e8c Add error return to cleanup, use os.OpenFile instead of syscall.Open, Make sure the pools are removed after all other devices 2013-10-14 12:26:46 -07:00
Michael Crosby 7f429e0ceb Do not allow image to be deleted when containers are dependent 2013-10-14 09:47:01 -07:00
Alexander Larsson bb42801cdc Runtime: Remove unused funtion hasFilesystemSupport()
This used to be used to be used to detect AUFS support, but is not
used anymore.
2013-10-14 10:54:50 +02:00
Alexander Larsson 8e4b3a3390 Add some docs for newly exported functions 2013-10-14 10:53:12 +02:00
Solomon Hykes 48070274ee Change the base filesystem size in unit tests 2013-10-12 15:06:53 -07:00
Michael Crosby c3f1bb3287 Allow loopback and base fs sizes set by env var 2013-10-11 20:37:11 -07:00
Michael Crosby 562e4f1e23 Verbose migration add warning for running container
Conflicts:
	hack/make.sh
	runtime.go
	runtime_test.go
2013-10-11 17:11:18 -07:00
Michael Crosby a263e07678 Migrate AUFS containers to devmapper
Conflicts:
	hack/make.sh
	runtime.go
2013-10-11 16:54:08 -07:00
Alexander Larsson e7986da531 Clean up better from previous unit-test runs
This makes sure we unmount existing mounts (as well as removing the
devmapper devices), and it fails with proper logs rather than just
panic()ing.
2013-10-11 16:42:11 -07:00
Alexander Larsson b440ec0136 device-mapper: Move all devicemapper spew to log through utils.Debugf(). 2013-10-11 16:31:06 -07:00
Alexander Larsson c77697a45c devmapper: Move all "raw" libdevmapper wrappers to devmapper.go
This separates out the DeviceSet logic a bit better from the raw
device mapper operations.

devicemapper: Serialize addess to the devicemapper deviceset

This code is not safe to run in multiple threads at the same time,
and neither is libdevmapper.

DeviceMapper: Move deactivate into UnmountDevice

This way the deactivate is atomic wrt othe device mapper operations
and will not fail with EBUSY if someone else starts a devicemapper
operation inbetween unmount and deactivate.

devmapper: Fix loopback mounting regression

Some changes were added to attach_loop_device which added
a perror() in a place that caused it to override errno so that
a later errno != EBUSY failed. This fixes that and cleans up
the error reporting a bit.

devmapper: Build on old kernels without LOOP_CTL_GET_FREE define
2013-10-11 16:30:02 -07:00
Solomon Hykes 1804fcba93 Merged master into device-mapper branch 2013-10-10 12:50:30 -07:00
Victor Vieux f6913592a1 Merge pull request #2087 from alexlarsson/device-mapper-test
Device mapper test branch update
2013-10-04 07:54:43 -07:00
Alexander Larsson aaf1f73bcc Tests: Initialize devicemapper early to avoid it happening in a test
This can take a while and may cause some tests to timeout
2013-10-04 15:47:43 +02:00
Alexander Larsson 9b65c7cf49 hack: Don't just run the "TestRunHostname" test 2013-10-04 15:38:47 +02:00
Alexander Larsson f7e374fb3a Remove overly spewy Debugf 2013-10-04 15:36:30 +02:00
Guillaume J. Charmes f29c500d8d Small fixes 2013-10-03 18:05:07 -07:00
Guillaume J. Charmes b843998718 Small fixes 2013-10-03 18:04:14 -07:00
Alexander Larsson 1a1be5a87c Make sure we mark the libdevmapper /dev/mapper/control fd CLOEXEC
We do a hack to mark it such, because otherwise lxc-start will not
work.
2013-10-03 21:00:16 +02:00
Alexander Larsson 7b58e15b08 Be better at cleaning up leftover from earlier test runs
When running the test inside a docker container we sometimes are left with
leftover device nodes for device mapper devices that no longer exist.
We were panic:ing in this case, but with this change we just remove such
nodes.
2013-10-03 19:54:14 +02:00
Guillaume J. Charmes 8b2f4aab23 Random improvments 2013-10-02 20:18:15 -07:00
Tianon Gravi 06d0843a61 Update Dockerfile and hack to support compiling device-mapper code statically (using go1.2rc1) 2013-10-03 10:32:47 -06:00
Victor Vieux deb05a36e8 rebase 2013-10-03 15:49:28 +00:00
Victor Vieux 55189307d0 disable: don't create device nodes manually if udev is not availabile as we don't have it in dind 2013-10-03 15:47:38 +00:00
Alexander Larsson 55e1782d66 Image: Fix time setting for old kernels
This is a better fix for futimes() on kernels not supporting O_PATH.
The previous fix broke when copying a device, as it tried to open it
and got and error.
2013-09-30 17:35:02 -06:00
Victor Vieux 152302e379 go fmt and aufs support removed 2013-09-30 17:35:02 -06:00
Victor Vieux 72a08a5458 Revert "add a -mount-method flag"
This reverts commit e52d756f40c9ccf8b37ca496cb72be057c909ed7.
2013-09-30 17:35:02 -06:00
Victor Vieux aeb89ffbba add a -mount-method flag 2013-09-30 17:35:02 -06:00
Alexander Larsson 0484b2c325 RootIsShared: Fix root detection
Column 4 is the mount position, column 3 will not always be
"/" for the root. On one of my system its "/root".
2013-09-30 17:35:02 -06:00
Alexander Larsson be6fef0254 Tests: Clean up any old devmapper leftovers before starting tests 2013-09-30 17:35:02 -06:00
Alexander Larsson 071cc18b58 Image.Changes: Deactivate image device after unmounting it
There is no need to keep the image device around if we were the
onces creating the device.
2013-09-30 17:35:02 -06:00
Alexander Larsson 6ec5585501 Add DeviceSet.HasActivatedDevice()
This lets you see if a device has been activated
2013-09-30 17:35:02 -06:00
Alexander Larsson 6f57e8025a image: Unmount before removing device in error paths
The device remove fails unless we unmount first
2013-09-30 17:35:02 -06:00
Alexander Larsson b0a9147fd5 runtime test: Ensure all containers are unmounted at nuke()
Otherwise we may leave around e.g. devmapper mounts
2013-09-30 17:35:02 -06:00
Alexander Larsson a7e876e357 ShellQuoteArguments: Fix quoting
This accidentally used two quotes to start/end each quoted string.
2013-09-30 17:35:01 -06:00
Alexander Larsson ecdbdfdaea Image: unmount device before removing it on failures
If we don't do this the remove will fail due to EBUSY
2013-09-30 17:35:01 -06:00
Alexander Larsson 2c71710b74 image: Handle systems that don't support O_PATH when updating timestamp
Older kernel can't handle O_PATH in open() so this will
fail on dirs and symlinks. For dirs wa can fallback to
the normal Utimes, but for symlinks there is not much to do
but ignore their timestamps.
2013-09-30 17:35:01 -06:00
Alexander Larsson bbc72c85f7 devmapper: Fix loopback mount code
Typo in the loop-control code made it always fall back to the
old method of opening loopback devices.
2013-09-30 17:35:01 -06:00
Alexander Larsson 1a082ed245 applyLayer() use btrfs reflinks if availible
We use the new file copy helper which uses btrfs reflinks if availible.
2013-09-30 17:35:01 -06:00
Alexander Larsson 86421e8b5e Add CopyFile that can use btrfs reflinks if availible 2013-09-30 17:35:01 -06:00
Alexander Larsson 91c69fd353 Remove accidental commit that enabled MountMethodFilesystem 2013-09-30 17:35:01 -06:00
Alexander Larsson 43a7d3d0e9 Add trivial copy-based CoW backend
This creates a container by copying the corresponding files
from the layers into the containers. This is not gonna be very useful
on a developer setup, as there is no copy-on-write or general diskspace
sharing. It also makes container instantiation slower.

However, it may be useful in deployment where we don't always have a lot
of containers running (long-running daemons) and where we don't
do a lot of docker commits.
2013-09-30 17:35:01 -06:00
Alexander Larsson 60f552cac3 Add Changes.ChangesLayers()
This calculates the difference between a set of layers and a
directory tree.
2013-09-30 17:35:01 -06:00
Alexander Larsson ad402763e1 Changes: Better metadata comparison
Change the comparison to better handle files that are copied during
container creation but not actually changed:

* Inode - this will change during a copy
* ctime - this will change during a copy (as we can't set it back)
* blocksize - this will change for sparse files during copy
* size for directories - this can change anytime but doesn't
  necessarily reflect an actual contents change
* Compare mtimes at microsecond precision (as this is what utimes has)
2013-09-30 17:35:01 -06:00
Alexander Larsson 5d2ace3424 Image.applyLayer: Be better at creating identical files
There are some changes here that make the file metadata better match
the layer files:

* Set the mode of the file after the chown, as otherwise the per-group/uid
  specific flags and e.g. sticky bit is lost
* Use lchown instead of chown
* Delay mtime updates to after all other changes so that later file
  creation doesn't change the mtime for the parent directory
* Use Futimes in combination with O_PATH|O_NOFOLLOW to set mtime on symlinks
2013-09-30 17:35:01 -06:00
Alexander Larsson 727e7fccca Change how ChangesDirs() works
Rather than scan the files in the old directory twice to detect the
deletions we now scan both directories twice and then do all the
diffing on the in-memory structure.

This is more efficient, but it also lets us diff more complex things
later that are not exact on-disk trees.
2013-09-30 17:35:01 -06:00
Alexander Larsson 7d566b4f76 RootIsShared() - Fix array out of bounds error
This happened for me on the last (empty) line, but better safe than sorry
so we make the check general.
2013-09-30 17:35:01 -06:00
Alexander Larsson fdbc2695fe devmapper: Move init layer to top rather than bottom
The init layer needs to be topmost to make sure certain files
are always there (for instance, the ubuntu:12.10 image wrongly
has /dev/shm being a symlink to /run/shm, and we need to override
that). However, previously the devmapper code implemented the
init layer by putting it in the base devmapper device, which meant
layers above it could override these files (so that ubuntu:12.10
broke).

So, instead we put the base layer in *each* images devmapper device.
This is "safe" because we still have the pristine layer data
in the layer directory. Also, it means we diff the container
against the image with the init layer applied, so it won't show
up in diffs/commits.
2013-09-30 17:35:01 -06:00
Alexander Larsson 429587779a lxc: Work around lxc-start need for private mounts
lxc-start requires / to be mounted private, otherwise the changes
it does inside the container (both mounts and unmounts) will propagate
out to the host.

We work around this by starting up lxc-start in its own namespace where
we set / to rprivate.

Unfortunately go can't really execute any code between clone and exec,
so we can't do this in a nice way. Instead we have a horrible hack that
use the unshare command, the shell and the mount command...
2013-09-30 17:35:01 -06:00
Alexander Larsson 145024c6cc Utils: Add ShellQuoteArguments 2013-09-30 17:35:01 -06:00
Alexander Larsson c138801073 Container: Inject into the mount, not the rwPath
For device-mapper setups we can't just push the file into the rwPath.
2013-09-30 17:35:01 -06:00
Alexander Larsson 0722786600 api_test: Fix PostContainersCreate
We can't look for the created file in the rwpath, because that
doesn't exist in the device-mapper world, instead look in the
RootfsPath.
2013-09-30 17:35:01 -06:00
Alexander Larsson 5f8e24f842 Runtime: Only remove device on destroy if it exists 2013-09-30 17:35:01 -06:00
Alexander Larsson 0d7ab8db03 graph test: Unmount image via image.Unmount()
This helps us track the unmount
2013-09-30 17:35:01 -06:00
Alexander Larsson ed741f7b27 deviceset: Cleanup device sets on test end
We unmount all mounts and deactivate all device mapper devices to
make sure we're left with no leftovers after the test.
2013-09-30 17:35:01 -06:00
Alexander Larsson 9e64ebb295 DeviceSet: Add UnmountDevice()
Right now this does nothing but add a new layer, but it means
that all DeviceMounts are paired with DeviceUnmounts so that we
can track (and cleanup) active mounts.
2013-09-30 17:35:01 -06:00
Alexander Larsson d951911b23 Always start tests from a clean set of loopback images
This way we don't get any issues with leftovers
2013-09-30 17:35:01 -06:00
Alexander Larsson 7fb60caa5d tests: Store the loopback images for test outside unit-tests
This directory is copied to each test prefix which is really
slow with the large loopback mounts.
2013-09-30 17:35:01 -06:00
Alexander Larsson 76a2ab6e34 Allow specifying the docker client path in _DOCKER_INIT_PATH
I currently need this to get the tests running, otherwise it will
mount the docker.test binary inside the containers, which doesn't
work due to the libdevmapper.so dependency.
2013-09-30 17:35:00 -06:00
Alexander Larsson 6094257b28 Limit the amount of prints during normal runs
This removes some Debugf() calls and chages some direct prints to
Debugf(). This means we don't get a bunch of spew when running the
tests.
2013-09-30 17:35:00 -06:00
Alexander Larsson 52294192b2 Reuse a single DeviceSetDM for all the tests
We wrap the "real" DeviceSet for each test so that we get only
a single device-mapper pool and loopback mounts, but still
separate out the IDs in the tests. This makes the test run
much faster.
2013-09-30 17:35:00 -06:00
Alexander Larsson 381ce94ef4 Add DeviceSetWrapper
This wraps an existing DeviceSet and just adds a prefix to all ids in
it. This will be useful for reusing a single DeviceSet for all the tests
(but with separate ids)
2013-09-30 17:35:00 -06:00
Alexander Larsson 99393cf3cf Delete corresponding Devices when deleting Images
If an image is deleted and there is a corresponding device
for that image we also delete the image.
2013-09-30 17:35:00 -06:00
Alexander Larsson 30890c7763 Runtime: Delete corresponding devices when deleting container 2013-09-30 17:35:00 -06:00
Alexander Larsson b0626f403b Implement container.ExportRW() on device-mapper 2013-09-30 17:34:59 -06:00
Alexander Larsson fda6ff9c27 Make TarFilter more useful
There are a few changes:
* Callers can specify if they want recursive behaviour or not
* All file listings to tar are sent on stdin, to handle long lists better
* We can pass in a list of filenames which will be created as empty
  files in the tarball

This is exactly what we want for the creation of layer tarballs given
a container fs, a set of files to add and a set of whiteout files to create.
2013-09-30 17:34:59 -06:00
Alexander Larsson b86f67126c Archive: Fix up tar commandline arguments in TarFilter()
There is no need to duplicate the compression flags for
every element in the filter.
2013-09-30 17:34:59 -06:00
Alexander Larsson 1c5dc26a7c Implement docker diff for device-mapper
To do diffing we just compare file metadata, so this relies
on things like size and mtime/ctime to catch any changes.
Its *possible* to trick this by updating a file without
changing the size and setting back the mtime/ctime, but
that seems pretty unlikely to happen in reality, and lets
us avoid comparing the actual file data.
2013-09-30 17:34:59 -06:00
Alexander Larsson 8e7cbbff50 devmapper: Base the device-mapper names on the root dir name
This means the default is "docker-*", but for tests we get separate
prefixes for each test.
2013-09-30 17:34:59 -06:00
Alexander Larsson 074f38d493 Image: Always create a .docker-id file in the devices we create
Without this there is really no way to map back from the device-mapper
devices to the actual docker image/container ids in case the json file
somehow got lost
2013-09-30 17:34:59 -06:00
Alexander Larsson a9ec1dbc9b Image: Deactivate image device when unmounting container
There is no need to keep all the device-mapper devices active, we
can just activate them on demand if needed.
2013-09-30 17:34:59 -06:00
Alexander Larsson d2ba3e2005 Image: Initial support for device-mapper mounts
This supports creating images from layers and mounting them
for running a container.

Not supported yet are:
* Creating diffs between images/containers
* Creating layers for new images from a device-mapper container
2013-09-30 17:34:59 -06:00
Alexander Larsson 8f7361279c Runtime: Add MountMethod to allow AUFS and device-mapper to coexist 2013-09-30 17:34:59 -06:00
Alexander Larsson ca2f7f955e Runtime: Add DeviceSet singleton
This adds a DeviceSet singleton to the Runtime object which will be used for
any DeviceMapper dependent code.
2013-09-30 17:34:59 -06:00
Alexander Larsson 1d36b8c7b7 Server: Pass in device-mapper DeviceSet to server
This makes docker (but not docker-init) link to libdevmapper and will
allow it to use the DeviceSet
2013-09-30 17:34:59 -06:00
Alexander Larsson e6216793d9 Add DeviceSet interface
This interface matches the device-mapper implementation (DeviceSetDM)
but is free from any dependencies. This allows core docker code
to refer to a DeviceSet without having an explicit dependency on
the devmapper package.

This is important, because the devmapper package has external
dependencies which are not wanted in the docker client app, as it
needs to run with minimal dependencies in the docker image.
2013-09-30 17:34:58 -06:00
Alexander Larsson e368c8bb01 Image: Add runtime and container id args to Mount()
We will later need the runtime to get access to the VolumeSet
singleton, and the container id to have a name for the volume
for the container
2013-09-30 17:34:58 -06:00
Alexander Larsson 167601e858 Runtime: Automatically use docker-init if it exists
In some builds the main docker binary is not statically linked,
and as such not usable in as the .dockerinit binary, for those
cases we look for a separately shipped docker-init binary and
use that instead.
2013-09-30 17:34:58 -06:00
Alexander Larsson b8dc7b5f1a Add a separate docker-init binary
This may be used for the .dockerinit case if the main binary is not
statically linked.
2013-09-30 17:34:58 -06:00
Alexander Larsson 7fb3bfed03 devmapper: Add simple tool to test the DeviceSet commands 2013-09-30 17:34:58 -06:00
Alexander Larsson 374a5e9913 devmapper: Add DeviceSet device-mapper helper
This is a module that uses the device-mapper create CoW snapshots
You instantiate a DeviceSetDM object on a specified root (/var/lib/docker),
and it will create a subdirectory there called "loopback". It will
contain two sparse files which are loopback mounted into
a thin-pool device-mapper device called "docker-pool".

We then create a base snapshot in the pool with an empty filesystem
which can be used as a base for docker snapshots. It also keeps track
of the mapping between docker image ids and the snapshots in the pool.

Typical use of is something like (without error checking):

devices = NewDeviceSetDM("/var/lib/docker")
devices.AddDevice(imageId, "") // "" is the base image id
devices.MountDevice(imageId, "/mnt/image")
 ... extract base image to /mnt/image
devices.AddDevice(containerId, imageId)
devices.MountDevice(containerId, "/mnt/container")
... start container at /mnt/container
2013-09-30 17:34:58 -06:00
Alexander Larsson 459bac7127 Add libdevmapper wrapper 2013-09-30 17:34:58 -06:00
Victor Vieux 514886c73d Merge pull request #2023 from alexlarsson/old_kernel
Image: Fix time setting for old kernels
2013-09-27 05:25:13 -07:00
Alexander Larsson 75e958bf48 Image: Fix time setting for old kernels
This is a better fix for futimes() on kernels not supporting O_PATH.
The previous fix broke when copying a device, as it tried to open it
and got and error.
2013-09-26 23:59:37 +02:00
Victor Vieux ebfa24acb0 go fmt and aufs support removed 2013-09-26 15:40:13 +00:00
Victor Vieux 5e1d540209 Revert "add a -mount-method flag"
This reverts commit e52d756f40c9ccf8b37ca496cb72be057c909ed7.
2013-09-26 15:14:03 +00:00
Victor Vieux c1e25d7273 add a -mount-method flag 2013-09-26 15:10:01 +00:00
Alexander Larsson d263aa6ca9 RootIsShared: Fix root detection
Column 4 is the mount position, column 3 will not always be
"/" for the root. On one of my system its "/root".
2013-09-26 15:09:33 +00:00
Alexander Larsson 03320f0d1c Tests: Clean up any old devmapper leftovers before starting tests 2013-09-26 15:09:33 +00:00
Alexander Larsson 6c7ae06435 Image.Changes: Deactivate image device after unmounting it
There is no need to keep the image device around if we were the
onces creating the device.
2013-09-26 15:09:33 +00:00
Alexander Larsson 395bce4c41 Add DeviceSet.HasActivatedDevice()
This lets you see if a device has been activated
2013-09-26 15:09:33 +00:00
Alexander Larsson 41399ac005 image: Unmount before removing device in error paths
The device remove fails unless we unmount first
2013-09-26 15:09:33 +00:00
Alexander Larsson 67788723c9 runtime test: Ensure all containers are unmounted at nuke()
Otherwise we may leave around e.g. devmapper mounts
2013-09-26 15:09:33 +00:00
Alexander Larsson f99f39abaa ShellQuoteArguments: Fix quoting
This accidentally used two quotes to start/end each quoted string.
2013-09-26 15:09:33 +00:00
Alexander Larsson 009d0f9d81 Image: unmount device before removing it on failures
If we don't do this the remove will fail due to EBUSY
2013-09-26 15:09:32 +00:00
Alexander Larsson ed65815613 image: Handle systems that don't support O_PATH when updating timestamp
Older kernel can't handle O_PATH in open() so this will
fail on dirs and symlinks. For dirs wa can fallback to
the normal Utimes, but for symlinks there is not much to do
but ignore their timestamps.
2013-09-26 15:09:32 +00:00
Alexander Larsson cc28829429 devmapper: Fix loopback mount code
Typo in the loop-control code made it always fall back to the
old method of opening loopback devices.
2013-09-26 15:09:32 +00:00
Alexander Larsson 062a2b32e9 applyLayer() use btrfs reflinks if availible
We use the new file copy helper which uses btrfs reflinks if availible.
2013-09-26 15:09:32 +00:00
Alexander Larsson cda8754013 Add CopyFile that can use btrfs reflinks if availible 2013-09-26 15:09:32 +00:00
Alexander Larsson 5415804c9d Remove accidental commit that enabled MountMethodFilesystem 2013-09-26 15:09:32 +00:00
Alexander Larsson adae684987 Add trivial copy-based CoW backend
This creates a container by copying the corresponding files
from the layers into the containers. This is not gonna be very useful
on a developer setup, as there is no copy-on-write or general diskspace
sharing. It also makes container instantiation slower.

However, it may be useful in deployment where we don't always have a lot
of containers running (long-running daemons) and where we don't
do a lot of docker commits.
2013-09-26 15:09:32 +00:00
Alexander Larsson ad0a6a03e3 Add Changes.ChangesLayers()
This calculates the difference between a set of layers and a
directory tree.
2013-09-26 15:09:32 +00:00
Alexander Larsson 36603e68e3 Changes: Better metadata comparison
Change the comparison to better handle files that are copied during
container creation but not actually changed:

* Inode - this will change during a copy
* ctime - this will change during a copy (as we can't set it back)
* blocksize - this will change for sparse files during copy
* size for directories - this can change anytime but doesn't
  necessarily reflect an actual contents change
* Compare mtimes at microsecond precision (as this is what utimes has)
2013-09-26 15:09:32 +00:00
Alexander Larsson 99c7d129f4 Image.applyLayer: Be better at creating identical files
There are some changes here that make the file metadata better match
the layer files:

* Set the mode of the file after the chown, as otherwise the per-group/uid
  specific flags and e.g. sticky bit is lost
* Use lchown instead of chown
* Delay mtime updates to after all other changes so that later file
  creation doesn't change the mtime for the parent directory
* Use Futimes in combination with O_PATH|O_NOFOLLOW to set mtime on symlinks
2013-09-26 15:09:32 +00:00
Alexander Larsson 02b5f1369c Change how ChangesDirs() works
Rather than scan the files in the old directory twice to detect the
deletions we now scan both directories twice and then do all the
diffing on the in-memory structure.

This is more efficient, but it also lets us diff more complex things
later that are not exact on-disk trees.
2013-09-26 15:09:32 +00:00
Alexander Larsson d478a4bb54 RootIsShared() - Fix array out of bounds error
This happened for me on the last (empty) line, but better safe than sorry
so we make the check general.
2013-09-26 15:09:32 +00:00
Alexander Larsson c199ed228b devmapper: Move init layer to top rather than bottom
The init layer needs to be topmost to make sure certain files
are always there (for instance, the ubuntu:12.10 image wrongly
has /dev/shm being a symlink to /run/shm, and we need to override
that). However, previously the devmapper code implemented the
init layer by putting it in the base devmapper device, which meant
layers above it could override these files (so that ubuntu:12.10
broke).

So, instead we put the base layer in *each* images devmapper device.
This is "safe" because we still have the pristine layer data
in the layer directory. Also, it means we diff the container
against the image with the init layer applied, so it won't show
up in diffs/commits.
2013-09-26 15:09:32 +00:00
Alexander Larsson e40f5c7cb9 lxc: Work around lxc-start need for private mounts
lxc-start requires / to be mounted private, otherwise the changes
it does inside the container (both mounts and unmounts) will propagate
out to the host.

We work around this by starting up lxc-start in its own namespace where
we set / to rprivate.

Unfortunately go can't really execute any code between clone and exec,
so we can't do this in a nice way. Instead we have a horrible hack that
use the unshare command, the shell and the mount command...
2013-09-26 15:09:32 +00:00
Alexander Larsson d80be57c15 Utils: Add ShellQuoteArguments 2013-09-26 15:09:32 +00:00
Alexander Larsson 20bac716b5 Container: Inject into the mount, not the rwPath
For device-mapper setups we can't just push the file into the rwPath.
2013-09-26 15:09:32 +00:00
Alexander Larsson 2566e2604c api_test: Fix PostContainersCreate
We can't look for the created file in the rwpath, because that
doesn't exist in the device-mapper world, instead look in the
RootfsPath.
2013-09-26 15:09:32 +00:00
Alexander Larsson e1c418cac3 Runtime: Only remove device on destroy if it exists 2013-09-26 15:09:32 +00:00
Alexander Larsson 3343b3f8f8 graph test: Unmount image via image.Unmount()
This helps us track the unmount
2013-09-26 15:09:32 +00:00
Alexander Larsson c6e8813c97 deviceset: Cleanup device sets on test end
We unmount all mounts and deactivate all device mapper devices to
make sure we're left with no leftovers after the test.
2013-09-26 15:09:32 +00:00
Alexander Larsson 251a7ed437 DeviceSet: Add UnmountDevice()
Right now this does nothing but add a new layer, but it means
that all DeviceMounts are paired with DeviceUnmounts so that we
can track (and cleanup) active mounts.
2013-09-26 15:09:31 +00:00
Alexander Larsson 261b0b01df Always start tests from a clean set of loopback images
This way we don't get any issues with leftovers
2013-09-26 15:09:31 +00:00
Alexander Larsson a7fd1fce5d tests: Store the loopback images for test outside unit-tests
This directory is copied to each test prefix which is really
slow with the large loopback mounts.
2013-09-26 15:09:31 +00:00
Alexander Larsson 6938a36c69 Allow specifying the docker client path in _DOCKER_INIT_PATH
I currently need this to get the tests running, otherwise it will
mount the docker.test binary inside the containers, which doesn't
work due to the libdevmapper.so dependency.
2013-09-26 15:09:31 +00:00
Alexander Larsson bc7fa7b957 Limit the amount of prints during normal runs
This removes some Debugf() calls and chages some direct prints to
Debugf(). This means we don't get a bunch of spew when running the
tests.
2013-09-26 15:09:31 +00:00
Alexander Larsson d47c18c5fb Reuse a single DeviceSetDM for all the tests
We wrap the "real" DeviceSet for each test so that we get only
a single device-mapper pool and loopback mounts, but still
separate out the IDs in the tests. This makes the test run
much faster.
2013-09-26 15:09:31 +00:00
Alexander Larsson 0e686fa2f4 Add DeviceSetWrapper
This wraps an existing DeviceSet and just adds a prefix to all ids in
it. This will be useful for reusing a single DeviceSet for all the tests
(but with separate ids)
2013-09-26 15:09:31 +00:00
Alexander Larsson 3f3f5f0bba Delete corresponding Devices when deleting Images
If an image is deleted and there is a corresponding device
for that image we also delete the image.
2013-09-26 15:09:31 +00:00
Alexander Larsson 19ba0b851b Runtime: Delete corresponding devices when deleting container 2013-09-26 15:08:55 +00:00
Alexander Larsson 94fa3c7bb5 Implement container.ExportRW() on device-mapper 2013-09-26 15:08:55 +00:00
Alexander Larsson 223280f319 Make TarFilter more useful
There are a few changes:
* Callers can specify if they want recursive behaviour or not
* All file listings to tar are sent on stdin, to handle long lists better
* We can pass in a list of filenames which will be created as empty
  files in the tarball

This is exactly what we want for the creation of layer tarballs given
a container fs, a set of files to add and a set of whiteout files to create.
2013-09-26 15:08:55 +00:00
Alexander Larsson 8f23945f7f Archive: Fix up tar commandline arguments in TarFilter()
There is no need to duplicate the compression flags for
every element in the filter.
2013-09-26 15:08:55 +00:00
Alexander Larsson 8e8ef7cb5b Implement docker diff for device-mapper
To do diffing we just compare file metadata, so this relies
on things like size and mtime/ctime to catch any changes.
Its *possible* to trick this by updating a file without
changing the size and setting back the mtime/ctime, but
that seems pretty unlikely to happen in reality, and lets
us avoid comparing the actual file data.
2013-09-26 15:08:55 +00:00
Alexander Larsson 8f343ea65a devmapper: Base the device-mapper names on the root dir name
This means the default is "docker-*", but for tests we get separate
prefixes for each test.
2013-09-26 15:08:55 +00:00
Alexander Larsson b125f2334c Image: Always create a .docker-id file in the devices we create
Without this there is really no way to map back from the device-mapper
devices to the actual docker image/container ids in case the json file
somehow got lost
2013-09-26 15:08:55 +00:00
Alexander Larsson a89a51128e Image: Deactivate image device when unmounting container
There is no need to keep all the device-mapper devices active, we
can just activate them on demand if needed.
2013-09-26 15:08:54 +00:00
Alexander Larsson fcd41fe51a Image: Initial support for device-mapper mounts
This supports creating images from layers and mounting them
for running a container.

Not supported yet are:
* Creating diffs between images/containers
* Creating layers for new images from a device-mapper container
2013-09-26 15:08:54 +00:00
Alexander Larsson 53851474c0 Runtime: Add MountMethod to allow AUFS and device-mapper to coexist 2013-09-26 15:08:54 +00:00
Alexander Larsson f317a6b6fe Runtime: Add DeviceSet singleton
This adds a DeviceSet singleton to the Runtime object which will be used for
any DeviceMapper dependent code.
2013-09-26 15:08:54 +00:00
Alexander Larsson 87e248f524 Server: Pass in device-mapper DeviceSet to server
This makes docker (but not docker-init) link to libdevmapper and will
allow it to use the DeviceSet
2013-09-26 15:08:54 +00:00
Alexander Larsson ac194fc696 Add DeviceSet interface
This interface matches the device-mapper implementation (DeviceSetDM)
but is free from any dependencies. This allows core docker code
to refer to a DeviceSet without having an explicit dependency on
the devmapper package.

This is important, because the devmapper package has external
dependencies which are not wanted in the docker client app, as it
needs to run with minimal dependencies in the docker image.
2013-09-26 15:08:54 +00:00
Alexander Larsson 8637ba710e Image: Add runtime and container id args to Mount()
We will later need the runtime to get access to the VolumeSet
singleton, and the container id to have a name for the volume
for the container
2013-09-26 15:08:54 +00:00
Alexander Larsson 0f5ccf934e Runtime: Automatically use docker-init if it exists
In some builds the main docker binary is not statically linked,
and as such not usable in as the .dockerinit binary, for those
cases we look for a separately shipped docker-init binary and
use that instead.
2013-09-26 15:08:54 +00:00
Alexander Larsson 250bc3f615 Add a separate docker-init binary
This may be used for the .dockerinit case if the main binary is not
statically linked.
2013-09-26 15:08:54 +00:00
Alexander Larsson 2b1dc8a8a3 devmapper: Add simple tool to test the DeviceSet commands 2013-09-26 15:08:54 +00:00
Alexander Larsson 0b12702c0c devmapper: Add DeviceSet device-mapper helper
This is a module that uses the device-mapper create CoW snapshots
You instantiate a DeviceSetDM object on a specified root (/var/lib/docker),
and it will create a subdirectory there called "loopback". It will
contain two sparse files which are loopback mounted into
a thin-pool device-mapper device called "docker-pool".

We then create a base snapshot in the pool with an empty filesystem
which can be used as a base for docker snapshots. It also keeps track
of the mapping between docker image ids and the snapshots in the pool.

Typical use of is something like (without error checking):

devices = NewDeviceSetDM("/var/lib/docker")
devices.AddDevice(imageId, "") // "" is the base image id
devices.MountDevice(imageId, "/mnt/image")
 ... extract base image to /mnt/image
devices.AddDevice(containerId, imageId)
devices.MountDevice(containerId, "/mnt/container")
... start container at /mnt/container
2013-09-26 15:08:54 +00:00
Alexander Larsson 739af0a17f Add libdevmapper wrapper 2013-09-26 15:08:54 +00:00
113 changed files with 4430 additions and 2758 deletions
-1
View File
@@ -17,4 +17,3 @@ docs/_templates
bundles/
.hg/
.git/
vendor/pkg/
-2
View File
@@ -151,7 +151,6 @@ Roberto Hashioka <roberto_hashioka@hotmail.com>
Ryan Fowler <rwfowler@gmail.com>
Sam Alba <sam.alba@gmail.com>
Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
Scott Bessler <scottbessler@gmail.com>
Sean P. Kane <skane@newrelic.com>
Shawn Siefkas <shawn.siefkas@meredith.com>
Shih-Yuan Lee <fourdollars@gmail.com>
@@ -186,5 +185,4 @@ Vladimir Kirillov <proger@wilab.org.ua>
Walter Stanish <walter@pratyeka.org>
Wes Morgan <cap10morgan@gmail.com>
Will Dietz <w@wdtz.org>
Yang Bai <hamo.by@gmail.com>
Zaiste! <oh@zaiste.net>
-29
View File
@@ -1,34 +1,5 @@
# Changelog
## 0.6.5 (2013-10-29)
+ Runtime: Containers can now be named
+ Runtime: Containers can now be linked together for service discovery
+ Runtime: 'run -a', 'start -a' and 'attach' can forward signals to the container for better integration with process supervisors
+ Runtime: Automatically start crashed containers after a reboot
+ Runtime: Expose IP, port, and proto as separate environment vars for container links
* Runtime: Allow ports to be published to specific ips
* Runtime: Prohibit inter-container communication by default
* Documentation: Fix the flags for nc in example
- Client: Only pass stdin to hijack when needed to avoid closed pipe errors
* Testing: Remove warnings and prevent mount issues
* Client: Use less reflection in command-line method invocation
- Runtime: Ignore ErrClosedPipe for stdin in Container.Attach
- Testing: Change logic for tty resize to avoid warning in tests
- Client: Monitor the tty size after starting the container, not prior
- Hack: Update install.sh with $sh_c to get sudo/su for modprobe
- Client: Remove useless os.Exit() calls after log.Fatal
* Hack: Update all the mkimage scripts to use --numeric-owner as a tar argument
* Hack: Update hack/release.sh process to automatically invoke hack/make.sh and bail on build and test issues
+ Hack: Add initial init scripts library and a safer Ubuntu packaging script that works for Debian
- Runtime: Fix untag during removal of images
- Runtime: Remove unused field kernelVersion
* Hack: Add -p option to invoke debootstrap with http_proxy
- Builder: Fix race condition in docker build with verbose output
- Registry: Fix content-type for PushImageJSONIndex method
* Runtime: Fix issue when mounting subdirectories of /mnt in container
* Runtime: Check return value of syscall.Chdir when changing working directory inside dockerinit
* Contrib: Improve helper tools to generate debian and Arch linux server images
## 0.6.4 (2013-10-16)
- Runtime: Add cleanup of container when Start() fails
- Testing: Catch errClosing error when TCP and UDP proxies are terminated
+40 -26
View File
@@ -24,44 +24,58 @@
#
docker-version 0.6.1
from ubuntu:12.04
maintainer Solomon Hykes <solomon@dotcloud.com>
from ubuntu:12.10
maintainer Solomon Hykes <solomon@dotcloud.com>
# Build dependencies
run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list
run apt-get update
run apt-get install -y -q curl
run apt-get install -y -q git
run apt-get install -y -q mercurial
run apt-get install -y -q build-essential libsqlite3-dev
run apt-get update
run apt-get install -y -q curl
run apt-get install -y -q git
run apt-get install -y -q mercurial
run apt-get install -y -q build-essential
run apt-get install -y -q libsqlite3-dev
# Install Go
run curl -s https://go.googlecode.com/files/go1.2rc2.src.tar.gz | tar -v -C /usr/local -xz
env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor
run cd /usr/local/go/src && ./make.bash && go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
# Install Go from source (for eventual cross-compiling)
run curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C / -xz && mv /go /goroot
run cd /goroot/src && ./make.bash
env GOROOT /goroot
env PATH $PATH:/goroot/bin
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor
# Create Go cache with tag netgo (for static compilation of Go while preserving CGO support)
run go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
# Get lvm2 source for compiling statically
run git clone git://git.fedorahosted.org/git/lvm2.git /lvm2
run cd /lvm2 && git checkout v2_02_102
# can't use git clone -b because it's not supported by git versions before 1.7.10
run cd /lvm2 && ./configure --enable-static_link && make && make install_device-mapper
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
# Ubuntu stuff
run apt-get install -y -q ruby1.9.3 rubygems libffi-dev
run gem install --no-rdoc --no-ri fpm
run apt-get install -y -q reprepro dpkg-sig
run apt-get install -y -q ruby1.9.3 rubygems libffi-dev
run gem install --no-rdoc --no-ri fpm
run apt-get install -y -q reprepro dpkg-sig
# Install s3cmd 1.0.1 (earlier versions don't support env variables in the config)
run apt-get install -y -q python-pip
run pip install s3cmd
run pip install python-magic
run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg
run apt-get install -y -q python-pip
run pip install s3cmd
run pip install python-magic
run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg
# Runtime dependencies
run apt-get install -y -q iptables
run apt-get install -y -q lxc
run apt-get install -y -q aufs-tools
run apt-get install -y -q iptables
run dpkg-divert --local --rename --add /sbin/initctl && \
ln -s /bin/true /sbin/initctl && \
apt-get install -y -q lxc
volume /var/lib/docker
workdir /go/src/github.com/dotcloud/docker
volume /var/lib/docker
workdir /go/src/github.com/dotcloud/docker
# Wrap all commands in the "docker-in-docker" script to allow nested containers
entrypoint ["hack/dind"]
# Upload docker source
add . /go/src/github.com/dotcloud/docker
add . /go/src/github.com/dotcloud/docker
+3 -2
View File
@@ -1,7 +1,8 @@
Docker
Copyright 2012-2013 Docker, Inc.
Copyright 2012-2013 dotCloud, inc.
This product includes software developed at Docker, Inc. (http://www.docker.com).
This product includes software developed at dotCloud,
inc. (http://www.dotcloud.com).
This product contains software (https://github.com/kr/pty) developed
by Keith Rarick, licensed under the MIT License.
+6 -6
View File
@@ -193,10 +193,10 @@ wrong or incomplete.
*Brought to you courtesy of our legal counsel. For more context,
please see the Notice document.*
Transfers of Docker shall be in accordance with applicable export controls
of any country and all other applicable legal requirements. Without limiting the
foregoing, Docker shall not be distributed or downloaded to any individual or
location if such distribution or download would violate the applicable US
government export regulations.
Transfers of Docker shall be in accordance with applicable export
controls of any country and all other applicable legal requirements.
Docker shall not be distributed or downloaded to or in Cuba, Iran,
North Korea, Sudan or Syria and shall not be distributed or downloaded
to any person on the Denied Persons List administered by the U.S.
Department of Commerce.
For more information, please see http://www.bis.doc.gov
+1 -1
View File
@@ -1 +1 @@
0.6.5
0.6.4-dev
+73 -28
View File
@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/gograph"
"github.com/dotcloud/docker/utils"
"github.com/gorilla/mux"
"io"
@@ -42,9 +43,6 @@ func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
//If we don't do this, POST method without Content-type (even with empty body) will fail
func parseForm(r *http.Request) error {
if r == nil {
return nil
}
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
return err
}
@@ -138,23 +136,8 @@ func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r *
if vars == nil {
return fmt.Errorf("Missing parameter")
}
if err := parseForm(r); err != nil {
return err
}
name := vars["name"]
signal := 0
if r != nil {
s := r.Form.Get("signal")
if s != "" {
if s, err := strconv.Atoi(s); err != nil {
return err
} else {
signal = s
}
}
}
if err := srv.ContainerKill(name, signal); err != nil {
if err := srv.ContainerKill(name); err != nil {
return err
}
w.WriteHeader(http.StatusNoContent)
@@ -521,12 +504,8 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
}
func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return nil
}
config := &Config{}
out := &APIRun{}
name := r.Form.Get("name")
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
return err
@@ -542,7 +521,7 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
config.Dns = defaultDns
}
id, warnings, err := srv.ContainerCreate(config, name)
id, warnings, err := srv.ContainerCreate(config)
if err != nil {
return err
}
@@ -652,11 +631,8 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
// Register any links from the host config before starting the container
if err := srv.RegisterLinks(name, hostConfig); err != nil {
return err
}
if err := srv.ContainerStart(name, hostConfig); err != nil {
utils.Debugf("error ContainerStart: %s", err)
return err
}
w.WriteHeader(http.StatusNoContent)
@@ -1026,6 +1002,73 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s
}
}
func getContainersLinks(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
}
runtime := srv.runtime
all, err := getBoolParam(r.Form.Get("all"))
if err != nil {
return err
}
out := []APILink{}
err = runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error {
if container := runtime.Get(e.ID()); container != nil {
if !all && strings.Contains(p, container.ID) {
return nil
}
out = append(out, APILink{
Path: p,
ContainerID: container.ID,
Image: runtime.repositories.ImageName(container.Image),
})
}
return nil
}, -1)
if err != nil {
return err
}
return writeJSON(w, http.StatusOK, out)
}
func postContainerLink(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
}
values := make(map[string]string)
if matchesContentType(r.Header.Get("Content-Type"), "application/json") && r.Body != nil {
defer r.Body.Close()
dec := json.NewDecoder(r.Body)
if err := dec.Decode(&values); err != nil {
return err
}
} else {
return fmt.Errorf("Invalid json body")
}
currentName := values["currentName"]
newName := values["newName"]
if currentName == "" {
return fmt.Errorf("currentName cannot be empty")
}
if newName == "" {
return fmt.Errorf("newName cannot be empty")
}
if err := srv.runtime.RenameLink(currentName, newName); err != nil {
if strings.HasSuffix(err.Error(), "name are not unique") {
return fmt.Errorf("Conflict, %s already exists", newName)
}
return err
}
return nil
}
func createRouter(srv *Server, logging bool) (*mux.Router, error) {
r := mux.NewRouter()
@@ -1046,6 +1089,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/containers/{name:.*}/json": getContainersByName,
"/containers/{name:.*}/top": getContainersTop,
"/containers/{name:.*}/attach/ws": wsContainersAttach,
"/containers/links": getContainersLinks,
},
"POST": {
"/auth": postAuth,
@@ -1064,6 +1108,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/containers/{name:.*}/resize": postContainersResize,
"/containers/{name:.*}/attach": postContainersAttach,
"/containers/{name:.*}/copy": postContainersCopy,
"/containers/link": postContainerLink,
},
"DELETE": {
"/containers/{name:.*}": deleteContainers,
+23 -12
View File
@@ -17,18 +17,23 @@ type APIImages struct {
}
type APIInfo struct {
Debug bool
Containers int
Images int
NFd int `json:",omitempty"`
NGoroutines int `json:",omitempty"`
MemoryLimit bool `json:",omitempty"`
SwapLimit bool `json:",omitempty"`
IPv4Forwarding bool `json:",omitempty"`
LXCVersion string `json:",omitempty"`
NEventsListener int `json:",omitempty"`
KernelVersion string `json:",omitempty"`
IndexServerAddress string `json:",omitempty"`
Debug bool
Containers int
Images int
NFd int `json:",omitempty"`
NGoroutines int `json:",omitempty"`
MemoryLimit bool `json:",omitempty"`
SwapLimit bool `json:",omitempty"`
IPv4Forwarding bool `json:",omitempty"`
LXCVersion string `json:",omitempty"`
NEventsListener int `json:",omitempty"`
KernelVersion string `json:",omitempty"`
IndexServerAddress string `json:",omitempty"`
DevmapperPool string `json:",omitempty"`
DevmapperDataUsed uint64 `json:",omitempty"`
DevmapperDataTotal uint64 `json:",omitempty"`
DevmapperMetadataUsed uint64 `json:",omitempty"`
DevmapperMetadataTotal uint64 `json:",omitempty"`
}
type APITop struct {
@@ -121,3 +126,9 @@ type APICopy struct {
Resource string
HostPath string
}
type APILink struct {
Path string
ContainerID string
Image string
}
+21 -26
View File
@@ -347,12 +347,10 @@ func TestGetContainersJSON(t *testing.T) {
srv := &Server{runtime: runtime}
beginLen := runtime.containers.Len()
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "test"},
}, "")
})
if err != nil {
t.Fatal(err)
}
@@ -364,15 +362,17 @@ func TestGetContainersJSON(t *testing.T) {
}
r := httptest.NewRecorder()
if err := getContainersJSON(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
setTimeout(t, "getContainerJSON timed out", 5*time.Second, func() {
if err := getContainersJSON(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
})
containers := []APIContainers{}
if err := json.Unmarshal(r.Body.Bytes(), &containers); err != nil {
t.Fatal(err)
}
if len(containers) != beginLen+1 {
t.Fatalf("Expected %d container, %d found (started with: %d)", beginLen+1, len(containers), beginLen)
if len(containers) != 1 {
t.Fatalf("Expected %d container, %d found", 1, len(containers))
}
if containers[0].ID != container.ID {
t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.ID, containers[0].ID)
@@ -391,7 +391,6 @@ func TestGetContainersExport(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -403,7 +402,7 @@ func TestGetContainersExport(t *testing.T) {
}
r := httptest.NewRecorder()
if err = getContainersExport(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
if err := getContainersExport(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
@@ -442,7 +441,6 @@ func TestGetContainersChanges(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/rm", "/etc/passwd"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -487,7 +485,6 @@ func TestGetContainersTop(t *testing.T) {
Cmd: []string{"/bin/sh", "-c", "cat"},
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -569,7 +566,6 @@ func TestGetContainersByName(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "test"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -601,7 +597,6 @@ func TestPostCommit(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -676,13 +671,21 @@ func TestPostContainersCreate(t *testing.T) {
t.Fatal(err)
}
if _, err := os.Stat(path.Join(container.rwPath(), "test")); err != nil {
if err := container.EnsureMounted(); err != nil {
t.Fatalf("Unable to mount container: %s", err)
}
if _, err := os.Stat(path.Join(container.RootfsPath(), "test")); err != nil {
if os.IsNotExist(err) {
utils.Debugf("Err: %s", err)
t.Fatalf("The test file has not been created")
}
t.Fatal(err)
}
if err := container.Unmount(); err != nil {
t.Fatalf("Unable to unmount container: %s", err)
}
}
func TestPostContainersKill(t *testing.T) {
@@ -697,7 +700,6 @@ func TestPostContainersKill(t *testing.T) {
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -737,10 +739,9 @@ func TestPostContainersRestart(t *testing.T) {
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/top"},
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -795,7 +796,6 @@ func TestPostContainersStart(t *testing.T) {
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -845,10 +845,9 @@ func TestPostContainersStop(t *testing.T) {
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/top"},
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -896,7 +895,6 @@ func TestPostContainersWait(t *testing.T) {
Cmd: []string{"/bin/sleep", "1"},
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -939,7 +937,6 @@ func TestPostContainersAttach(t *testing.T) {
Cmd: []string{"/bin/cat"},
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1029,7 +1026,6 @@ func TestPostContainersAttachStderr(t *testing.T) {
Cmd: []string{"/bin/sh", "-c", "/bin/cat >&2"},
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1119,7 +1115,7 @@ func TestDeleteContainers(t *testing.T) {
container, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test"},
}, "")
})
if err != nil {
t.Fatal(err)
}
@@ -1311,7 +1307,6 @@ func TestPostContainersCopy(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test.txt"},
},
"",
)
if err != nil {
t.Fatal(err)
+85 -8
View File
@@ -80,20 +80,74 @@ func (compression *Compression) Extension() string {
// 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) {
return TarFilter(path, compression, nil)
return TarFilter(path, compression, nil, true, nil)
}
func escapeName(name string) string {
escaped := make([]byte, 0)
for i, c := range []byte(name) {
if i == 0 && c == '/' {
continue
}
// all printable chars except "-" which is 0x2d
if (0x20 <= c && c <= 0x7E) && c != 0x2d {
escaped = append(escaped, c)
} else {
escaped = append(escaped, fmt.Sprintf("\\%03o", c)...)
}
}
return string(escaped)
}
// 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", "--numeric-owner", "-f", "-", "-C", path}
func TarFilter(path string, compression Compression, filter []string, recursive bool, createFiles []string) (io.Reader, error) {
args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-"}
if filter == nil {
filter = []string{"."}
}
for _, f := range filter {
args = append(args, "-c"+compression.Flag(), f)
args = append(args, "-c"+compression.Flag())
if !recursive {
args = append(args, "--no-recursion")
}
return CmdStream(exec.Command(args[0], args[1:]...))
files := ""
for _, f := range filter {
files = files + escapeName(f) + "\n"
}
tmpDir := ""
if createFiles != nil {
var err error // Can't use := here or we override the outer tmpDir
tmpDir, err = ioutil.TempDir("", "docker-tar")
if err != nil {
return nil, err
}
files = files + "-C" + tmpDir + "\n"
for _, f := range createFiles {
path := filepath.Join(tmpDir, f)
err := os.MkdirAll(filepath.Dir(path), 0600)
if err != nil {
return nil, err
}
if file, err := os.OpenFile(path, os.O_CREATE, 0600); err != nil {
return nil, err
} else {
file.Close()
}
files = files + escapeName(f) + "\n"
}
}
return CmdStream(exec.Command(args[0], args[1:]...), &files, func() {
if tmpDir != "" {
_ = os.RemoveAll(tmpDir)
}
})
}
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
@@ -140,7 +194,7 @@ func Untar(archive io.Reader, path string) error {
// 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)
archive, err := TarFilter(src, Uncompressed, filter, true, nil)
if err != nil {
return err
}
@@ -227,13 +281,33 @@ func CopyFileWithTar(src, dst string) error {
// CmdStream executes a command, and returns its stdout as a stream.
// If the command fails to run or doesn't complete successfully, an error
// will be returned, including anything written on stderr.
func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
func CmdStream(cmd *exec.Cmd, input *string, atEnd func()) (io.Reader, error) {
if input != nil {
stdin, err := cmd.StdinPipe()
if err != nil {
if atEnd != nil {
atEnd()
}
return nil, err
}
// Write stdin if any
go func() {
_, _ = stdin.Write([]byte(*input))
stdin.Close()
}()
}
stdout, err := cmd.StdoutPipe()
if err != nil {
if atEnd != nil {
atEnd()
}
return nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
if atEnd != nil {
atEnd()
}
return nil, err
}
pipeR, pipeW := io.Pipe()
@@ -258,6 +332,9 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
} else {
pipeW.Close()
}
if atEnd != nil {
atEnd()
}
}()
// Run the command and return the pipe
if err := cmd.Start(); err != nil {
+3 -3
View File
@@ -14,7 +14,7 @@ import (
func TestCmdStreamLargeStderr(t *testing.T) {
cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
out, err := CmdStream(cmd)
out, err := CmdStream(cmd, nil, nil)
if err != nil {
t.Fatalf("Failed to start command: %s", err)
}
@@ -35,7 +35,7 @@ func TestCmdStreamLargeStderr(t *testing.T) {
func TestCmdStreamBad(t *testing.T) {
badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
out, err := CmdStream(badCmd)
out, err := CmdStream(badCmd, nil, nil)
if err != nil {
t.Fatalf("Failed to start command: %s", err)
}
@@ -50,7 +50,7 @@ func TestCmdStreamBad(t *testing.T) {
func TestCmdStreamGood(t *testing.T) {
cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0")
out, err := CmdStream(cmd)
out, err := CmdStream(cmd, nil, nil)
if err != nil {
t.Fatal(err)
}
+12 -17
View File
@@ -176,6 +176,8 @@ func (b *buildFile) CmdEnv(args string) error {
func (b *buildFile) CmdCmd(args string) error {
var cmd []string
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
// If the unmarshal fails, it is not an error, we just use the
// args as a string.
utils.Debugf("Error unmarshalling: %s, setting cmd to /bin/sh -c", err)
cmd = []string{"/bin/sh", "-c", args}
}
@@ -187,6 +189,9 @@ func (b *buildFile) CmdCmd(args string) error {
}
func (b *buildFile) CmdExpose(args string) error {
if strings.Contains(args, ":") {
return fmt.Errorf("EXPOSE cannot be used to bind to a host ip or port")
}
ports := strings.Split(args, " ")
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
@@ -296,7 +301,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error {
}
// First try to unpack the source as an archive
} else if err := UntarPath(origPath, destPath); err != nil {
utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err)
utils.Debugf("[tar] Not a directory nor a tar archive. Copying as a file. Untar error from %s to %s: %s", origPath, destPath, err)
// If that fails, just copy it as a regular file
if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
return err
@@ -332,7 +337,7 @@ func (b *buildFile) CmdAdd(args string) error {
b.config.Image = b.image
// Create the container and start it
container, _, err := b.runtime.Create(b.config, "")
container, _, err := b.runtime.Create(b.config)
if err != nil {
return err
}
@@ -367,7 +372,7 @@ func (b *buildFile) run() (string, error) {
b.config.Image = b.image
// Create the container and start it
c, _, err := b.runtime.Create(b.config, "")
c, _, err := b.runtime.Create(b.config)
if err != nil {
return "", err
}
@@ -378,22 +383,15 @@ func (b *buildFile) run() (string, error) {
c.Path = b.config.Cmd[0]
c.Args = b.config.Cmd[1:]
var errCh chan error
if b.verbose {
errCh = utils.Go(func() error {
return <-c.Attach(nil, nil, b.out, b.out)
})
}
//start the container
hostConfig := &HostConfig{}
if err := c.Start(hostConfig); err != nil {
return "", err
}
if errCh != nil {
if err := <-errCh; err != nil {
if b.verbose {
err = <-c.Attach(nil, nil, b.out, b.out)
if err != nil {
return "", err
}
}
@@ -430,13 +428,10 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
}
}
container, warnings, err := b.runtime.Create(b.config, "")
container, _, err := b.runtime.Create(b.config)
if err != nil {
return err
}
for _, warning := range warnings {
fmt.Fprintf(b.out, " ---> [Warning] %s\n", warning)
}
b.tmpContainers[container.ID] = struct{}{}
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
id = container.ID
+259 -49
View File
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strings"
"syscall"
)
type ChangeType int
@@ -33,74 +34,283 @@ func (change *Change) String() string {
return fmt.Sprintf("%s %s", kind, change.Path)
}
func Changes(layers []string, rw string) ([]Change, error) {
type FileInfo struct {
parent *FileInfo
name string
stat syscall.Stat_t
children map[string]*FileInfo
}
func (root *FileInfo) LookUp(path string) *FileInfo {
parent := root
if path == "/" {
return root
}
pathElements := strings.Split(path, "/")
for _, elem := range pathElements {
if elem != "" {
child := parent.children[elem]
if child == nil {
return nil
}
parent = child
}
}
return parent
}
func (info *FileInfo) path() string {
if info.parent == nil {
return "/"
}
return filepath.Join(info.parent.path(), info.name)
}
func (info *FileInfo) unlink() {
if info.parent != nil {
delete(info.parent.children, info.name)
}
}
func (info *FileInfo) Remove(path string) bool {
child := info.LookUp(path)
if child != nil {
child.unlink()
return true
}
return false
}
func (info *FileInfo) isDir() bool {
return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR
}
func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
if oldInfo == nil {
// add
change := Change{
Path: info.path(),
Kind: ChangeAdd,
}
*changes = append(*changes, change)
}
// We make a copy so we can modify it to detect additions
// also, we only recurse on the old dir if the new info is a directory
// otherwise any previous delete/change is considered recursive
oldChildren := make(map[string]*FileInfo)
if oldInfo != nil && info.isDir() {
for k, v := range oldInfo.children {
oldChildren[k] = v
}
}
for name, newChild := range info.children {
oldChild, _ := oldChildren[name]
if oldChild != nil {
// change?
oldStat := &oldChild.stat
newStat := &newChild.stat
// Note: We can't compare inode or ctime or blocksize here, because these change
// when copying a file into a container. However, that is not generally a problem
// because any content change will change mtime, and any status change should
// be visible when actually comparing the stat fields. The only time this
// breaks down is if some code intentionally hides a change by setting
// back mtime
oldMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano())
newMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano())
if oldStat.Mode != newStat.Mode ||
oldStat.Uid != newStat.Uid ||
oldStat.Gid != newStat.Gid ||
oldStat.Rdev != newStat.Rdev ||
// Don't look at size for dirs, its not a good measure of change
(oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) ||
oldMtime.Sec != newMtime.Sec ||
oldMtime.Usec != newMtime.Usec {
change := Change{
Path: newChild.path(),
Kind: ChangeModify,
}
*changes = append(*changes, change)
}
// Remove from copy so we can detect deletions
delete(oldChildren, name)
}
newChild.addChanges(oldChild, changes)
}
for _, oldChild := range oldChildren {
// delete
change := Change{
Path: oldChild.path(),
Kind: ChangeDelete,
}
*changes = append(*changes, change)
}
}
func (info *FileInfo) Changes(oldInfo *FileInfo) []Change {
var changes []Change
err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
info.addChanges(oldInfo, &changes)
return changes
}
func newRootFileInfo() *FileInfo {
root := &FileInfo{
name: "/",
children: make(map[string]*FileInfo),
}
return root
}
func applyLayer(root *FileInfo, layer string) error {
err := filepath.Walk(layer, func(layerPath string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// Skip root
if layerPath == layer {
return nil
}
// rebase path
relPath, err := filepath.Rel(layer, layerPath)
if err != nil {
return err
}
relPath = filepath.Join("/", relPath)
// Skip AUFS metadata
if matched, err := filepath.Match("/.wh..wh.*", relPath); err != nil || matched {
if err != nil || !f.IsDir() {
return err
}
return filepath.SkipDir
}
var layerStat syscall.Stat_t
err = syscall.Lstat(layerPath, &layerStat)
if err != nil {
return err
}
file := filepath.Base(relPath)
// If there is a whiteout, then the file was removed
if strings.HasPrefix(file, ".wh.") {
originalFile := file[len(".wh."):]
deletePath := filepath.Join(filepath.Dir(relPath), originalFile)
root.Remove(deletePath)
} else {
// Added or changed file
existing := root.LookUp(relPath)
if existing != nil {
// Changed file
existing.stat = layerStat
if !existing.isDir() {
// Changed from dir to non-dir, delete all previous files
existing.children = make(map[string]*FileInfo)
}
} else {
// Added file
parent := root.LookUp(filepath.Dir(relPath))
if parent == nil {
return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
}
info := &FileInfo{
name: filepath.Base(relPath),
children: make(map[string]*FileInfo),
parent: parent,
stat: layerStat,
}
parent.children[info.name] = info
}
}
return nil
})
return err
}
func collectFileInfo(sourceDir string) (*FileInfo, error) {
root := newRootFileInfo()
err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// Rebase path
path, err = filepath.Rel(rw, path)
relPath, err := filepath.Rel(sourceDir, path)
if err != nil {
return err
}
path = filepath.Join("/", path)
relPath = filepath.Join("/", relPath)
// Skip root
if path == "/" {
if relPath == "/" {
return nil
}
// Skip AUFS metadata
if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
parent := root.LookUp(filepath.Dir(relPath))
if parent == nil {
return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
}
info := &FileInfo{
name: filepath.Base(relPath),
children: make(map[string]*FileInfo),
parent: parent,
}
if err := syscall.Lstat(path, &info.stat); err != nil {
return err
}
change := Change{
Path: path,
}
parent.children[info.name] = info
// Find out what kind of modification happened
file := filepath.Base(path)
// If there is a whiteout, then the file was removed
if strings.HasPrefix(file, ".wh.") {
originalFile := file[len(".wh."):]
change.Path = filepath.Join(filepath.Dir(path), originalFile)
change.Kind = ChangeDelete
} else {
// Otherwise, the file was added
change.Kind = ChangeAdd
// ...Unless it already existed in a top layer, in which case, it's a modification
for _, layer := range layers {
stat, err := os.Stat(filepath.Join(layer, path))
if err != nil && !os.IsNotExist(err) {
return err
}
if err == nil {
// The file existed in the top layer, so that's a modification
// However, if it's a directory, maybe it wasn't actually modified.
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
if stat.IsDir() && f.IsDir() {
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
// Both directories are the same, don't record the change
return nil
}
}
change.Kind = ChangeModify
break
}
}
}
// Record change
changes = append(changes, change)
return nil
})
if err != nil && !os.IsNotExist(err) {
if err != nil {
return nil, err
}
return changes, nil
return root, nil
}
// Compare a directory with an array of layer directories it was based on and
// generate an array of Change objects describing the changes
func ChangesLayers(newDir string, layers []string) ([]Change, error) {
newRoot, err := collectFileInfo(newDir)
if err != nil {
return nil, err
}
oldRoot := newRootFileInfo()
for i := len(layers) - 1; i >= 0; i-- {
layer := layers[i]
if err = applyLayer(oldRoot, layer); err != nil {
return nil, err
}
}
return newRoot.Changes(oldRoot), nil
}
// Compare two directories and generate an array of Change objects describing the changes
func ChangesDirs(newDir, oldDir string) ([]Change, error) {
oldRoot, err := collectFileInfo(oldDir)
if err != nil {
return nil, err
}
newRoot, err := collectFileInfo(newDir)
if err != nil {
return nil, err
}
return newRoot.Changes(oldRoot), nil
}
+146 -216
View File
@@ -42,13 +42,9 @@ var (
ErrConnectionRefused = errors.New("Can't connect to docker daemon. Is 'docker -d' running on this host?")
)
func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) {
func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
method := reflect.ValueOf(cli).MethodByName(methodName)
if !method.IsValid() {
return nil, false
}
return method.Interface().(func(...string) error), true
return reflect.TypeOf(cli).MethodByName(methodName)
}
func ParseCommands(proto, addr string, args ...string) error {
@@ -60,7 +56,14 @@ func ParseCommands(proto, addr string, args ...string) error {
fmt.Println("Error: Command not found:", args[0])
return cli.CmdHelp(args[1:]...)
}
return method(args[1:]...)
ret := method.Func.CallSlice([]reflect.Value{
reflect.ValueOf(cli),
reflect.ValueOf(args[1:]),
})[0].Interface()
if ret == nil {
return nil
}
return ret.(error)
}
return cli.CmdHelp(args...)
}
@@ -71,7 +74,10 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
if !exists {
fmt.Fprintf(cli.err, "Error: Command not found: %s\n", args[0])
} else {
method("--help")
method.Func.CallSlice([]reflect.Value{
reflect.ValueOf(cli),
reflect.ValueOf([]string{"--help"}),
})[0].Interface()
return nil
}
}
@@ -94,6 +100,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
{"login", "Register or Login to the docker registry server"},
{"logs", "Fetch the logs of a container"},
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
{"top", "Lookup the running processes of a container"},
{"ps", "List containers"},
{"pull", "Pull an image or a repository from the docker registry server"},
{"push", "Push an image or a repository to the docker registry server"},
@@ -105,7 +112,6 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
{"start", "Start a stopped container"},
{"stop", "Stop a running container"},
{"tag", "Tag an image into a repository"},
{"top", "Lookup the running processes of a container"},
{"version", "Show the docker version information"},
{"wait", "Block until a container stops, then print its exit code"},
} {
@@ -460,6 +466,11 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
fmt.Fprintf(cli.out, "Containers: %d\n", out.Containers)
fmt.Fprintf(cli.out, "Images: %d\n", out.Images)
if out.DevmapperDataTotal != 0 {
fmt.Fprintf(cli.out, "Devmapper disk use: Data: %.1f/%.1f Metadata: %.1f/%.1f\n",
float64(out.DevmapperDataUsed)/(1024*1024), float64(out.DevmapperDataTotal)/(1024*1024),
float64(out.DevmapperMetadataUsed)/(1024*1024), float64(out.DevmapperMetadataTotal)/(1024*1024))
}
if out.Debug || os.Getenv("DEBUG") != "" {
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", out.Debug)
fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
@@ -468,6 +479,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
fmt.Fprintf(cli.out, "LXC Version: %s\n", out.LXCVersion)
fmt.Fprintf(cli.out, "EventsListeners: %d\n", out.NEventsListener)
fmt.Fprintf(cli.out, "Kernel Version: %s\n", out.KernelVersion)
fmt.Fprintf(cli.out, "Devmapper pool: %s\n", out.DevmapperPool)
}
if len(out.IndexServerAddress) != 0 {
@@ -540,23 +552,8 @@ func (cli *DockerCli) CmdRestart(args ...string) error {
return nil
}
func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
sigc := make(chan os.Signal, 1)
utils.CatchAll(sigc)
go func() {
for s := range sigc {
if _, _, err := cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%d", cid, s), nil); err != nil {
utils.Debugf("Error sending signal: %s", err)
}
}
}()
return sigc
}
func (cli *DockerCli) CmdStart(args ...string) error {
cmd := Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
attach := cmd.Bool("a", false, "Attach container's stdout/stderr and forward all signals to the process")
openStdin := cmd.Bool("i", false, "Attach container's stdin")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -565,75 +562,17 @@ func (cli *DockerCli) CmdStart(args ...string) error {
return nil
}
var cErr chan error
if *attach || *openStdin {
if cmd.NArg() > 1 {
return fmt.Errorf("Impossible to start and attach multiple containers at once.")
}
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
if err != nil {
return err
}
container := &Container{}
err = json.Unmarshal(body, container)
if err != nil {
return err
}
if !container.Config.Tty {
sigc := cli.forwardAllSignals(cmd.Arg(0))
defer utils.StopCatch(sigc)
}
if container.Config.Tty && cli.isTerminal {
if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
return err
}
}
var in io.ReadCloser
v := url.Values{}
v.Set("stream", "1")
if *openStdin && container.Config.OpenStdin {
v.Set("stdin", "1")
in = cli.in
}
v.Set("stdout", "1")
v.Set("stderr", "1")
cErr = utils.Go(func() error {
return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil)
})
}
var encounteredError error
for _, name := range cmd.Args() {
for _, name := range args {
_, _, err := cli.call("POST", "/containers/"+name+"/start", nil)
if err != nil {
if !*attach || !*openStdin {
fmt.Fprintf(cli.err, "%s\n", err)
encounteredError = fmt.Errorf("Error: failed to start one or more containers")
}
fmt.Fprintf(cli.err, "%s\n", err)
encounteredError = fmt.Errorf("Error: failed to start one or more containers")
} else {
if !*attach || !*openStdin {
fmt.Fprintf(cli.out, "%s\n", name)
}
fmt.Fprintf(cli.out, "%s\n", name)
}
}
if encounteredError != nil {
if *openStdin || *attach {
cli.in.Close()
<-cErr
}
return encounteredError
}
if *openStdin || *attach {
return <-cErr
}
return nil
return encounteredError
}
func (cli *DockerCli) CmdInspect(args ...string) error {
@@ -645,39 +584,30 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
cmd.Usage()
return nil
}
indented := new(bytes.Buffer)
status := 0
for _, name := range args {
fmt.Fprintf(cli.out, "[")
for i, name := range args {
if i > 0 {
fmt.Fprintf(cli.out, ",")
}
obj, _, err := cli.call("GET", "/containers/"+name+"/json", nil)
if err != nil {
obj, _, err = cli.call("GET", "/images/"+name+"/json", nil)
if err != nil {
fmt.Fprintf(cli.err, "No such image or container: %s\n", name)
status = 1
continue
}
}
indented := new(bytes.Buffer)
if err = json.Indent(indented, obj, "", " "); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
indented.WriteString(",")
}
// Remove trailling ','
indented.Truncate(indented.Len() - 1)
fmt.Fprintf(cli.out, "[")
if _, err := io.Copy(cli.out, indented); err != nil {
return err
if _, err := io.Copy(cli.out, indented); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
}
}
fmt.Fprintf(cli.out, "]")
if status != 0 {
return &utils.StatusError{Status: status}
}
return nil
}
@@ -724,11 +654,11 @@ func (cli *DockerCli) CmdPort(args ...string) error {
}
port := cmd.Arg(1)
proto := "tcp"
proto := "Tcp"
parts := strings.SplitN(port, "/", 2)
if len(parts) == 2 && len(parts[1]) != 0 {
port = parts[0]
proto = parts[1]
proto = strings.ToUpper(parts[1][:1]) + strings.ToLower(parts[1][1:])
}
body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
if err != nil {
@@ -740,14 +670,8 @@ func (cli *DockerCli) CmdPort(args ...string) error {
return err
}
if frontends, exists := out.NetworkSettings.Ports[Port(port+"/"+proto)]; exists {
if frontends == nil {
fmt.Fprintf(cli.out, "%s\n", port)
} else {
for _, frontend := range frontends {
fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort)
}
}
if frontend, exists := out.NetworkSettings.PortMapping[proto][port]; exists {
fmt.Fprintf(cli.out, "%s\n", frontend)
} else {
return fmt.Errorf("Error: No private port '%s' allocated on %s", cmd.Arg(1), cmd.Arg(0))
}
@@ -1167,17 +1091,14 @@ func (cli *DockerCli) CmdPs(args ...string) error {
if !*noTrunc {
out.ID = utils.TruncateID(out.ID)
}
// Remove the leading / from the names
for i := 0; i < len(out.Names); i++ {
out.Names[i] = out.Names[i][1:]
}
if !*quiet {
if !*noTrunc {
out.Command = utils.Trunc(out.Command, 20)
for i := 0; i < len(out.Names); i++ {
out.Names[i] = utils.Trunc(out.Names[i], 10)
}
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), strings.Join(out.Names, ","))
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), strings.Join(out.Names, ","))
if *size {
if out.SizeRootFs > 0 {
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
@@ -1198,6 +1119,64 @@ func (cli *DockerCli) CmdPs(args ...string) error {
return nil
}
func (cli *DockerCli) CmdLs(args ...string) error {
cmd := Subcmd("ls", "", "List links for containers")
flAll := cmd.Bool("a", false, "Show all links")
if err := cmd.Parse(args); err != nil {
return nil
}
v := url.Values{}
if *flAll {
v.Set("all", "1")
}
body, _, err := cli.call("GET", "/containers/links?"+v.Encode(), nil)
if err != nil {
return err
}
var links []APILink
if err := json.Unmarshal(body, &links); err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "NAME\tID\tIMAGE")
fmt.Fprintf(w, "\n")
sortLinks(links, func(i, j APILink) bool {
return len(i.Path) < len(j.Path)
})
for _, link := range links {
fmt.Fprintf(w, "%s\t%s\t%s", link.Path, utils.TruncateID(link.ContainerID), link.Image)
fmt.Fprintf(w, "\n")
}
w.Flush()
return nil
}
func (cli *DockerCli) CmdLink(args ...string) error {
cmd := Subcmd("link", "CURRENT_NAME NEW_NAME", "Link the container with a new name")
if err := cmd.Parse(args); err != nil {
return nil
}
if cmd.NArg() != 2 {
cmd.Usage()
return nil
}
body := map[string]string{
"currentName": cmd.Arg(0),
"newName": cmd.Arg(1),
}
_, _, err := cli.call("POST", "/containers/link", body)
if err != nil {
return err
}
return nil
}
func (cli *DockerCli) CmdCommit(args ...string) error {
cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes")
flComment := cmd.String("m", "", "Commit message")
@@ -1317,16 +1296,14 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
}
name := cmd.Arg(0)
if err := cli.hijack("POST", "/containers/"+name+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err, nil); err != nil {
if err := cli.hijack("POST", "/containers/"+name+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err); err != nil {
return err
}
return nil
}
func (cli *DockerCli) CmdAttach(args ...string) error {
cmd := Subcmd("attach", "[OPTIONS] CONTAINER", "Attach to a running container")
noStdin := cmd.Bool("nostdin", false, "Do not attach stdin")
proxy := cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
cmd := Subcmd("attach", "CONTAINER", "Attach to a running container")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -1350,29 +1327,19 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
return fmt.Errorf("Impossible to attach to a stopped container, start it first")
}
if container.Config.Tty && cli.isTerminal {
if container.Config.Tty {
if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
utils.Debugf("Error monitoring TTY size: %s", err)
utils.Debugf("Error monitoring tty size: %s", err)
}
}
var in io.ReadCloser
v := url.Values{}
v.Set("stream", "1")
if !*noStdin && container.Config.OpenStdin {
v.Set("stdin", "1")
in = cli.in
}
v.Set("stdin", "1")
v.Set("stdout", "1")
v.Set("stderr", "1")
if *proxy && !container.Config.Tty {
sigc := cli.forwardAllSignals(cmd.Arg(0))
defer utils.StopCatch(sigc)
}
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil); err != nil {
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, cli.in, cli.out, cli.err); err != nil {
return err
}
return nil
@@ -1523,13 +1490,6 @@ func (cli *DockerCli) CmdRun(args ...string) error {
flRm := cmd.Lookup("rm")
autoRemove, _ := strconv.ParseBool(flRm.Value.String())
flSigProxy := cmd.Lookup("sig-proxy")
sigProxy, _ := strconv.ParseBool(flSigProxy.Value.String())
flName := cmd.Lookup("name")
if config.Tty {
sigProxy = false
}
var containerIDFile *os.File
if len(hostConfig.ContainerIDFile) > 0 {
if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil {
@@ -1541,14 +1501,9 @@ func (cli *DockerCli) CmdRun(args ...string) error {
}
defer containerIDFile.Close()
}
containerValues := url.Values{}
name := flName.Value.String()
if name != "" {
containerValues.Set("name", name)
}
//create the container
body, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config)
body, statusCode, err := cli.call("POST", "/containers/create", config)
//if image not found try to pull it
if statusCode == 404 {
_, tag := utils.ParseRepositoryTag(config.Image)
@@ -1589,7 +1544,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
if err != nil {
return err
}
body, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config)
body, _, err = cli.call("POST", "/containers/create", config)
if err != nil {
return err
}
@@ -1613,15 +1568,12 @@ func (cli *DockerCli) CmdRun(args ...string) error {
}
}
if sigProxy {
sigc := cli.forwardAllSignals(runResult.ID)
defer utils.StopCatch(sigc)
//start the container
if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {
return err
}
var (
wait chan struct{}
errCh chan error
)
var wait chan struct{}
if !config.AttachStdout && !config.AttachStderr {
// Make this asynchrone in order to let the client write to stdin before having to read the ID
@@ -1632,18 +1584,24 @@ func (cli *DockerCli) CmdRun(args ...string) error {
}()
}
hijacked := make(chan bool)
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
if config.Tty {
if err := cli.monitorTtySize(runResult.ID); err != nil {
// When running the test suite, there is no terminal, just pipes.
// Discard the error then.
if os.Getenv("TEST") != "1" {
utils.Errorf("Error monitoring TTY size: %s\n", err)
}
}
}
v := url.Values{}
v.Set("logs", "1")
v.Set("stream", "1")
var out, stderr io.Writer
var in io.ReadCloser
if config.AttachStdin {
v.Set("stdin", "1")
in = cli.in
}
if config.AttachStdout {
v.Set("stdout", "1")
@@ -1658,36 +1616,18 @@ func (cli *DockerCli) CmdRun(args ...string) error {
}
}
errCh = utils.Go(func() error {
return cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked)
})
} else {
close(hijacked)
}
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
go func() {
for sig := range signals {
fmt.Printf("\nReceived signal: %s; cleaning up\n", sig)
if err := cli.CmdStop("-t", "4", runResult.ID); err != nil {
fmt.Printf("failed to stop container: %v", err)
}
}
}()
// Acknowledge the hijack before starting
select {
case <-hijacked:
case err := <-errCh:
if err != nil {
utils.Debugf("Error hijack: %s", err)
return err
}
}
//start the container
if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {
return err
}
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminal {
if err := cli.monitorTtySize(runResult.ID); err != nil {
utils.Errorf("Error monitoring TTY size: %s\n", err)
}
}
if errCh != nil {
if err := <-errCh; err != nil {
if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, out, stderr); err != nil {
utils.Debugf("Error hijack: %s", err)
return err
}
@@ -1697,19 +1637,13 @@ func (cli *DockerCli) CmdRun(args ...string) error {
// Detached mode
<-wait
} else {
running, status, err := getExitCode(cli, runResult.ID)
status, err := getExitCode(cli, runResult.ID)
if err != nil {
return err
}
if autoRemove {
if running {
return fmt.Errorf("Impossible to auto-remove a detached container")
}
// Wait for the process to
if _, _, err := cli.call("POST", "/containers/"+runResult.ID+"/wait", nil); err != nil {
return err
}
if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID, nil); err != nil {
_, _, err = cli.call("DELETE", "/containers/"+runResult.ID, nil)
if err != nil {
return err
}
}
@@ -1798,7 +1732,6 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
return nil, -1, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, -1, err
@@ -1876,7 +1809,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
return nil
}
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan bool) error {
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer) error {
// fixme: refactor client to support redirect
re := regexp.MustCompile("/+")
path = re.ReplaceAllString(path, "/")
@@ -1905,10 +1838,6 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
rwc, br := clientconn.Hijack()
defer rwc.Close()
if started != nil {
started <- true
}
var receiveStdout chan error
if stdout != nil {
@@ -1995,6 +1924,9 @@ func (cli *DockerCli) resizeTty(id string) {
}
func (cli *DockerCli) monitorTtySize(id string) error {
if !cli.isTerminal {
return fmt.Errorf("Impossible to monitor size on non-tty")
}
cli.resizeTty(id)
sigchan := make(chan os.Signal, 1)
@@ -2042,22 +1974,20 @@ func waitForExit(cli *DockerCli, containerId string) (int, error) {
return out.StatusCode, nil
}
// getExitCode perform an inspect on the container. It returns
// the running state and the exit code.
func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
func getExitCode(cli *DockerCli, containerId string) (int, error) {
body, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil)
if err != nil {
// If we can't connect, then the daemon probably died.
if err != ErrConnectionRefused {
return false, -1, err
return -1, err
}
return false, -1, nil
return -1, nil
}
c := &Container{}
if err := json.Unmarshal(body, c); err != nil {
return false, -1, err
return -1, err
}
return c.State.Running, c.State.ExitCode, nil
return c.State.ExitCode, nil
}
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
+16 -81
View File
@@ -84,24 +84,10 @@ func TestRunHostname(t *testing.T) {
}
})
container := globalRuntime.List()[0]
setTimeout(t, "CmdRun timed out", 10*time.Second, func() {
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
<-c
go func() {
cli.CmdWait(container.ID)
}()
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
t.Fatal(err)
}
})
// Cleanup pipes
if err := closeWrap(stdout, stdoutPipe); err != nil {
t.Fatal(err)
}
}
// TestRunWorkdir checks that 'docker run -w' correctly sets a custom working directory
@@ -129,24 +115,10 @@ func TestRunWorkdir(t *testing.T) {
}
})
container := globalRuntime.List()[0]
setTimeout(t, "CmdRun timed out", 10*time.Second, func() {
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
<-c
go func() {
cli.CmdWait(container.ID)
}()
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
t.Fatal(err)
}
})
// Cleanup pipes
if err := closeWrap(stdout, stdoutPipe); err != nil {
t.Fatal(err)
}
}
// TestRunWorkdirExists checks that 'docker run -w' correctly sets a custom working directory, even if it exists
@@ -174,24 +146,10 @@ func TestRunWorkdirExists(t *testing.T) {
}
})
container := globalRuntime.List()[0]
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
<-c
go func() {
cli.CmdWait(container.ID)
}()
if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
t.Fatal(err)
}
})
// Cleanup pipes
if err := closeWrap(stdout, stdoutPipe); err != nil {
t.Fatal(err)
}
}
func TestRunExit(t *testing.T) {
@@ -304,7 +262,7 @@ func TestRunDisconnectTty(t *testing.T) {
// We're simulating a disconnect so the return value doesn't matter. What matters is the
// fact that CmdRun returns.
if err := cli.CmdRun("-i", "-t", unitTestImageID, "/bin/cat"); err != nil {
utils.Debugf("Error CmdRun: %s", err)
utils.Debugf("Error CmdRun: %s\n", err)
}
close(c1)
@@ -435,14 +393,12 @@ func TestRunDetach(t *testing.T) {
container := globalRuntime.List()[0]
setTimeout(t, "Escape sequence timeout", 5*time.Second, func() {
stdinPipe.Write([]byte{16, 17})
stdinPipe.Write([]byte{'', ''})
if err := stdinPipe.Close(); err != nil {
t.Fatal(err)
}
})
closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
// wait for CmdRun to return
setTimeout(t, "Waiting for CmdRun timed out", 15*time.Second, func() {
<-ch
@@ -455,6 +411,7 @@ func TestRunDetach(t *testing.T) {
setTimeout(t, "Waiting for container to die timed out", 20*time.Second, func() {
container.Kill()
container.Wait()
})
}
@@ -466,62 +423,39 @@ func TestAttachDetach(t *testing.T) {
cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
defer cleanup(globalRuntime)
ch := make(chan struct{})
go func() {
defer close(ch)
go stdout.Read(make([]byte, 1024))
setTimeout(t, "Starting container timed out", 2*time.Second, func() {
if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil {
t.Fatal(err)
}
}()
var container *Container
setTimeout(t, "Reading container's id timed out", 10*time.Second, func() {
buf := make([]byte, 1024)
n, err := stdout.Read(buf)
if err != nil {
t.Fatal(err)
}
container = globalRuntime.List()[0]
if strings.Trim(string(buf[:n]), " \r\n") != container.ShortID() {
t.Fatalf("Wrong ID received. Expect %s, received %s", container.ShortID(), buf[:n])
}
})
setTimeout(t, "Starting container timed out", 10*time.Second, func() {
<-ch
})
container := globalRuntime.List()[0]
stdin, stdinPipe = io.Pipe()
stdout, stdoutPipe = io.Pipe()
cli = NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
ch = make(chan struct{})
ch := make(chan struct{})
go func() {
defer close(ch)
if err := cli.CmdAttach(container.ShortID()); err != nil {
if err != io.ErrClosedPipe {
t.Fatal(err)
}
t.Fatal(err)
}
}()
setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
if err != io.ErrClosedPipe {
t.Fatal(err)
}
t.Fatal(err)
}
})
setTimeout(t, "Escape sequence timeout", 5*time.Second, func() {
stdinPipe.Write([]byte{16, 17})
stdinPipe.Write([]byte{'', ''})
if err := stdinPipe.Close(); err != nil {
t.Fatal(err)
}
})
closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
// wait for CmdRun to return
setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() {
@@ -535,6 +469,7 @@ func TestAttachDetach(t *testing.T) {
setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() {
container.Kill()
container.Wait()
})
}
@@ -549,7 +484,7 @@ func TestAttachDisconnect(t *testing.T) {
go func() {
// Start a process in daemon mode
if err := cli.CmdRun("-d", "-i", unitTestImageID, "/bin/cat"); err != nil {
utils.Debugf("Error CmdRun: %s", err)
utils.Debugf("Error CmdRun: %s\n", err)
}
}()
@@ -635,7 +570,7 @@ func TestRunAutoRemove(t *testing.T) {
}
})
setTimeout(t, "CmdRun timed out", 10*time.Second, func() {
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
<-c
})
+99 -102
View File
@@ -1,7 +1,6 @@
package docker
import (
"bytes"
"encoding/json"
"errors"
"flag"
@@ -17,7 +16,6 @@ import (
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
@@ -44,7 +42,7 @@ type Container struct {
ResolvConfPath string
HostnamePath string
HostsPath string
Name string
FilesystemType string
cmd *exec.Cmd
stdout *utils.WriteBroadcaster
@@ -106,11 +104,7 @@ type BindMap struct {
}
var (
ErrContainerStart = errors.New("The container failed to start. Unkown error")
ErrContainerStartTimeout = errors.New("The container failed to start due to timed out.")
ErrInvalidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.")
ErrConflictAttachDetach = errors.New("Conflicting options: -a and -d")
ErrConflictDetachAutoRemove = errors.New("Conflicting options: -rm and -d")
ErrInvaidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.")
)
type KeyValuePair struct {
@@ -166,8 +160,6 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
flNetwork := cmd.Bool("n", true, "Enable networking for this container")
flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container")
flAutoRemove := cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
cmd.String("name", "", "Assign a name to the container")
if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
@@ -200,21 +192,17 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
var flLinks utils.ListOpts
cmd.Var(&flLinks, "link", "Add link to another container (name:alias)")
cmd.Var(&flLinks, "link", "Add link to another container (containerid:alias)")
if err := cmd.Parse(args); err != nil {
return nil, nil, cmd, err
}
if *flDetach && len(flAttach) > 0 {
return nil, nil, cmd, ErrConflictAttachDetach
return nil, nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
}
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
return nil, nil, cmd, ErrInvalidWorikingDirectory
return nil, nil, cmd, ErrInvaidWorikingDirectory
}
if *flDetach && *flAutoRemove {
return nil, nil, cmd, ErrConflictDetachAutoRemove
}
// If neither -d or -a are set, attach to everything by default
if len(flAttach) == 0 && !*flDetach {
if !*flDetach {
@@ -226,6 +214,10 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
}
}
if *flDetach && *flAutoRemove {
return nil, nil, cmd, fmt.Errorf("Conflicting options: -rm and -d")
}
var binds []string
// add any bind targets to the list of container volumes
@@ -367,12 +359,17 @@ func (settings *NetworkSettings) PortMappingAPI() []APIPort {
// Inject the io.Reader at the given path. Note: do not close the reader
func (container *Container) Inject(file io.Reader, pth string) error {
// Make sure the directory exists
if err := os.MkdirAll(path.Join(container.rwPath(), path.Dir(pth)), 0755); err != nil {
if err := container.EnsureMounted(); err != nil {
return err
}
// Make sure the directory exists
if err := os.MkdirAll(path.Join(container.RootfsPath(), path.Dir(pth)), 0755); err != nil {
return err
}
// FIXME: Handle permissions/already existing dest
dest, err := os.Create(path.Join(container.rwPath(), pth))
dest, err := os.Create(path.Join(container.RootfsPath(), pth))
if err != nil {
return err
}
@@ -530,13 +527,11 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
} else {
_, err = io.Copy(cStdin, stdin)
}
if err == io.ErrClosedPipe {
err = nil
}
if err != nil {
utils.Errorf("attach: stdin: %s", err)
}
errors <- err
// Discard error, expecting pipe error
errors <- nil
}()
}
}
@@ -825,6 +820,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
}
params := []string{
"lxc-start",
"-n", container.ID,
"-f", container.lxcConfigPath(),
"--",
@@ -856,7 +852,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
// Init any links between the parent and children
runtime := container.runtime
children, err := runtime.Children(container.Name)
children, err := runtime.Children(fmt.Sprintf("/%s", container.ID))
if err != nil {
return err
}
@@ -913,7 +909,21 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
params = append(params, "--", container.Path)
params = append(params, container.Args...)
container.cmd = exec.Command("lxc-start", params...)
if RootIsShared() {
// lxc-start really needs / to be private, or all kinds of stuff break
// What we really want is to clone into a new namespace and then
// mount / MS_REC|MS_PRIVATE, but since we can't really clone or fork
// without exec in go we have to do this horrible shell hack...
shellString :=
"mount --make-rprivate /; exec " +
utils.ShellQuoteArguments(params)
params = []string{
"unshare", "-m", "--", "/bin/sh", "-c", shellString,
}
}
container.cmd = exec.Command(params[0], params[1:]...)
// Setup logging of stdout and stderr to disk
if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil {
@@ -943,43 +953,12 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) {
container.ToDisk()
container.SaveHostConfig(hostConfig)
go container.monitor(hostConfig)
defer utils.Debugf("Container running: %v", container.State.Running)
// We wait for the container to be fully running.
// Timeout after 5 seconds. In case of broken pipe, just retry.
// Note: The container can run and finish correctly before
// the end of this loop
for now := time.Now(); time.Since(now) < 5*time.Second; {
// If the container dies while waiting for it, just return
if !container.State.Running {
return nil
}
output, err := exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput()
if err != nil {
utils.Debugf("Error with lxc-info: %s (%s)", err, output)
output, err = exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput()
if err != nil {
utils.Debugf("Second Error with lxc-info: %s (%s)", err, output)
return err
}
}
if strings.Contains(string(output), "RUNNING") {
return nil
}
utils.Debugf("Waiting for the container to start (running: %v): %s", container.State.Running, bytes.TrimSpace(output))
time.Sleep(50 * time.Millisecond)
}
if container.State.Running {
return ErrContainerStartTimeout
}
return ErrContainerStart
return nil
}
func (container *Container) Run() error {
if err := container.Start(&HostConfig{}); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
return err
}
container.Wait()
@@ -1054,13 +1033,10 @@ func (container *Container) allocateNetwork(hostConfig *HostConfig) error {
if container.Config.PortSpecs != nil {
utils.Debugf("Migrating port mappings for container: %s", strings.Join(container.Config.PortSpecs, ", "))
if err := migratePortMappings(container.Config, hostConfig); err != nil {
if err := migratePortMappings(container.Config); err != nil {
return err
}
container.Config.PortSpecs = nil
if err := container.SaveHostConfig(hostConfig); err != nil {
return err
}
}
portSpecs := make(map[Port]struct{})
@@ -1142,7 +1118,8 @@ func (container *Container) monitor(hostConfig *HostConfig) {
if container.cmd == nil {
utils.Debugf("monitor: waiting for container %s using waitLxc", container.ID)
if err := container.waitLxc(); err != nil {
utils.Errorf("monitor: while waiting for container %s, waitLxc had a problem: %s", container.ID, err)
// Discard the error as any signals or non 0 returns will generate an error
utils.Debugf("monitor: while waiting for container %s, waitLxc had a problem: %s", container.ShortID(), err)
}
} else {
utils.Debugf("monitor: waiting for container %s using cmd.Wait", container.ID)
@@ -1221,57 +1198,54 @@ func (container *Container) cleanup() {
}
}
func (container *Container) kill(sig int) error {
container.State.Lock()
defer container.State.Unlock()
func (container *Container) kill() error {
if !container.State.Running {
return nil
}
if output, err := exec.Command("lxc-kill", "-n", container.ID, strconv.Itoa(sig)).CombinedOutput(); err != nil {
log.Printf("error killing container %s (%s, %s)", container.ShortID(), output, err)
return err
}
return nil
}
func (container *Container) Kill() error {
if !container.State.Running {
return nil
}
// 1. Send SIGKILL
if err := container.kill(9); err != nil {
return err
// Sending SIGKILL to the process via lxc
output, err := exec.Command("lxc-kill", "-n", container.ID, "9").CombinedOutput()
if err != nil {
log.Printf("error killing container %s (%s, %s)", container.ID, output, err)
}
// 2. Wait for the process to die, in last resort, try to kill the process directly
if err := container.WaitTimeout(10 * time.Second); err != nil {
if container.cmd == nil {
return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.ShortID())
return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.ID)
}
log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %s - trying direct SIGKILL", "SIGKILL", container.ShortID())
log.Printf("Container %s failed to exit within 10 seconds of lxc SIGKILL - trying direct SIGKILL", container.ID)
if err := container.cmd.Process.Kill(); err != nil {
return err
}
}
// Wait for the container to be actually stopped
container.Wait()
return nil
}
func (container *Container) Kill() error {
container.State.Lock()
defer container.State.Unlock()
if !container.State.Running {
return nil
}
return container.kill()
}
func (container *Container) Stop(seconds int) error {
container.State.Lock()
defer container.State.Unlock()
if !container.State.Running {
return nil
}
// 1. Send a SIGTERM
if err := container.kill(15); err != nil {
utils.Debugf("Error sending kill SIGTERM: %s", err)
if output, err := exec.Command("lxc-kill", "-n", container.ID, "15").CombinedOutput(); err != nil {
log.Print(string(output))
log.Print("Failed to send SIGTERM to the process, force killing")
if err := container.kill(9); err != nil {
if err := container.kill(); err != nil {
return err
}
}
@@ -1279,8 +1253,7 @@ func (container *Container) Stop(seconds int) error {
// 2. Wait for the process to exit on its own
if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil {
log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds)
// 3. If it doesn't, then send SIGKILL
if err := container.Kill(); err != nil {
if err := container.kill(); err != nil {
return err
}
}
@@ -1313,7 +1286,15 @@ func (container *Container) Resize(h, w int) error {
}
func (container *Container) ExportRw() (Archive, error) {
return Tar(container.rwPath(), Uncompressed)
if err := container.EnsureMounted(); err != nil {
return nil, err
}
image, err := container.GetImage()
if err != nil {
return nil, err
}
return image.ExportChanges(container.runtime, container.RootfsPath(), container.rwPath(), container.ID)
}
func (container *Container) RwChecksum() (string, error) {
@@ -1355,20 +1336,33 @@ func (container *Container) EnsureMounted() error {
return container.Mount()
}
func (container *Container) EnsureUnmounted() error {
if mounted, err := container.Mounted(); err != nil {
return err
} else if !mounted {
return nil
}
return container.Unmount()
}
func (container *Container) Mount() error {
image, err := container.GetImage()
if err != nil {
return err
}
return image.Mount(container.RootfsPath(), container.rwPath())
return image.Mount(container.runtime, container.RootfsPath(), container.rwPath(), container.ID)
}
func (container *Container) Changes() ([]Change, error) {
if err := container.EnsureMounted(); err != nil {
return nil, err
}
image, err := container.GetImage()
if err != nil {
return nil, err
}
return image.Changes(container.rwPath())
return image.Changes(container.runtime, container.RootfsPath(), container.rwPath(), container.ID)
}
func (container *Container) GetImage() (*Image, error) {
@@ -1379,17 +1373,20 @@ func (container *Container) GetImage() (*Image, error) {
}
func (container *Container) Mounted() (bool, error) {
return Mounted(container.RootfsPath())
image, err := container.GetImage()
if err != nil {
return false, err
}
return image.Mounted(container.runtime, container.RootfsPath(), container.rwPath())
}
func (container *Container) Unmount() error {
if _, err := os.Stat(container.RootfsPath()); err != nil {
if os.IsNotExist(err) {
return nil
}
image, err := container.GetImage()
if err != nil {
return err
}
return Unmount(container.RootfsPath())
err = image.Unmount(container.runtime, container.RootfsPath(), container.ID)
return err
}
// ShortID returns a shorthand version of the container's id for convenience.
@@ -1477,7 +1474,7 @@ func (container *Container) Copy(resource string) (Archive, error) {
filter = []string{path.Base(basePath)}
basePath = path.Dir(basePath)
}
return TarFilter(basePath, Uncompressed, filter)
return TarFilter(basePath, Uncompressed, filter, true, nil)
}
// Returns true if the container exposes a certain port
+6 -37
View File
@@ -23,7 +23,6 @@ func TestIDFormat(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "echo hello world"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -394,7 +393,6 @@ func TestOutput(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foobar"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -417,7 +415,6 @@ func TestContainerNetwork(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"ping", "-c", "1", "127.0.0.1"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -441,7 +438,6 @@ func TestKillDifferentUser(t *testing.T) {
OpenStdin: true,
User: "daemon",
},
"",
)
if err != nil {
t.Fatal(err)
@@ -496,7 +492,7 @@ func TestCreateVolume(t *testing.T) {
if err != nil {
t.Fatal(err)
}
c, _, err := runtime.Create(config, "")
c, _, err := runtime.Create(config)
if err != nil {
t.Fatal(err)
}
@@ -515,7 +511,6 @@ func TestKill(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"sleep", "2"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -559,7 +554,7 @@ func TestExitCode(t *testing.T) {
trueContainer, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/true", ""},
}, "")
})
if err != nil {
t.Fatal(err)
}
@@ -574,7 +569,7 @@ func TestExitCode(t *testing.T) {
falseContainer, _, err := runtime.Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/false", ""},
}, "")
})
if err != nil {
t.Fatal(err)
}
@@ -594,7 +589,6 @@ func TestRestart(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foobar"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -627,7 +621,6 @@ func TestRestartStdin(t *testing.T) {
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -704,7 +697,6 @@ func TestUser(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -725,7 +717,6 @@ func TestUser(t *testing.T) {
User: "root",
},
"",
)
if err != nil {
t.Fatal(err)
@@ -746,7 +737,6 @@ func TestUser(t *testing.T) {
User: "0",
},
"",
)
if err != nil || container.State.ExitCode != 0 {
t.Fatal(err)
@@ -767,7 +757,6 @@ func TestUser(t *testing.T) {
User: "1",
},
"",
)
if err != nil {
t.Fatal(err)
@@ -790,7 +779,6 @@ func TestUser(t *testing.T) {
User: "daemon",
},
"",
)
if err != nil {
t.Fatal(err)
@@ -811,7 +799,6 @@ func TestUser(t *testing.T) {
User: "unknownuser",
},
"",
)
if err != nil {
t.Fatal(err)
@@ -831,7 +818,6 @@ func TestMultipleContainers(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"sleep", "2"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -842,7 +828,6 @@ func TestMultipleContainers(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"sleep", "2"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -889,7 +874,6 @@ func TestStdin(t *testing.T) {
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -935,7 +919,6 @@ func TestTty(t *testing.T) {
OpenStdin: true,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -979,7 +962,6 @@ func TestEnv(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"env"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1031,7 +1013,6 @@ func TestEntrypoint(t *testing.T) {
Entrypoint: []string{"/bin/echo"},
Cmd: []string{"-n", "foobar"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1054,7 +1035,6 @@ func TestEntrypointNoCmd(t *testing.T) {
Image: GetTestImage(runtime).ID,
Entrypoint: []string{"/bin/echo", "foobar"},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1109,12 +1089,10 @@ func TestLXCConfig(t *testing.T) {
Memory: int64(mem),
CpuShares: int64(cpu),
},
"",
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
container.generateLXCConfig(nil)
grepFile(t, container.lxcConfigPath(), "lxc.utsname = foobar")
grepFile(t, container.lxcConfigPath(),
@@ -1132,7 +1110,6 @@ func TestCustomLxcConfig(t *testing.T) {
Hostname: "foobar",
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1162,7 +1139,6 @@ func BenchmarkRunSequencial(b *testing.B) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foo"},
},
"",
)
if err != nil {
b.Fatal(err)
@@ -1195,7 +1171,6 @@ func BenchmarkRunParallel(b *testing.B) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"echo", "-n", "foo"},
},
"",
)
if err != nil {
complete <- err
@@ -1348,7 +1323,6 @@ func TestVolumesFromReadonlyMount(t *testing.T) {
Cmd: []string{"/bin/echo", "-n", "foobar"},
Volumes: map[string]struct{}{"/test": {}},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1368,7 +1342,6 @@ func TestVolumesFromReadonlyMount(t *testing.T) {
Cmd: []string{"/bin/echo", "-n", "foobar"},
VolumesFrom: container.ID,
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1404,7 +1377,6 @@ func TestRestartWithVolumes(t *testing.T) {
Cmd: []string{"echo", "-n", "foobar"},
Volumes: map[string]struct{}{"/test": {}},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1448,7 +1420,6 @@ func TestVolumesFromWithVolumes(t *testing.T) {
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"},
Volumes: map[string]struct{}{"/test": {}},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1478,7 +1449,6 @@ func TestVolumesFromWithVolumes(t *testing.T) {
VolumesFrom: container.ID,
Volumes: map[string]struct{}{"/test": {}},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1513,7 +1483,7 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) {
if err != nil {
t.Fatal(err)
}
c, _, err := runtime.Create(config, "")
c, _, err := runtime.Create(config)
if err != nil {
t.Fatal(err)
}
@@ -1584,7 +1554,6 @@ func TestMultipleVolumesFrom(t *testing.T) {
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"},
Volumes: map[string]struct{}{"/test": {}},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1613,7 +1582,6 @@ func TestMultipleVolumesFrom(t *testing.T) {
Cmd: []string{"sh", "-c", "echo -n bar > /other/foo"},
Volumes: map[string]struct{}{"/other": {}},
},
"",
)
if err != nil {
t.Fatal(err)
@@ -1634,7 +1602,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/echo", "-n", "foobar"},
VolumesFrom: strings.Join([]string{container.ID, container2.ID}, ","),
}, "")
})
if err != nil {
t.Fatal(err)
@@ -1645,6 +1613,7 @@ func TestMultipleVolumesFrom(t *testing.T) {
t.Fatal(err)
}
t.Log(container3.Volumes)
if container3.Volumes["/test"] != container.Volumes["/test"] {
t.Fail()
}
+1
View File
@@ -0,0 +1 @@
*.pyc
+78
View File
@@ -0,0 +1,78 @@
# docker-brew
docker-brew is a command-line tool used to build the docker standard library.
## Install instructions
1. Install python if it isn't already available on your OS of choice
1. Install the easy_install tool (`sudo apt-get install python-setuptools`
for Debian)
1. Install the python package manager, `pip` (`easy_install pip`)
1. Run the following command: `sudo pip install -r requirements.txt`
1. You should now be able to use the `docker-brew` script as such.
## Basics
./docker-brew -h
Display usage and help.
./docker-brew
Default build from the default repo/branch. Images will be created under the
`library/` namespace. Does not perform a remote push.
./docker-brew -n mycorp.com -b stable --push git://github.com/mycorp/docker
Will fetch the library definition files in the `stable` branch of the
`git://github.com/mycorp/docker` repository and create images under the
`mycorp.com` namespace (e.g. `mycorp.com/ubuntu`). Created images will then
be pushed to the official docker repository (pending: support for private
repositories)
## Library definition files
The library definition files are plain text files found in the `library/`
subfolder of the docker repository.
### File names
The name of a definition file will determine the name of the image(s) it
creates. For example, the `library/ubuntu` file will create images in the
`<namespace>/ubuntu` repository. If multiple instructions are present in
a single file, all images are expected to be created under a different tag.
### Instruction format
Each line represents a build instruction.
There are different formats that `docker-brew` is able to parse.
<git-url>
git://github.com/dotcloud/hipache
https://github.com/dotcloud/docker.git
The simplest format. `docker-brew` will fetch data from the provided git
repository from the `HEAD`of its `master` branch. Generated image will be
tagged as `latest`. Use of this format is discouraged because there is no
way to ensure stability.
<docker-tag> <git-url>
bleeding-edge git://github.com/dotcloud/docker
unstable https://github.com/dotcloud/docker-redis.git
A more advanced format. `docker-brew` will fetch data from the provided git
repository from the `HEAD`of its `master` branch. Generated image will be
tagged as `<docker-tag>`. Recommended if we always want to provide a snapshot
of the latest development. Again, no way to ensure stability.
<docker-tag> <git-url> T:<git-tag>
2.4.0 git://github.com/dotcloud/docker-redis T:2.4.0
<docker-tag> <git-url> B:<git-branch>
zfs git://github.com/dotcloud/docker B:zfs-support
<docker-tag> <git-url> C:<git-commit-id>
2.2.0 https://github.com/dotcloud/docker-redis.git C:a4bf8923ee4ec566d3ddc212
The most complete format. `docker-brew` will fetch data from the provided git
repository from the provided reference (if it's a branch, brew will fetch its
`HEAD`). Generated image will be tagged as `<docker-tag>`. Recommended whenever
possible.
+1
View File
@@ -0,0 +1 @@
from brew import build_library, DEFAULT_REPOSITORY, DEFAULT_BRANCH
+185
View File
@@ -0,0 +1,185 @@
import os
import logging
from shutil import rmtree
import docker
import git
DEFAULT_REPOSITORY = 'git://github.com/dotcloud/docker'
DEFAULT_BRANCH = 'master'
logger = logging.getLogger(__name__)
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
level='INFO')
client = docker.Client()
processed = {}
processed_folders = []
def build_library(repository=None, branch=None, namespace=None, push=False,
debug=False, prefill=True, registry=None):
dst_folder = None
summary = Summary()
if repository is None:
repository = DEFAULT_REPOSITORY
if branch is None:
branch = DEFAULT_BRANCH
if debug:
logger.setLevel('DEBUG')
if not (repository.startswith('https://') or repository.startswith('git://')):
logger.info('Repository provided assumed to be a local path')
dst_folder = repository
try:
client.version()
except Exception as e:
logger.error('Could not reach the docker daemon. Please make sure it '
'is running.')
logger.warning('Also make sure you have access to the docker UNIX '
'socket (use sudo)')
return
#FIXME: set destination folder and only pull latest changes instead of
# cloning the whole repo everytime
if not dst_folder:
logger.info('Cloning docker repo from {0}, branch: {1}'.format(
repository, branch))
try:
rep, dst_folder = git.clone_branch(repository, branch)
except Exception as e:
logger.exception(e)
logger.error('Source repository could not be fetched. Check '
'that the address is correct and the branch exists.')
return
try:
dirlist = os.listdir(os.path.join(dst_folder, 'library'))
except OSError as e:
logger.error('The path provided ({0}) could not be found or didn\'t'
'contain a library/ folder.'.format(dst_folder))
return
for buildfile in dirlist:
if buildfile == 'MAINTAINERS':
continue
f = open(os.path.join(dst_folder, 'library', buildfile))
linecnt = 0
for line in f:
linecnt = linecnt + 1
logger.debug('{0} ---> {1}'.format(buildfile, line))
args = line.split()
try:
if len(args) > 3:
raise RuntimeError('Incorrect line format, '
'please refer to the docs')
url = None
ref = 'refs/heads/master'
tag = None
if len(args) == 1: # Just a URL, simple mode
url = args[0]
elif len(args) == 2 or len(args) == 3: # docker-tag url
url = args[1]
tag = args[0]
if len(args) == 3: # docker-tag url B:branch or T:tag
ref = None
if args[2].startswith('B:'):
ref = 'refs/heads/' + args[2][2:]
elif args[2].startswith('T:'):
ref = 'refs/tags/' + args[2][2:]
elif args[2].startswith('C:'):
ref = args[2][2:]
else:
raise RuntimeError('Incorrect line format, '
'please refer to the docs')
if prefill:
logger.debug('Pulling {0} from official repository (cache '
'fill)'.format(buildfile))
client.pull(buildfile)
img = build_repo(url, ref, buildfile, tag, namespace, push,
registry)
summary.add_success(buildfile, (linecnt, line), img)
processed['{0}@{1}'.format(url, ref)] = img
except Exception as e:
logger.exception(e)
summary.add_exception(buildfile, (linecnt, line), e)
f.close()
if dst_folder != repository:
rmtree(dst_folder, True)
for d in processed_folders:
rmtree(d, True)
summary.print_summary(logger)
def build_repo(repository, ref, docker_repo, docker_tag, namespace, push, registry):
docker_repo = '{0}/{1}'.format(namespace or 'library', docker_repo)
img_id = None
dst_folder = None
if '{0}@{1}'.format(repository, ref) not in processed.keys():
logger.info('Cloning {0} (ref: {1})'.format(repository, ref))
if repository not in processed:
rep, dst_folder = git.clone(repository, ref)
processed[repository] = rep
processed_folders.append(dst_folder)
else:
dst_folder = git.checkout(processed[repository], ref)
if not 'Dockerfile' in os.listdir(dst_folder):
raise RuntimeError('Dockerfile not found in cloned repository')
logger.info('Building using dockerfile...')
img_id, logs = client.build(path=dst_folder, quiet=True)
else:
img_id = processed['{0}@{1}'.format(repository, ref)]
logger.info('Committing to {0}:{1}'.format(docker_repo,
docker_tag or 'latest'))
client.tag(img_id, docker_repo, docker_tag)
if push:
logger.info('Pushing result to registry {0}'.format(
registry or "default"))
if registry is not None:
docker_repo = '{0}/{1}'.format(registry, docker_repo)
logger.info('Also tagging {0}'.format(docker_repo))
client.tag(img_id, docker_repo, docker_tag)
client.push(docker_repo)
return img_id
class Summary(object):
def __init__(self):
self._summary = {}
self._has_exc = False
def _add_data(self, image, linestr, data):
if image not in self._summary:
self._summary[image] = { linestr: data }
else:
self._summary[image][linestr] = data
def add_exception(self, image, line, exc):
lineno, linestr = line
self._add_data(image, linestr, { 'line': lineno, 'exc': str(exc) })
self._has_exc = True
def add_success(self, image, line, img_id):
lineno, linestr = line
self._add_data(image, linestr, { 'line': lineno, 'id': img_id })
def print_summary(self, logger=None):
linesep = ''.center(61, '-') + '\n'
s = 'BREW BUILD SUMMARY\n' + linesep
success = 'OVERALL SUCCESS: {}\n'.format(not self._has_exc)
details = linesep
for image, lines in self._summary.iteritems():
details = details + '{}\n{}'.format(image, linesep)
for linestr, data in lines.iteritems():
details = details + '{0:2} | {1} | {2:50}\n'.format(
data['line'],
'KO' if 'exc' in data else 'OK',
data['exc'] if 'exc' in data else data['id']
)
details = details + linesep
if logger:
logger.info(s + success + details)
else:
print s, success, details
+63
View File
@@ -0,0 +1,63 @@
import tempfile
import logging
from dulwich import index
from dulwich.client import get_transport_and_path
from dulwich.repo import Repo
logger = logging.getLogger(__name__)
def clone_branch(repo_url, branch="master", folder=None):
return clone(repo_url, 'refs/heads/' + branch, folder)
def clone_tag(repo_url, tag, folder=None):
return clone(repo_url, 'refs/tags/' + tag, folder)
def checkout(rep, ref=None):
is_commit = False
if ref is None:
ref = 'refs/heads/master'
elif not ref.startswith('refs/'):
is_commit = True
if is_commit:
rep['HEAD'] = rep.commit(ref)
else:
rep['HEAD'] = rep.refs[ref]
indexfile = rep.index_path()
tree = rep["HEAD"].tree
index.build_index_from_tree(rep.path, indexfile, rep.object_store, tree)
return rep.path
def clone(repo_url, ref=None, folder=None):
is_commit = False
if ref is None:
ref = 'refs/heads/master'
elif not ref.startswith('refs/'):
is_commit = True
logger.debug("clone repo_url={0}, ref={1}".format(repo_url, ref))
if folder is None:
folder = tempfile.mkdtemp()
logger.debug("folder = {0}".format(folder))
rep = Repo.init(folder)
client, relative_path = get_transport_and_path(repo_url)
logger.debug("client={0}".format(client))
remote_refs = client.fetch(relative_path, rep)
for k, v in remote_refs.iteritems():
try:
rep.refs.add_if_new(k, v)
except:
pass
if is_commit:
rep['HEAD'] = rep.commit(ref)
else:
rep['HEAD'] = remote_refs[ref]
indexfile = rep.index_path()
tree = rep["HEAD"].tree
index.build_index_from_tree(rep.path, indexfile, rep.object_store, tree)
logger.debug("done")
return rep, folder
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env python
import argparse
import sys
try:
import brew
except ImportError as e:
print str(e)
print 'Please install the required dependencies first'
print 'sudo pip install -r requirements.txt'
sys.exit(1)
if __name__ == '__main__':
parser = argparse.ArgumentParser('Build the docker standard library')
parser.add_argument('--push', action='store_true', default=False,
help='Push generated repositories')
parser.add_argument('--debug', default=False, action='store_true',
help='Enable debugging output')
parser.add_argument('--noprefill', default=True, action='store_false',
dest='prefill', help='Disable cache prefill')
parser.add_argument('-n', metavar='NAMESPACE', default='library',
help='Namespace used for generated repositories.'
' Default is library')
parser.add_argument('-b', metavar='BRANCH', default=brew.DEFAULT_BRANCH,
help='Branch in the repository where the library definition'
' files will be fetched. Default is ' + brew.DEFAULT_BRANCH)
parser.add_argument('repository', default=brew.DEFAULT_REPOSITORY,
nargs='?', help='git repository containing the library definition'
' files. Default is ' + brew.DEFAULT_REPOSITORY)
parser.add_argument('--reg', default=None, help='Registry address to'
' push build results to. Also sets push to true.')
args = parser.parse_args()
brew.build_library(args.repository, args.b, args.n,
args.push or args.reg is not None, args.debug, args.prefill, args.reg)
+2
View File
@@ -0,0 +1,2 @@
dulwich==0.9.0
-e git://github.com/dotcloud/docker-py.git#egg=docker-py
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env python
import os
from setuptools import setup
ROOT_DIR = os.path.dirname(__file__)
SOURCE_DIR = os.path.join(ROOT_DIR)
test_requirements = []
setup(
name="docker-brew",
version='0.0.1',
description="-",
packages=['dockerbrew'],
install_requires=['dulwich', 'docker'] + test_requirements,
zip_safe=False,
classifiers=['Development Status :: 3 - Alpha',
'Environment :: Other Environment',
'Intended Audience :: Developers',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Utilities'],
)
-27
View File
@@ -1,27 +0,0 @@
#
# This Dockerfile will create an image that allows to generate upstart and
# systemd scripts (more to come)
#
# docker-version 0.6.2
#
FROM ubuntu:12.10
MAINTAINER Guillaume J. Charmes <guillaume@dotcloud.com>
RUN apt-get update && apt-get install -y wget git mercurial
# Install Go
RUN wget --no-check-certificate https://go.googlecode.com/files/go1.1.2.linux-amd64.tar.gz -O go-1.1.2.tar.gz
RUN tar -xzvf go-1.1.2.tar.gz && mv /go /goroot
RUN mkdir /go
ENV GOROOT /goroot
ENV GOPATH /go
ENV PATH $GOROOT/bin:$PATH
RUN go get github.com/dotcloud/docker && cd /go/src/github.com/dotcloud/docker && git checkout v0.6.3
ADD manager.go /manager/
RUN cd /manager && go build -o /usr/bin/manager
ENTRYPOINT ["/usr/bin/manager"]
-4
View File
@@ -1,4 +0,0 @@
FROM busybox
MAINTAINER Guillaume J. Charmes <guillaume@dotcloud.com>
ADD manager /usr/bin/
ENTRYPOINT ["/usr/bin/manager"]
-130
View File
@@ -1,130 +0,0 @@
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"github.com/dotcloud/docker"
"os"
"strings"
"text/template"
)
var templates = map[string]string{
"upstart": `description "{{.description}}"
author "{{.author}}"
start on filesystem and started lxc-net and started docker
stop on runlevel [!2345]
respawn
exec /home/vagrant/goroot/bin/docker start -a {{.container_id}}
`,
"systemd": `[Unit]
Description={{.description}}
Author={{.author}}
After=docker.service
[Service]
Restart=always
ExecStart=/usr/bin/docker start -a {{.container_id}}
ExecStop=/usr/bin/docker stop -t 2 {{.container_id}}
[Install]
WantedBy=local.target
`,
}
func main() {
// Parse command line for custom options
kind := flag.String("t", "upstart", "Type of manager requested")
author := flag.String("a", "<none>", "Author of the image")
description := flag.String("d", "<none>", "Description of the image")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "\nUsage: manager <container id>\n\n")
flag.PrintDefaults()
}
flag.Parse()
// We require at least the container ID
if flag.NArg() != 1 {
println(flag.NArg())
flag.Usage()
return
}
// Check that the requested process manager is supported
if _, exists := templates[*kind]; !exists {
panic("Unkown script template")
}
// Load the requested template
tpl, err := template.New("processManager").Parse(templates[*kind])
if err != nil {
panic(err)
}
// Create stdout/stderr buffers
bufOut := bytes.NewBuffer(nil)
bufErr := bytes.NewBuffer(nil)
// Instanciate the Docker CLI
cli := docker.NewDockerCli(nil, bufOut, bufErr, "unix", "/var/run/docker.sock")
// Retrieve the container info
if err := cli.CmdInspect(flag.Arg(0)); err != nil {
// As of docker v0.6.3, CmdInspect always returns nil
panic(err)
}
// If there is nothing in the error buffer, then the Docker daemon is there and the container has been found
if bufErr.Len() == 0 {
// Unmarshall the resulting container data
c := []*docker.Container{{}}
if err := json.Unmarshal(bufOut.Bytes(), &c); err != nil {
panic(err)
}
// Reset the buffers
bufOut.Reset()
bufErr.Reset()
// Retrieve the info of the linked image
if err := cli.CmdInspect(c[0].Image); err != nil {
panic(err)
}
// If there is nothing in the error buffer, then the image has been found.
if bufErr.Len() == 0 {
// Unmarshall the resulting image data
img := []*docker.Image{{}}
if err := json.Unmarshal(bufOut.Bytes(), &img); err != nil {
panic(err)
}
// If no author has been set, use the one from the image
if *author == "<none>" && img[0].Author != "" {
*author = strings.Replace(img[0].Author, "\"", "", -1)
}
// If no description has been set, use the comment from the image
if *description == "<none>" && img[0].Comment != "" {
*description = strings.Replace(img[0].Comment, "\"", "", -1)
}
}
}
/// Old version: Wrtie the resulting script to file
// f, err := os.OpenFile(kind, os.O_CREATE|os.O_WRONLY, 0755)
// if err != nil {
// panic(err)
// }
// defer f.Close()
// Create a map with needed data
data := map[string]string{
"author": *author,
"description": *description,
"container_id": flag.Arg(0),
}
// Process the template and output it on Stdout
if err := tpl.Execute(os.Stdout, data); err != nil {
panic(err)
}
}
-53
View File
@@ -1,53 +0,0 @@
#!/bin/sh
set -e
usage() {
echo >&2 "usage: $0 [-a author] [-d description] container [manager]"
echo >&2 " ie: $0 -a 'John Smith' 4ec9612a37cd systemd"
echo >&2 " ie: $0 -d 'Super Cool System' 4ec9612a37cd # defaults to upstart"
exit 1
}
auth='<none>'
desc='<none>'
have_auth=
have_desc=
while getopts a:d: opt; do
case "$opt" in
a)
auth="$OPTARG"
have_auth=1
;;
d)
desc="$OPTARG"
have_desc=1
;;
esac
done
shift $(($OPTIND - 1))
[ $# -ge 1 -a $# -le 2 ] || usage
cid="$1"
script="${2:-upstart}"
if [ ! -e "manager/$script" ]; then
echo >&2 "Error: manager type '$script' is unknown (PRs always welcome!)."
echo >&2 'The currently supported types are:'
echo >&2 " $(cd manager && echo *)"
exit 1
fi
# TODO https://github.com/dotcloud/docker/issues/734 (docker inspect formatting)
#if command -v docker > /dev/null 2>&1; then
# image="$(docker inspect -f '{{.Image}}' "$cid")"
# if [ "$image" ]; then
# if [ -z "$have_auth" ]; then
# auth="$(docker inspect -f '{{.Author}}' "$image")"
# fi
# if [ -z "$have_desc" ]; then
# desc="$(docker inspect -f '{{.Comment}}' "$image")"
# fi
# fi
#fi
exec "manager/$script" "$cid" "$auth" "$desc"
-20
View File
@@ -1,20 +0,0 @@
#!/bin/sh
set -e
cid="$1"
auth="$2"
desc="$3"
cat <<-EOF
[Unit]
Description=$desc
Author=$auth
After=docker.service
[Service]
ExecStart=/usr/bin/docker start -a $cid
ExecStop=/usr/bin/docker stop -t 2 $cid
[Install]
WantedBy=local.target
EOF
-15
View File
@@ -1,15 +0,0 @@
#!/bin/sh
set -e
cid="$1"
auth="$2"
desc="$3"
cat <<-EOF
description "$(echo "$desc" | sed 's/"/\\"/g')"
author "$(echo "$auth" | sed 's/"/\\"/g')"
start on filesystem and started lxc-net and started docker
stop on runlevel [!2345]
respawn
exec /usr/bin/docker start -a "$cid"
EOF
-13
View File
@@ -1,13 +0,0 @@
# /etc/conf.d/docker: config file for /etc/init.d/docker
# where the docker daemon output gets piped
#DOCKER_LOGFILE="/var/log/docker.log"
# where docker's pid get stored
#DOCKER_PIDFILE="/run/docker.pid"
# where the docker daemon itself is run from
#DOCKER_BINARY="/usr/bin/docker"
# any other random options you want to pass to docker
DOCKER_OPTS=""
-31
View File
@@ -1,31 +0,0 @@
#!/sbin/runscript
# Copyright 1999-2013 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header: $
DOCKER_LOGFILE=${DOCKER_LOGFILE:-/var/log/${SVCNAME}.log}
DOCKER_PIDFILE=${DOCKER_PIDFILE:-/run/${SVCNAME}.pid}
DOCKER_BINARY=${DOCKER_BINARY:-/usr/bin/docker}
DOCKER_OPTS=${DOCKER_OPTS:-}
start() {
checkpath -f -m 0644 -o root:docker "$DOCKER_LOGFILE"
ebegin "Starting docker daemon"
start-stop-daemon --start --background \
--exec "$DOCKER_BINARY" \
--pidfile "$DOCKER_PIDFILE" \
--stdout "$DOCKER_LOGFILE" \
--stderr "$DOCKER_LOGFILE" \
-- -d -p "$DOCKER_PIDFILE" \
$DOCKER_OPTS
eend $?
}
stop() {
ebegin "Stopping docker daemon"
start-stop-daemon --stop \
--exec "$DOCKER_BINARY" \
--pidfile "$DOCKER_PIDFILE"
eend $?
}
-13
View File
@@ -1,13 +0,0 @@
[Unit]
Description=Easily create lightweight, portable, self-sufficient containers from any application!
Documentation=http://docs.docker.io
Requires=network.target
After=multi-user.target
[Service]
Type=simple
ExecStartPre=/bin/mount --make-rprivate /
ExecStart=/usr/bin/docker -d
[Install]
WantedBy=multi-user.target
-85
View File
@@ -1,85 +0,0 @@
#!/bin/sh
### BEGIN INIT INFO
# Provides: docker
# Required-Start: $syslog $remote_fs
# Required-Stop: $syslog $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Linux container runtime
# Description: Linux container runtime
### END INIT INFO
DOCKER=/usr/bin/docker
DOCKER_PIDFILE=/var/run/docker.pid
DOCKER_OPTS=
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
# Check lxc-docker is present
[ -x $DOCKER ] || (log_failure_msg "docker not present"; exit 1)
# Get lsb functions
. /lib/lsb/init-functions
if [ -f /etc/default/lxc ]; then
. /etc/default/lxc
fi
if [ "$1" = start ] && which initctl >/dev/null && initctl version | grep -q upstart; then
exit 1
fi
check_root_id ()
{
if [ "$(id -u)" != "0" ]; then
log_failure_msg "Docker must be run as root"; exit 1
fi
}
case "$1" in
start)
check_root_id || exit 1
log_begin_msg "Starting Docker"
mount | grep cgroup >/dev/null || mount -t cgroup none /sys/fs/cgroup 2>/dev/null
start-stop-daemon --start --background $NO_CLOSE \
--exec "$DOCKER" \
--pidfile "$DOCKER_PIDFILE" \
-- -d -p "$DOCKER_PIDFILE" \
$DOCKER_OPTS
log_end_msg $?
;;
stop)
check_root_id || exit 1
log_begin_msg "Stopping Docker"
start-stop-daemon --stop \
--pidfile "$DOCKER_PIDFILE"
log_end_msg $?
;;
restart)
check_root_id || exit 1
docker_pid=`cat "$DOCKER_PIDFILE" 2>/dev/null`
[ -n "$docker_pid" ] \
&& ps -p $docker_pid > /dev/null 2>&1 \
&& $0 stop
$0 start
;;
force-reload)
check_root_id || exit 1
$0 restart
;;
status)
status_of_proc -p "$DOCKER_PIDFILE" "$DOCKER" docker
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
exit 0
-10
View File
@@ -1,10 +0,0 @@
description "Docker daemon"
start on filesystem and started lxc-net
stop on runlevel [!2345]
respawn
script
/usr/bin/docker -d
end script
+1 -1
View File
@@ -62,6 +62,6 @@ mknod -m 666 ${DEV}/full c 1 7
mknod -m 600 ${DEV}/initctl p
mknod -m 666 ${DEV}/ptmx c 5 2
tar --numeric-owner -C $ROOTFS -c . | docker import - archlinux
tar -C $ROOTFS -c . | docker import - archlinux
docker run -i -t archlinux echo Success.
rm -rf $ROOTFS
+1 -1
View File
@@ -35,5 +35,5 @@ do
cp -a /dev/$X dev
done
tar --numeric-owner -cf- . | docker import - busybox
tar -cf- . | docker import - busybox
docker run -i -u root busybox /bin/echo Success.
+83
View File
@@ -0,0 +1,83 @@
#!/bin/bash
set -e
# these should match the names found at http://www.debian.org/releases/
stableSuite='wheezy'
testingSuite='jessie'
unstableSuite='sid'
variant='minbase'
include='iproute,iputils-ping'
repo="$1"
suite="$2"
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
if [ ! "$repo" ] || [ ! "$suite" ]; then
echo >&2 "usage: $0 repo suite [mirror]"
echo >&2
echo >&2 " ie: $0 tianon/debian squeeze"
echo >&2 " $0 tianon/debian squeeze http://ftp.uk.debian.org/debian/"
echo >&2
echo >&2 " ie: $0 tianon/ubuntu precise"
echo >&2 " $0 tianon/ubuntu precise http://mirrors.melbourne.co.uk/ubuntu/"
echo >&2
exit 1
fi
target="/tmp/docker-rootfs-debian-$suite-$$-$RANDOM"
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
returnTo="$(pwd -P)"
set -x
# bootstrap
mkdir -p "$target"
sudo debootstrap --verbose --variant="$variant" --include="$include" "$suite" "$target" "$mirror"
cd "$target"
# prevent init scripts from running during install/update
# policy-rc.d (for most scripts)
echo $'#!/bin/sh\nexit 101' | sudo tee usr/sbin/policy-rc.d > /dev/null
sudo chmod +x usr/sbin/policy-rc.d
# initctl (for some pesky upstart scripts)
sudo chroot . dpkg-divert --local --rename --add /sbin/initctl
sudo ln -sf /bin/true sbin/initctl
# see https://github.com/dotcloud/docker/issues/446#issuecomment-16953173
# shrink the image, since apt makes us fat (wheezy: ~157.5MB vs ~120MB)
sudo chroot . apt-get clean
# while we're at it, apt is unnecessarily slow inside containers
# this forces dpkg not to call sync() after package extraction and speeds up install
# the benefit is huge on spinning disks, and the penalty is nonexistent on SSD or decent server virtualization
echo 'force-unsafe-io' | sudo tee etc/dpkg/dpkg.cfg.d/02apt-speedup > /dev/null
# we want to effectively run "apt-get clean" after every install to keep images small
echo 'DPkg::Post-Invoke {"/bin/rm -f /var/cache/apt/archives/*.deb || true";};' | sudo tee etc/apt/apt.conf.d/no-cache > /dev/null
# helpful undo lines for each the above tweaks (for lack of a better home to keep track of them):
# rm /usr/sbin/policy-rc.d
# rm /sbin/initctl; dpkg-divert --rename --remove /sbin/initctl
# rm /etc/dpkg/dpkg.cfg.d/02apt-speedup
# rm /etc/apt/apt.conf.d/no-cache
# create the image (and tag $repo:$suite)
sudo tar -c . | docker import - $repo $suite
# test the image
docker run -i -t $repo:$suite echo success
if [ "$suite" = "$stableSuite" -o "$suite" = 'stable' ]; then
# tag latest
docker tag $repo:$suite $repo latest
# tag the specific debian release version
ver=$(docker run $repo:$suite cat /etc/debian_version)
docker tag $repo:$suite $repo $ver
fi
# cleanup
cd "$returnTo"
sudo rm -rf "$target"
-233
View File
@@ -1,233 +0,0 @@
#!/bin/bash
set -e
variant='minbase'
include='iproute,iputils-ping'
arch='amd64' # intentionally undocumented for now
skipDetection=
strictDebootstrap=
justTar=
usage() {
echo >&2
echo >&2 "usage: $0 [options] repo suite [mirror]"
echo >&2
echo >&2 'options: (not recommended)'
echo >&2 " -p set an http_proxy for debootstrap"
echo >&2 " -v $variant # change default debootstrap variant"
echo >&2 " -i $include # change default package includes"
echo >&2 " -d # strict debootstrap (do not apply any docker-specific tweaks)"
echo >&2 " -s # skip version detection and tagging (ie, precise also tagged as 12.04)"
echo >&2 " # note that this will also skip adding universe and/or security/updates to sources.list"
echo >&2 " -t # just create a tarball, especially for dockerbrew (uses repo as tarball name)"
echo >&2
echo >&2 " ie: $0 username/debian squeeze"
echo >&2 " $0 username/debian squeeze http://ftp.uk.debian.org/debian/"
echo >&2
echo >&2 " ie: $0 username/ubuntu precise"
echo >&2 " $0 username/ubuntu precise http://mirrors.melbourne.co.uk/ubuntu/"
echo >&2
echo >&2 " ie: $0 -t precise.tar.bz2 precise"
echo >&2 " $0 -t wheezy.tgz wheezy"
echo >&2 " $0 -t wheezy-uk.tar.xz wheezy http://ftp.uk.debian.org/debian/"
echo >&2
}
# these should match the names found at http://www.debian.org/releases/
debianStable=wheezy
debianUnstable=sid
# this should match the name found at http://releases.ubuntu.com/
ubuntuLatestLTS=precise
while getopts v:i:a:p:dst name; do
case "$name" in
p)
http_proxy="$OPTARG"
;;
v)
variant="$OPTARG"
;;
i)
include="$OPTARG"
;;
a)
arch="$OPTARG"
;;
d)
strictDebootstrap=1
;;
s)
skipDetection=1
;;
t)
justTar=1
;;
?)
usage
exit 0
;;
esac
done
shift $(($OPTIND - 1))
repo="$1"
suite="$2"
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
if [ ! "$repo" ] || [ ! "$suite" ]; then
usage
exit 1
fi
# some rudimentary detection for whether we need to "sudo" our docker calls
docker=''
if docker version > /dev/null 2>&1; then
docker='docker'
elif sudo docker version > /dev/null 2>&1; then
docker='sudo docker'
elif command -v docker > /dev/null 2>&1; then
docker='docker'
else
echo >&2 "warning: either docker isn't installed, or your current user cannot run it;"
echo >&2 " this script is not likely to work as expected"
sleep 3
docker='docker' # give us a command-not-found later
fi
# make sure we have an absolute path to our final tarball so we can still reference it properly after we change directory
if [ "$justTar" ]; then
if [ ! -d "$(dirname "$repo")" ]; then
echo >&2 "error: $(dirname "$repo") does not exist"
exit 1
fi
repo="$(cd "$(dirname "$repo")" && pwd -P)/$(basename "$repo")"
fi
# will be filled in later, if [ -z "$skipDetection" ]
lsbDist=''
target="/tmp/docker-rootfs-debootstrap-$suite-$$-$RANDOM"
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
returnTo="$(pwd -P)"
set -x
# bootstrap
mkdir -p "$target"
sudo http_proxy=$http_proxy debootstrap --verbose --variant="$variant" --include="$include" --arch="$arch" "$suite" "$target" "$mirror"
cd "$target"
if [ -z "$strictDebootstrap" ]; then
# prevent init scripts from running during install/update
# policy-rc.d (for most scripts)
echo $'#!/bin/sh\nexit 101' | sudo tee usr/sbin/policy-rc.d > /dev/null
sudo chmod +x usr/sbin/policy-rc.d
# initctl (for some pesky upstart scripts)
sudo chroot . dpkg-divert --local --rename --add /sbin/initctl
sudo ln -sf /bin/true sbin/initctl
# see https://github.com/dotcloud/docker/issues/446#issuecomment-16953173
# shrink the image, since apt makes us fat (wheezy: ~157.5MB vs ~120MB)
sudo chroot . apt-get clean
# while we're at it, apt is unnecessarily slow inside containers
# this forces dpkg not to call sync() after package extraction and speeds up install
# the benefit is huge on spinning disks, and the penalty is nonexistent on SSD or decent server virtualization
echo 'force-unsafe-io' | sudo tee etc/dpkg/dpkg.cfg.d/02apt-speedup > /dev/null
# we want to effectively run "apt-get clean" after every install to keep images small
echo 'DPkg::Post-Invoke {"/bin/rm -f /var/cache/apt/archives/*.deb || true";};' | sudo tee etc/apt/apt.conf.d/no-cache > /dev/null
# helpful undo lines for each the above tweaks (for lack of a better home to keep track of them):
# rm /usr/sbin/policy-rc.d
# rm /sbin/initctl; dpkg-divert --rename --remove /sbin/initctl
# rm /etc/dpkg/dpkg.cfg.d/02apt-speedup
# rm /etc/apt/apt.conf.d/no-cache
if [ -z "$skipDetection" ]; then
# see also rudimentary platform detection in hack/install.sh
lsbDist=''
if [ -r etc/lsb-release ]; then
lsbDist="$(. etc/lsb-release && echo "$DISTRIB_ID")"
fi
if [ -z "$lsbDist" ] && [ -r etc/debian_version ]; then
lsbDist='Debian'
fi
case "$lsbDist" in
Debian)
# add the updates and security repositories
if [ "$suite" != "$debianUnstable" -a "$suite" != 'unstable' ]; then
# ${suite}-updates only applies to non-unstable
sudo sed -i "p; s/ $suite main$/ ${suite}-updates main/" etc/apt/sources.list
# same for security updates
echo "deb http://security.debian.org/ $suite/updates main" | sudo tee -a etc/apt/sources.list > /dev/null
fi
;;
Ubuntu)
# add the universe, updates, and security repositories
sudo sed -i "
s/ $suite main$/ $suite main universe/; p;
s/ $suite main/ ${suite}-updates main/; p;
s/ $suite-updates main/ ${suite}-security main/
" etc/apt/sources.list
;;
esac
fi
fi
if [ "$justTar" ]; then
# create the tarball file so it has the right permissions (ie, not root)
touch "$repo"
# fill the tarball
sudo tar --numeric-owner -caf "$repo" .
else
# create the image (and tag $repo:$suite)
sudo tar --numeric-owner -c . | $docker import - $repo $suite
# test the image
$docker run -i -t $repo:$suite echo success
if [ -z "$skipDetection" ]; then
case "$lsbDist" in
Debian)
if [ "$suite" = "$debianStable" -o "$suite" = 'stable' ] && [ -r etc/debian_version ]; then
# tag latest
$docker tag $repo:$suite $repo latest
if [ -r etc/debian_version ]; then
# tag the specific debian release version (which is only reasonable to tag on debian stable)
ver=$(cat etc/debian_version)
$docker tag $repo:$suite $repo $ver
fi
fi
;;
Ubuntu)
if [ "$suite" = "$ubuntuLatestLTS" ]; then
# tag latest
$docker tag $repo:$suite $repo latest
fi
if [ -r etc/lsb-release ]; then
lsbRelease="$(. etc/lsb-release && echo "$DISTRIB_RELEASE")"
if [ "$lsbRelease" ]; then
# tag specific Ubuntu version number, if available (12.04, etc.)
$docker tag $repo:$suite $repo $lsbRelease
fi
fi
;;
esac
fi
fi
# cleanup
cd "$returnTo"
sudo rm -rf "$target"
+1 -1
View File
@@ -44,6 +44,6 @@ do
done
chmod 0755 $ROOTFS # See #486
tar --numeric-owner -cf- . | docker import - docker-ut
tar -cf- . | docker import - docker-ut
docker run -i -u root docker-ut /bin/echo Success.
rm -rf $ROOTFS
-2
View File
@@ -20,5 +20,3 @@ highlight link dockerfileString String
syntax match dockerfileComment "\v^\s*#.*$"
highlight link dockerfileComment Comment
set commentstring=#\ %s
+850
View File
@@ -0,0 +1,850 @@
package devmapper
import (
"encoding/json"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"sync"
"syscall"
"time"
)
var (
DefaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024
DefaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024
DefaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024
)
type DevInfo struct {
Hash string `json:"-"`
DeviceId int `json:"device_id"`
Size uint64 `json:"size"`
TransactionId uint64 `json:"transaction_id"`
Initialized bool `json:"initialized"`
devices *DeviceSet `json:"-"`
}
type MetaData struct {
Devices map[string]*DevInfo `json:devices`
}
type DeviceSet struct {
MetaData
sync.Mutex
initialized bool
root string
devicePrefix string
TransactionId uint64
NewTransactionId uint64
nextFreeDevice int
activeMounts map[string]int
}
type DiskUsage struct {
Used uint64
Total uint64
}
type Status struct {
PoolName string
DataLoopback string
MetadataLoopback string
Data DiskUsage
Metadata DiskUsage
}
func getDevName(name string) string {
return "/dev/mapper/" + name
}
func (info *DevInfo) Name() string {
hash := info.Hash
if hash == "" {
hash = "base"
}
return fmt.Sprintf("%s-%s", info.devices.devicePrefix, hash)
}
func (info *DevInfo) DevName() string {
return getDevName(info.Name())
}
func (devices *DeviceSet) loopbackDir() string {
return path.Join(devices.root, "devicemapper")
}
func (devices *DeviceSet) jsonFile() string {
return path.Join(devices.loopbackDir(), "json")
}
func (devices *DeviceSet) getPoolName() string {
return devices.devicePrefix + "-pool"
}
func (devices *DeviceSet) getPoolDevName() string {
return getDevName(devices.getPoolName())
}
func (devices *DeviceSet) hasImage(name string) bool {
dirname := devices.loopbackDir()
filename := path.Join(dirname, name)
_, err := os.Stat(filename)
return err == nil
}
// ensureImage creates a sparse file of <size> bytes at the path
// <root>/devicemapper/<name>.
// If the file already exists, it does nothing.
// Either way it returns the full path.
func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) {
dirname := devices.loopbackDir()
filename := path.Join(dirname, name)
if err := os.MkdirAll(dirname, 0700); err != nil && !os.IsExist(err) {
return "", err
}
if _, err := os.Stat(filename); err != nil {
if !os.IsNotExist(err) {
return "", err
}
utils.Debugf("Creating loopback file %s for device-manage use", filename)
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
return "", err
}
if err = file.Truncate(size); err != nil {
return "", err
}
}
return filename, nil
}
func (devices *DeviceSet) allocateDeviceId() int {
// TODO: Add smarter reuse of deleted devices
id := devices.nextFreeDevice
devices.nextFreeDevice = devices.nextFreeDevice + 1
return id
}
func (devices *DeviceSet) allocateTransactionId() uint64 {
devices.NewTransactionId = devices.NewTransactionId + 1
return devices.NewTransactionId
}
func (devices *DeviceSet) saveMetadata() error {
jsonData, err := json.Marshal(devices.MetaData)
if err != nil {
return fmt.Errorf("Error encoding metaadata to json: %s", err)
}
tmpFile, err := ioutil.TempFile(filepath.Dir(devices.jsonFile()), ".json")
if err != nil {
return fmt.Errorf("Error creating metadata file: %s", err)
}
n, err := tmpFile.Write(jsonData)
if err != nil {
return fmt.Errorf("Error writing metadata to %s: %s", tmpFile.Name(), err)
}
if n < len(jsonData) {
return io.ErrShortWrite
}
if err := tmpFile.Sync(); err != nil {
return fmt.Errorf("Error syncing metadata file %s: %s", tmpFile.Name(), err)
}
if err := tmpFile.Close(); err != nil {
return fmt.Errorf("Error closing metadata file %s: %s", tmpFile.Name(), err)
}
if err := os.Rename(tmpFile.Name(), devices.jsonFile()); err != nil {
return fmt.Errorf("Error committing metadata file", err)
}
if devices.NewTransactionId != devices.TransactionId {
if err = setTransactionId(devices.getPoolDevName(), devices.TransactionId, devices.NewTransactionId); err != nil {
return fmt.Errorf("Error setting devmapper transition ID: %s", err)
}
devices.TransactionId = devices.NewTransactionId
}
return nil
}
func (devices *DeviceSet) registerDevice(id int, hash string, size uint64) (*DevInfo, error) {
utils.Debugf("registerDevice(%v, %v)", id, hash)
info := &DevInfo{
Hash: hash,
DeviceId: id,
Size: size,
TransactionId: devices.allocateTransactionId(),
Initialized: false,
devices: devices,
}
devices.Devices[hash] = info
if err := devices.saveMetadata(); err != nil {
// Try to remove unused device
delete(devices.Devices, hash)
return nil, err
}
return info, nil
}
func (devices *DeviceSet) activateDeviceIfNeeded(hash string) error {
utils.Debugf("activateDeviceIfNeeded(%v)", hash)
info := devices.Devices[hash]
if info == nil {
return fmt.Errorf("Unknown device %s", hash)
}
if devinfo, _ := getInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 {
return nil
}
return activateDevice(devices.getPoolDevName(), info.Name(), info.DeviceId, info.Size)
}
func (devices *DeviceSet) createFilesystem(info *DevInfo) error {
devname := info.DevName()
err := exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run()
if err != nil {
err = exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname).Run()
}
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) loadMetaData() error {
utils.Debugf("loadMetadata()")
defer utils.Debugf("loadMetadata END")
_, _, _, params, err := getStatus(devices.getPoolName())
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
if _, err := fmt.Sscanf(params, "%d", &devices.TransactionId); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
devices.NewTransactionId = devices.TransactionId
jsonData, err := ioutil.ReadFile(devices.jsonFile())
if err != nil && !os.IsNotExist(err) {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
devices.MetaData.Devices = make(map[string]*DevInfo)
if jsonData != nil {
if err := json.Unmarshal(jsonData, &devices.MetaData); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
for hash, d := range devices.Devices {
d.Hash = hash
d.devices = devices
if d.DeviceId >= devices.nextFreeDevice {
devices.nextFreeDevice = d.DeviceId + 1
}
// If the transaction id is larger than the actual one we lost the device due to some crash
if d.TransactionId > devices.TransactionId {
utils.Debugf("Removing lost device %s with id %d", hash, d.TransactionId)
delete(devices.Devices, hash)
}
}
return nil
}
func (devices *DeviceSet) setupBaseImage() error {
oldInfo := devices.Devices[""]
if oldInfo != nil && oldInfo.Initialized {
return nil
}
if oldInfo != nil && !oldInfo.Initialized {
utils.Debugf("Removing uninitialized base image")
if err := devices.removeDevice(""); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
utils.Debugf("Initializing base device-manager snapshot")
id := devices.allocateDeviceId()
// Create initial device
if err := createDevice(devices.getPoolDevName(), id); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
utils.Debugf("Registering base device (id %v) with FS size %v", id, DefaultBaseFsSize)
info, err := devices.registerDevice(id, "", DefaultBaseFsSize)
if err != nil {
_ = deleteDevice(devices.getPoolDevName(), id)
utils.Debugf("\n--->Err: %s\n", err)
return err
}
utils.Debugf("Creating filesystem on base device-manager snapshot")
if err = devices.activateDeviceIfNeeded(""); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
if err := devices.createFilesystem(info); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
info.Initialized = true
if err = devices.saveMetadata(); err != nil {
info.Initialized = false
utils.Debugf("\n--->Err: %s\n", err)
return err
}
return nil
}
func setCloseOnExec(name string) {
fileInfos, _ := ioutil.ReadDir("/proc/self/fd")
if fileInfos != nil {
for _, i := range fileInfos {
link, _ := os.Readlink(filepath.Join("/proc/self/fd", i.Name()))
if link == name {
fd, err := strconv.Atoi(i.Name())
if err == nil {
syscall.CloseOnExec(fd)
}
}
}
}
}
func (devices *DeviceSet) log(level int, file string, line int, dmError int, message string) {
if level >= 7 {
return // Ignore _LOG_DEBUG
}
utils.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
}
func major(device uint64) uint64 {
return (device >> 8) & 0xfff
}
func minor(device uint64) uint64 {
return (device & 0xff) | ((device >> 12) & 0xfff00)
}
func (devices *DeviceSet) initDevmapper() error {
logInit(devices)
// Make sure the sparse images exist in <root>/devicemapper/data and
// <root>/devicemapper/metadata
createdLoopback := !devices.hasImage("data") || !devices.hasImage("metadata")
data, err := devices.ensureImage("data", DefaultDataLoopbackSize)
if err != nil {
utils.Debugf("Error device ensureImage (data): %s\n", err)
return err
}
metadata, err := devices.ensureImage("metadata", DefaultMetaDataLoopbackSize)
if err != nil {
utils.Debugf("Error device ensureImage (metadata): %s\n", err)
return err
}
// Set the device prefix from the device id and inode of the docker root dir
st, err := os.Stat(devices.root)
if err != nil {
return fmt.Errorf("Error looking up dir %s: %s", devices.root, err)
}
sysSt := st.Sys().(*syscall.Stat_t)
// "reg-" stands for "regular file".
// In the future we might use "dev-" for "device file", etc.
// docker-maj,min[-inode] stands for:
// - Managed by docker
// - The target of this device is at major <maj> and minor <min>
// - If <inode> is defined, use that file inside the device as a loopback image. Otherwise use the device itself.
devices.devicePrefix = fmt.Sprintf("docker-%d:%d-%d", major(sysSt.Dev), minor(sysSt.Dev), sysSt.Ino)
utils.Debugf("Generated prefix: %s", devices.devicePrefix)
// Check for the existence of the device <prefix>-pool
utils.Debugf("Checking for existence of the pool '%s'", devices.getPoolName())
info, err := getInfo(devices.getPoolName())
if info == nil {
utils.Debugf("Error device getInfo: %s", err)
return err
}
// It seems libdevmapper opens this without O_CLOEXEC, and go exec will not close files
// that are not Close-on-exec, and lxc-start will die if it inherits any unexpected files,
// so we add this badhack to make sure it closes itself
setCloseOnExec("/dev/mapper/control")
// If the pool doesn't exist, create it
if info.Exists == 0 {
utils.Debugf("Pool doesn't exist. Creating it.")
dataFile, err := AttachLoopDevice(data)
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
defer dataFile.Close()
metadataFile, err := AttachLoopDevice(metadata)
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
defer metadataFile.Close()
if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
// If we didn't just create the data or metadata image, we need to
// load the metadata from the existing file.
if !createdLoopback {
if err = devices.loadMetaData(); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
// Setup the base image
if err := devices.setupBaseImage(); err != nil {
utils.Debugf("Error device setupBaseImage: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) AddDevice(hash, baseHash string) error {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
utils.Debugf("Error init: %s\n", err)
return err
}
if devices.Devices[hash] != nil {
return fmt.Errorf("hash %s already exists", hash)
}
baseInfo := devices.Devices[baseHash]
if baseInfo == nil {
return fmt.Errorf("Error adding device for '%s': can't find device for parent '%s'", hash, baseHash)
}
deviceId := devices.allocateDeviceId()
if err := devices.createSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil {
utils.Debugf("Error creating snap device: %s\n", err)
return err
}
if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size); err != nil {
deleteDevice(devices.getPoolDevName(), deviceId)
utils.Debugf("Error registering device: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) removeDevice(hash string) error {
info := devices.Devices[hash]
if info == nil {
return fmt.Errorf("hash %s doesn't exists", hash)
}
devinfo, _ := getInfo(info.Name())
if devinfo != nil && devinfo.Exists != 0 {
if err := removeDevice(info.Name()); err != nil {
utils.Debugf("Error removing device: %s\n", err)
return err
}
}
if info.Initialized {
info.Initialized = false
if err := devices.saveMetadata(); err != nil {
utils.Debugf("Error saving meta data: %s\n", err)
return err
}
}
if err := deleteDevice(devices.getPoolDevName(), info.DeviceId); err != nil {
utils.Debugf("Error deleting device: %s\n", err)
return err
}
devices.allocateTransactionId()
delete(devices.Devices, info.Hash)
if err := devices.saveMetadata(); err != nil {
devices.Devices[info.Hash] = info
utils.Debugf("Error saving meta data: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) RemoveDevice(hash string) error {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
return devices.removeDevice(hash)
}
func (devices *DeviceSet) deactivateDevice(hash string) error {
utils.Debugf("[devmapper] deactivateDevice(%s)", hash)
defer utils.Debugf("[devmapper] deactivateDevice END")
var devname string
// FIXME: shouldn't we just register the pool into devices?
devname, err := devices.byHash(hash)
if err != nil {
return err
}
devinfo, err := getInfo(devname)
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
if devinfo.Exists != 0 {
if err := removeDevice(devname); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
if err := devices.waitRemove(hash); err != nil {
return err
}
}
return nil
}
// waitRemove blocks until either:
// a) the device registered at <device_set_prefix>-<hash> is removed,
// or b) the 1 second timeout expires.
func (devices *DeviceSet) waitRemove(hash string) error {
utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, hash)
defer utils.Debugf("[deviceset %s] waitRemove END", devices.devicePrefix, hash)
devname, err := devices.byHash(hash)
if err != nil {
return err
}
i := 0
for ; i < 1000; i += 1 {
devinfo, err := getInfo(devname)
if err != nil {
// If there is an error we assume the device doesn't exist.
// The error might actually be something else, but we can't differentiate.
return nil
}
utils.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists)
if devinfo.Exists == 0 {
break
}
time.Sleep(1 * time.Millisecond)
}
if i == 1000 {
return fmt.Errorf("Timeout while waiting for device %s to be removed", devname)
}
return nil
}
// waitClose blocks until either:
// a) the device registered at <device_set_prefix>-<hash> is closed,
// or b) the 1 second timeout expires.
func (devices *DeviceSet) waitClose(hash string) error {
devname, err := devices.byHash(hash)
if err != nil {
return err
}
i := 0
for ; i < 1000; i += 1 {
devinfo, err := getInfo(devname)
if err != nil {
return err
}
utils.Debugf("Waiting for unmount of %s: opencount=%d", devname, devinfo.OpenCount)
if devinfo.OpenCount == 0 {
break
}
time.Sleep(1 * time.Millisecond)
}
if i == 1000 {
return fmt.Errorf("Timeout while waiting for device %s to close", devname)
}
return nil
}
// byHash is a hack to allow looking up the deviceset's pool by the hash "pool".
// FIXME: it seems probably cleaner to register the pool in devices.Devices,
// but I am afraid of arcane implications deep in the devicemapper code,
// so this will do.
func (devices *DeviceSet) byHash(hash string) (devname string, err error) {
if hash == "pool" {
return devices.getPoolDevName(), nil
}
info := devices.Devices[hash]
if info == nil {
return "", fmt.Errorf("hash %s doesn't exists", hash)
}
return info.Name(), nil
}
func (devices *DeviceSet) Shutdown() error {
utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix)
defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix)
devices.Lock()
utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root)
defer devices.Unlock()
if !devices.initialized {
return nil
}
for path, count := range devices.activeMounts {
for i := count; i > 0; i-- {
if err := syscall.Unmount(path, 0); err != nil {
utils.Debugf("Shutdown unmounting %s, error: %s\n", path, err)
}
}
delete(devices.activeMounts, path)
}
for _, d := range devices.Devices {
if err := devices.waitClose(d.Hash); err != nil {
utils.Errorf("Warning: error waiting for device %s to unmount: %s\n", d.Hash, err)
}
if err := devices.deactivateDevice(d.Hash); err != nil {
utils.Debugf("Shutdown deactivate %s , error: %s\n", d.Hash, err)
}
}
pool := devices.getPoolDevName()
if devinfo, err := getInfo(pool); err == nil && devinfo.Exists != 0 {
if err := devices.deactivateDevice("pool"); err != nil {
utils.Debugf("Shutdown deactivate %s , error: %s\n", pool, err)
}
}
return nil
}
func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
return fmt.Errorf("Error initializing devmapper: %s", err)
}
if err := devices.activateDeviceIfNeeded(hash); err != nil {
return fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err)
}
info := devices.Devices[hash]
var flags uintptr = syscall.MS_MGC_VAL
if readOnly {
flags = flags | syscall.MS_RDONLY
}
err := syscall.Mount(info.DevName(), path, "ext4", flags, "discard")
if err != nil && err == syscall.EINVAL {
err = syscall.Mount(info.DevName(), path, "ext4", flags, "")
}
if err != nil {
return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)
}
count := devices.activeMounts[path]
devices.activeMounts[path] = count + 1
return nil
}
func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) error {
utils.Debugf("[devmapper] UnmountDevice(hash=%s path=%s)", hash, path)
defer utils.Debugf("[devmapper] UnmountDevice END")
devices.Lock()
defer devices.Unlock()
utils.Debugf("[devmapper] Unmount(%s)", path)
if err := syscall.Unmount(path, 0); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
utils.Debugf("[devmapper] Unmount done")
// Wait for the unmount to be effective,
// by watching the value of Info.OpenCount for the device
if err := devices.waitClose(hash); err != nil {
return err
}
if count := devices.activeMounts[path]; count > 1 {
devices.activeMounts[path] = count - 1
} else {
delete(devices.activeMounts, path)
}
if deactivate {
devices.deactivateDevice(hash)
}
return nil
}
func (devices *DeviceSet) HasDevice(hash string) bool {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
return false
}
return devices.Devices[hash] != nil
}
func (devices *DeviceSet) HasInitializedDevice(hash string) bool {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
return false
}
info := devices.Devices[hash]
return info != nil && info.Initialized
}
func (devices *DeviceSet) HasActivatedDevice(hash string) bool {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
return false
}
info := devices.Devices[hash]
if info == nil {
return false
}
devinfo, _ := getInfo(info.Name())
return devinfo != nil && devinfo.Exists != 0
}
func (devices *DeviceSet) SetInitialized(hash string) error {
devices.Lock()
defer devices.Unlock()
if err := devices.ensureInit(); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
info := devices.Devices[hash]
if info == nil {
return fmt.Errorf("Unknown device %s", hash)
}
info.Initialized = true
if err := devices.saveMetadata(); err != nil {
info.Initialized = false
utils.Debugf("\n--->Err: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) Status() *Status {
devices.Lock()
defer devices.Unlock()
status := &Status{}
if err := devices.ensureInit(); err != nil {
return status
}
status.PoolName = devices.getPoolName()
status.DataLoopback = path.Join(devices.loopbackDir(), "data")
status.MetadataLoopback = path.Join(devices.loopbackDir(), "metadata")
_, totalSizeInSectors, _, params, err := getStatus(devices.getPoolName())
if err == nil {
var transactionId, dataUsed, dataTotal, metadataUsed, metadataTotal uint64
if _, err := fmt.Sscanf(params, "%d %d/%d %d/%d", &transactionId, &metadataUsed, &metadataTotal, &dataUsed, &dataTotal); err == nil {
// Convert from blocks to bytes
blockSizeInSectors := totalSizeInSectors / dataTotal
status.Data.Used = dataUsed * blockSizeInSectors * 512
status.Data.Total = dataTotal * blockSizeInSectors * 512
// metadata blocks are always 4k
status.Metadata.Used = metadataUsed * 4096
status.Metadata.Total = metadataTotal * 4096
}
}
return status
}
func (devices *DeviceSet) ensureInit() error {
if !devices.initialized {
devices.initialized = true
if err := devices.initDevmapper(); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
return nil
}
func NewDeviceSet(root string) *DeviceSet {
SetDevDir("/dev")
return &DeviceSet{
initialized: false,
root: root,
MetaData: MetaData{Devices: make(map[string]*DevInfo)},
activeMounts: make(map[string]int),
}
}
+709
View File
@@ -0,0 +1,709 @@
package devmapper
/*
#cgo LDFLAGS: -L. -ldevmapper
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libdevmapper.h>
#include <linux/loop.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <errno.h>
#ifndef LOOP_CTL_GET_FREE
#define LOOP_CTL_GET_FREE 0x4C82
#endif
// FIXME: this could easily be rewritten in go
char* attach_loop_device(const char *filename, int *loop_fd_out)
{
struct loop_info64 loopinfo = {0};
struct stat st;
char buf[64];
int i, loop_fd, fd, start_index;
char* loopname;
*loop_fd_out = -1;
start_index = 0;
fd = open("/dev/loop-control", O_RDONLY);
if (fd >= 0) {
start_index = ioctl(fd, LOOP_CTL_GET_FREE);
close(fd);
if (start_index < 0)
start_index = 0;
}
fd = open(filename, O_RDWR);
if (fd < 0) {
perror("open");
return NULL;
}
loop_fd = -1;
for (i = start_index ; loop_fd < 0 ; i++ ) {
if (sprintf(buf, "/dev/loop%d", i) < 0) {
close(fd);
return NULL;
}
if (stat(buf, &st)) {
if (!S_ISBLK(st.st_mode)) {
fprintf(stderr, "[error] Loopback device %s is not a block device.\n", buf);
} else if (errno == ENOENT) {
fprintf(stderr, "[error] There are no more loopback device available.\n");
} else {
fprintf(stderr, "[error] Unkown error trying to stat the loopback device %s (errno: %d).\n", buf, errno);
}
close(fd);
return NULL;
}
loop_fd = open(buf, O_RDWR);
if (loop_fd < 0 && errno == ENOENT) {
fprintf(stderr, "[error] The loopback device %s does not exists.\n", buf);
close(fd);
return NULL;
} else if (loop_fd < 0) {
fprintf(stderr, "[error] Unkown error openning the loopback device %s. (errno: %d)\n", buf, errno);
continue;
}
if (ioctl(loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) {
int errsv = errno;
close(loop_fd);
loop_fd = -1;
if (errsv != EBUSY) {
close(fd);
fprintf(stderr, "cannot set up loopback device %s: %s", buf, strerror(errsv));
return NULL;
}
continue;
}
close(fd);
strncpy((char*)loopinfo.lo_file_name, buf, LO_NAME_SIZE);
loopinfo.lo_offset = 0;
loopinfo.lo_flags = LO_FLAGS_AUTOCLEAR;
if (ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo) < 0) {
perror("ioctl LOOP_SET_STATUS64");
if (ioctl(loop_fd, LOOP_CLR_FD, 0) < 0) {
perror("ioctl LOOP_CLR_FD");
}
close(loop_fd);
fprintf (stderr, "cannot set up loopback device info");
return (NULL);
}
loopname = strdup(buf);
if (loopname == NULL) {
close(loop_fd);
return (NULL);
}
*loop_fd_out = loop_fd;
return (loopname);
}
return (NULL);
}
static int64_t get_block_size(int fd)
{
uint64_t size;
if (ioctl(fd, BLKGETSIZE64, &size) == -1)
return -1;
return ((int64_t)size);
}
extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str);
static void
log_cb(int level, const char *file, int line,
int dm_errno_or_class, const char *f, ...)
{
char buffer[256];
va_list ap;
va_start(ap, f);
vsnprintf(buffer, 256, f, ap);
va_end(ap);
DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer);
}
static void
log_with_errno_init ()
{
dm_log_with_errno_init(log_cb);
}
*/
import "C"
import (
"errors"
"fmt"
"github.com/dotcloud/docker/utils"
"os"
"runtime"
"syscall"
"unsafe"
)
type DevmapperLogger interface {
log(level int, file string, line int, dmError int, message string)
}
const (
DeviceCreate TaskType = iota
DeviceReload
DeviceRemove
DeviceRemoveAll
DeviceSuspend
DeviceResume
DeviceInfo
DeviceDeps
DeviceRename
DeviceVersion
DeviceStatus
DeviceTable
DeviceWaitevent
DeviceList
DeviceClear
DeviceMknodes
DeviceListVersions
DeviceTargetMsg
DeviceSetGeometry
)
const (
AddNodeOnResume AddNodeType = iota
AddNodeOnCreate
)
var (
ErrTaskRun = errors.New("dm_task_run failed")
ErrTaskSetName = errors.New("dm_task_set_name failed")
ErrTaskSetMessage = errors.New("dm_task_set_message failed")
ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed")
ErrTaskSetRO = errors.New("dm_task_set_ro failed")
ErrTaskAddTarget = errors.New("dm_task_add_target failed")
ErrGetDriverVersion = errors.New("dm_task_get_driver_version failed")
ErrAttachLoopbackDevice = errors.New("loopback mounting failed")
ErrGetBlockSize = errors.New("Can't get block size")
ErrUdevWait = errors.New("wait on udev cookie failed")
ErrSetDevDir = errors.New("dm_set_dev_dir failed")
ErrGetLibraryVersion = errors.New("dm_get_library_version failed")
ErrCreateRemoveTask = errors.New("Can't create task of type DeviceRemove")
ErrRunRemoveDevice = errors.New("running removeDevice failed")
)
type (
Task struct {
unmanaged *C.struct_dm_task
}
Info struct {
Exists int
Suspended int
LiveTable int
InactiveTable int
OpenCount int32
EventNr uint32
Major uint32
Minor uint32
ReadOnly int
TargetCount int32
}
TaskType int
AddNodeType int
)
func (t *Task) destroy() {
if t != nil {
C.dm_task_destroy(t.unmanaged)
runtime.SetFinalizer(t, nil)
}
}
func TaskCreate(tasktype TaskType) *Task {
c_task := C.dm_task_create(C.int(tasktype))
if c_task == nil {
return nil
}
task := &Task{unmanaged: c_task}
runtime.SetFinalizer(task, (*Task).destroy)
return task
}
func (t *Task) Run() error {
if res := C.dm_task_run(t.unmanaged); res != 1 {
return ErrTaskRun
}
return nil
}
func (t *Task) SetName(name string) error {
c_name := C.CString(name)
defer free(c_name)
if res := C.dm_task_set_name(t.unmanaged, c_name); res != 1 {
return ErrTaskSetName
}
return nil
}
func (t *Task) SetMessage(message string) error {
c_message := C.CString(message)
defer free(c_message)
if res := C.dm_task_set_message(t.unmanaged, c_message); res != 1 {
return ErrTaskSetMessage
}
return nil
}
func (t *Task) SetSector(sector uint64) error {
if res := C.dm_task_set_sector(t.unmanaged, C.uint64_t(sector)); res != 1 {
return ErrTaskSetAddNode
}
return nil
}
func (t *Task) SetCookie(cookie *uint32, flags uint16) error {
c_cookie := C.uint32_t(*cookie)
if res := C.dm_task_set_cookie(t.unmanaged, &c_cookie, C.uint16_t(flags)); res != 1 {
return ErrTaskSetAddNode
}
*cookie = uint32(c_cookie)
return nil
}
func (t *Task) SetAddNode(add_node AddNodeType) error {
if res := C.dm_task_set_add_node(t.unmanaged, C.dm_add_node_t(add_node)); res != 1 {
return ErrTaskSetAddNode
}
return nil
}
func (t *Task) SetRo() error {
if res := C.dm_task_set_ro(t.unmanaged); res != 1 {
return ErrTaskSetRO
}
return nil
}
func (t *Task) AddTarget(start uint64, size uint64, ttype string, params string) error {
c_ttype := C.CString(ttype)
defer free(c_ttype)
c_params := C.CString(params)
defer free(c_params)
if res := C.dm_task_add_target(t.unmanaged, C.uint64_t(start), C.uint64_t(size), c_ttype, c_params); res != 1 {
return ErrTaskAddTarget
}
return nil
}
func (t *Task) GetDriverVersion() (string, error) {
buffer := C.CString(string(make([]byte, 128)))
defer free(buffer)
if res := C.dm_task_get_driver_version(t.unmanaged, buffer, 128); res != 1 {
return "", ErrGetDriverVersion
}
return C.GoString(buffer), nil
}
func (t *Task) GetInfo() (*Info, error) {
c_info := C.struct_dm_info{}
if res := C.dm_task_get_info(t.unmanaged, &c_info); res != 1 {
return nil, ErrGetDriverVersion
}
return &Info{
Exists: int(c_info.exists),
Suspended: int(c_info.suspended),
LiveTable: int(c_info.live_table),
InactiveTable: int(c_info.inactive_table),
OpenCount: int32(c_info.open_count),
EventNr: uint32(c_info.event_nr),
Major: uint32(c_info.major),
Minor: uint32(c_info.minor),
ReadOnly: int(c_info.read_only),
TargetCount: int32(c_info.target_count),
}, nil
}
func (t *Task) GetNextTarget(next uintptr) (uintptr, uint64, uint64, string, string) {
var (
c_start, c_length C.uint64_t
c_target_type, c_params *C.char
)
nextp := C.dm_get_next_target(t.unmanaged, unsafe.Pointer(next), &c_start, &c_length, &c_target_type, &c_params)
return uintptr(nextp), uint64(c_start), uint64(c_length), C.GoString(c_target_type), C.GoString(c_params)
}
func AttachLoopDevice(filename string) (*os.File, error) {
c_filename := C.CString(filename)
defer free(c_filename)
var fd C.int
res := C.attach_loop_device(c_filename, &fd)
if res == nil {
if os.Getenv("DEBUG") != "" {
C.perror(C.CString(fmt.Sprintf("[debug] Error attach_loop_device(%s, %d)", filename, int(fd))))
}
return nil, ErrAttachLoopbackDevice
}
defer free(res)
return os.NewFile(uintptr(fd), C.GoString(res)), nil
}
func getBlockSize(fd uintptr) int {
var size uint64
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); err != 0 {
utils.Debugf("Error ioctl: %s", err)
return -1
}
return int(size)
}
func GetBlockDeviceSize(file *os.File) (uint64, error) {
if size := C.get_block_size(C.int(file.Fd())); size == -1 {
return 0, ErrGetBlockSize
} else {
return uint64(size), nil
}
}
func UdevWait(cookie uint32) error {
if res := C.dm_udev_wait(C.uint32_t(cookie)); res != 1 {
utils.Debugf("Failed to wait on udev cookie %d", cookie)
return ErrUdevWait
}
return nil
}
func LogInitVerbose(level int) {
C.dm_log_init_verbose(C.int(level))
}
var dmLogger DevmapperLogger = nil
func logInit(logger DevmapperLogger) {
dmLogger = logger
C.log_with_errno_init()
}
func SetDevDir(dir string) error {
c_dir := C.CString(dir)
defer free(c_dir)
if res := C.dm_set_dev_dir(c_dir); res != 1 {
utils.Debugf("Error dm_set_dev_dir")
return ErrSetDevDir
}
return nil
}
func GetLibraryVersion() (string, error) {
buffer := C.CString(string(make([]byte, 128)))
defer free(buffer)
if res := C.dm_get_library_version(buffer, 128); res != 1 {
return "", ErrGetLibraryVersion
}
return C.GoString(buffer), nil
}
// Useful helper for cleanup
func RemoveDevice(name string) error {
task := TaskCreate(DeviceRemove)
if task == nil {
return ErrCreateRemoveTask
}
if err := task.SetName(name); err != nil {
utils.Debugf("Can't set task name %s", name)
return err
}
if err := task.Run(); err != nil {
return ErrRunRemoveDevice
}
return nil
}
func free(p *C.char) {
C.free(unsafe.Pointer(p))
}
// This is the programmatic example of "dmsetup create"
func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error {
task, err := createTask(DeviceCreate, poolName)
if task == nil {
return err
}
size, err := GetBlockDeviceSize(dataFile)
if err != nil {
return fmt.Errorf("Can't get data size")
}
params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768"
if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil {
return fmt.Errorf("Can't add target")
}
var cookie uint32 = 0
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can't set cookie")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceCreate")
}
UdevWait(cookie)
return nil
}
func createTask(t TaskType, name string) (*Task, error) {
task := TaskCreate(t)
if task == nil {
return nil, fmt.Errorf("Can't create task of type %d", int(t))
}
if err := task.SetName(name); err != nil {
return nil, fmt.Errorf("Can't set task name %s", name)
}
return task, nil
}
func getInfo(name string) (*Info, error) {
task, err := createTask(DeviceInfo, name)
if task == nil {
return nil, err
}
if err := task.Run(); err != nil {
return nil, err
}
return task.GetInfo()
}
func getStatus(name string) (uint64, uint64, string, string, error) {
task, err := createTask(DeviceStatus, name)
if task == nil {
utils.Debugf("getStatus: Error createTask: %s", err)
return 0, 0, "", "", err
}
if err := task.Run(); err != nil {
utils.Debugf("getStatus: Error Run: %s", err)
return 0, 0, "", "", err
}
devinfo, err := task.GetInfo()
if err != nil {
utils.Debugf("getStatus: Error GetInfo: %s", err)
return 0, 0, "", "", err
}
if devinfo.Exists == 0 {
utils.Debugf("getStatus: Non existing device %s", name)
return 0, 0, "", "", fmt.Errorf("Non existing device %s", name)
}
_, start, length, target_type, params := task.GetNextTarget(0)
return start, length, target_type, params, nil
}
func setTransactionId(poolName string, oldId uint64, newId uint64) error {
task, err := createTask(DeviceTargetMsg, poolName)
if task == nil {
return err
}
if err := task.SetSector(0); err != nil {
return fmt.Errorf("Can't set sector")
}
if err := task.SetMessage(fmt.Sprintf("set_transaction_id %d %d", oldId, newId)); err != nil {
return fmt.Errorf("Can't set message")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running setTransactionId")
}
return nil
}
func suspendDevice(name string) error {
task, err := createTask(DeviceSuspend, name)
if task == nil {
return err
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceSuspend")
}
return nil
}
func resumeDevice(name string) error {
task, err := createTask(DeviceResume, name)
if task == nil {
return err
}
var cookie uint32 = 0
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can't set cookie")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceSuspend")
}
UdevWait(cookie)
return nil
}
func createDevice(poolName string, deviceId int) error {
utils.Debugf("[devmapper] createDevice(poolName=%v, deviceId=%v)", poolName, deviceId)
task, err := createTask(DeviceTargetMsg, poolName)
if task == nil {
return err
}
if err := task.SetSector(0); err != nil {
return fmt.Errorf("Can't set sector")
}
if err := task.SetMessage(fmt.Sprintf("create_thin %d", deviceId)); err != nil {
return fmt.Errorf("Can't set message")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running createDevice")
}
return nil
}
func deleteDevice(poolName string, deviceId int) error {
task, err := createTask(DeviceTargetMsg, poolName)
if task == nil {
return err
}
if err := task.SetSector(0); err != nil {
return fmt.Errorf("Can't set sector")
}
if err := task.SetMessage(fmt.Sprintf("delete %d", deviceId)); err != nil {
return fmt.Errorf("Can't set message")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running deleteDevice")
}
return nil
}
func removeDevice(name string) error {
utils.Debugf("[devmapper] removeDevice START")
defer utils.Debugf("[devmapper] removeDevice END")
task, err := createTask(DeviceRemove, name)
if task == nil {
return err
}
if err = task.Run(); err != nil {
return fmt.Errorf("Error running removeDevice")
}
return nil
}
func activateDevice(poolName string, name string, deviceId int, size uint64) error {
task, err := createTask(DeviceCreate, name)
if task == nil {
return err
}
params := fmt.Sprintf("%s %d", poolName, deviceId)
if err := task.AddTarget(0, size/512, "thin", params); err != nil {
return fmt.Errorf("Can't add target")
}
if err := task.SetAddNode(AddNodeOnCreate); err != nil {
return fmt.Errorf("Can't add node")
}
var cookie uint32 = 0
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can't set cookie")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceCreate")
}
UdevWait(cookie)
return nil
}
func (devices *DeviceSet) createSnapDevice(poolName string, deviceId int, baseName string, baseDeviceId int) error {
devinfo, _ := getInfo(baseName)
doSuspend := devinfo != nil && devinfo.Exists != 0
if doSuspend {
if err := suspendDevice(baseName); err != nil {
return err
}
}
task, err := createTask(DeviceTargetMsg, poolName)
if task == nil {
if doSuspend {
resumeDevice(baseName)
}
return err
}
if err := task.SetSector(0); err != nil {
if doSuspend {
resumeDevice(baseName)
}
return fmt.Errorf("Can't set sector")
}
if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", deviceId, baseDeviceId)); err != nil {
if doSuspend {
resumeDevice(baseName)
}
return fmt.Errorf("Can't set message")
}
if err := task.Run(); err != nil {
if doSuspend {
resumeDevice(baseName)
}
return fmt.Errorf("Error running DeviceCreate")
}
if doSuspend {
if err := resumeDevice(baseName); err != nil {
return err
}
}
return nil
}
+13
View File
@@ -0,0 +1,13 @@
package devmapper
import "C"
// Due to the way cgo works this has to be in a separate file, as devmapper.go has
// definitions in the cgo block, which is incompatible with using "//export"
//export DevmapperLogCallback
func DevmapperLogCallback(level C.int, file *C.char, line C.int, dm_errno_or_class C.int, message *C.char) {
if dmLogger != nil {
dmLogger.log(int(level), C.GoString(file), int(line), int(dm_errno_or_class), C.GoString(message))
}
}
@@ -0,0 +1,62 @@
package main
import (
"fmt"
"github.com/dotcloud/docker/devmapper"
"os"
)
func usage() {
fmt.Printf("Usage: %s [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0])
os.Exit(1)
}
func main() {
devices := devmapper.NewDeviceSet("/var/lib/docker")
if len(os.Args) < 2 {
usage()
}
cmd := os.Args[1]
if cmd == "snap" {
if len(os.Args) < 4 {
usage()
}
err := devices.AddDevice(os.Args[2], os.Args[3])
if err != nil {
fmt.Println("Can't create snap device: ", err)
os.Exit(1)
}
} else if cmd == "remove" {
if len(os.Args) < 3 {
usage()
}
err := devices.RemoveDevice(os.Args[2])
if err != nil {
fmt.Println("Can't remove device: ", err)
os.Exit(1)
}
} else if cmd == "mount" {
if len(os.Args) < 4 {
usage()
}
err := devices.MountDevice(os.Args[2], os.Args[3])
if err != nil {
fmt.Println("Can't create snap device: ", err)
os.Exit(1)
}
} else {
fmt.Printf("Unknown command %s\n", cmd)
if len(os.Args) < 4 {
usage()
}
os.Exit(1)
}
return
}
+14 -21
View File
@@ -41,7 +41,7 @@ func main() {
flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
flEnableIptables := flag.Bool("iptables", true, "Disable iptables within docker")
flDefaultIp := flag.String("ip", "0.0.0.0", "Default ip address to use when binding a containers ports")
flInterContainerComm := flag.Bool("icc", true, "Enable inter-container communication")
flInterContainerComm := flag.Bool("enable-container-comm", false, "Enable inter-container communication")
flag.Parse()
@@ -53,12 +53,7 @@ func main() {
flHosts = flHosts[1:] //trick to display a nice default value in the usage
}
for i, flHost := range flHosts {
host, err := utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
if err == nil {
flHosts[i] = host
} else {
log.Fatal(err)
}
flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
}
bridge := docker.DefaultNetworkBridge
@@ -96,10 +91,12 @@ func main() {
}
if err := daemon(config); err != nil {
log.Fatal(err)
os.Exit(-1)
}
} else {
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 {
@@ -107,6 +104,7 @@ func main() {
os.Exit(sterr.Status)
}
log.Fatal(err)
os.Exit(-1)
}
}
}
@@ -148,22 +146,18 @@ func daemon(config *docker.DaemonConfig) error {
}
defer removePidFile(config.Pidfile)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM)
go func() {
sig := <-c
log.Printf("Received signal '%v', exiting\n", sig)
removePidFile(config.Pidfile)
os.Exit(0)
}()
server, err := docker.NewServer(config)
if err != nil {
return err
}
defer server.Close()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM))
go func() {
sig := <-c
log.Printf("Received signal '%v', exiting\n", sig)
server.Close()
removePidFile(config.Pidfile)
os.Exit(0)
}()
chErrors := make(chan error, len(config.ProtoAddresses))
for _, protoAddr := range config.ProtoAddresses {
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
@@ -174,9 +168,8 @@ func daemon(config *docker.DaemonConfig) error {
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
}
} else {
server.Close()
removePidFile(config.Pidfile)
log.Fatal("Invalid protocol format.")
os.Exit(-1)
}
go func() {
chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true)
+1 -6
View File
@@ -1,16 +1,11 @@
from ubuntu:12.04
maintainer Nick Stinemates
#
# docker build -t docker:docs . && docker run -p 8000:8000 docker:docs
#
run apt-get update
run apt-get install -y python-setuptools make
run easy_install pip
#from docs/requirements.txt, but here to increase cacheability
run pip install Sphinx==1.1.3
run pip install sphinxcontrib-httpdomain==1.1.8
add . /docs
run pip install -r /docs/requirements.txt
run cd /docs; make docs
expose 8000
+4 -5
View File
@@ -39,7 +39,8 @@ Getting Started
To edit and test the docs, you'll need to install the Sphinx tool and
its dependencies. There are two main ways to install this tool:
###Native Installation
Native Installation
...................
* Install sphinx: `pip install sphinx`
* Mac OS X: `[sudo] pip-2.7 install sphinx`
@@ -47,7 +48,8 @@ its dependencies. There are two main ways to install this tool:
* Mac OS X: `[sudo] pip-2.7 install sphinxcontrib-httpdomain`
* If pip is not available you can probably install it using your favorite package manager as **python-pip**
###Alternative Installation: Docker Container
Alternative Installation: Docker Container
..........................................
If you're running ``docker`` on your development machine then you may
find it easier and cleaner to use the Dockerfile. This installs Sphinx
@@ -57,9 +59,6 @@ docs inside the container, even starting a simple HTTP server on port
build .`` and run the resulting image. This is the equivalent to
``make clean server`` since each container starts clean.
In the ``docs/`` directory, run:
```docker build -t docker:docs . && docker run -p 8000:8000 docker:docs```
Usage
-----
* Follow the contribution guidelines (``../CONTRIBUTING.md``)
+16 -21
View File
@@ -970,33 +970,28 @@ Create a new image from a container's changes
**Example request**:
.. sourcecode:: http
.. sourcecode:: http
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
Content-Type: application/json
{
"Cmd": ["cat", "/world"],
"PortSpecs":["22"]
}
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
**Example response**:
**Example response**:
.. sourcecode:: http
.. sourcecode:: http
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
{"Id":"596069db4bf5"}
{"Id":"596069db4bf5"}
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
3. Going further
+23 -28
View File
@@ -977,37 +977,32 @@ Create a new image from a container's changes
.. http:post:: /commit
Create a new image from a container's changes
Create a new image from a container's changes
**Example request**:
.. sourcecode:: http
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
Content-Type: application/json
{
"Cmd": ["cat", "/world"],
"PortSpecs":["22"]
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
**Example request**:
{"Id":"596069db4bf5"}
.. sourcecode:: http
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
{"Id":"596069db4bf5"}
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
3. Going further
+17 -22
View File
@@ -985,37 +985,32 @@ Create a new image from a container's changes
.. http:post:: /commit
Create a new image from a container's changes
Create a new image from a container's changes
**Example request**:
**Example request**:
.. sourcecode:: http
.. sourcecode:: http
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
Content-Type: application/json
{
"Cmd": ["cat", "/world"],
"PortSpecs":["22"]
}
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
**Example response**:
**Example response**:
.. sourcecode:: http
.. sourcecode:: http
HTTP/1.1 201 OK
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
{"Id":"596069db4bf5"}
{"Id":"596069db4bf5"}
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
3. Going further
+17 -22
View File
@@ -1034,37 +1034,32 @@ Create a new image from a container's changes
.. http:post:: /commit
Create a new image from a container's changes
Create a new image from a container's changes
**Example request**:
**Example request**:
.. sourcecode:: http
.. sourcecode:: http
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
Content-Type: application/json
{
"Cmd": ["cat", "/world"],
"PortSpecs":["22"]
}
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
**Example response**:
**Example response**:
.. sourcecode:: http
.. sourcecode:: http
HTTP/1.1 201 OK
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
{"Id":"596069db4bf5"}
{"Id":"596069db4bf5"}
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
Monitor Docker's events
+5 -10
View File
@@ -1084,28 +1084,23 @@ Create a new image from a container's changes
.. sourcecode:: http
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
Content-Type: application/json
{
"Cmd": ["cat", "/world"],
"PortSpecs":["22"]
}
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
{"Id":"596069db4bf5"}
{"Id":"596069db4bf5"}
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
+18 -23
View File
@@ -1050,37 +1050,32 @@ Create a new image from a container's changes
.. http:post:: /commit
Create a new image from a container's changes
Create a new image from a container's changes
**Example request**:
**Example request**:
.. sourcecode:: http
.. sourcecode:: http
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
Content-Type: application/json
{
"Cmd": ["cat", "/world"],
"PortSpecs":["22"]
}
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
**Example response**:
**Example response**:
.. sourcecode:: http
.. sourcecode:: http
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
{"Id":"596069db4bf5"}
{"Id":"596069db4bf5"}
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
:query container: source container
:query repo: repository
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
Monitor Docker's events
***********************
+6 -16
View File
@@ -13,12 +13,9 @@ Docker Remote API v1.6
1. Brief introduction
=====================
- The Remote API has replaced rcli
- The daemon listens on ``unix:///var/run/docker.sock``, but you can
:ref:`bind_docker`.
- The API tends to be REST, but for some complex commands, like
``attach`` or ``pull``, the HTTP connection is hijacked to transport
``stdout, stdin`` and ``stderr``
- The Remote API is replacing rcli
- Default port in the docker daemon is 4243
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
2. Endpoints
============
@@ -151,7 +148,6 @@ Create a container
}
:jsonparam config: the container's configuration
:query name: container name to use
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 406: impossible to attach (container not running)
@@ -446,8 +442,7 @@ Kill a container
.. sourcecode:: http
HTTP/1.1 204 OK
:query signal: Signal to send to the container (integer). When not set, SIGKILL is assumed and the call will waits for the container to exit.
:statuscode 204: no error
:statuscode 404: no such container
:statuscode 500: server error
@@ -1136,13 +1131,7 @@ Create a new image from a container's changes
.. sourcecode:: http
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
Content-Type: application/json
{
"Cmd": ["cat", "/world"],
"PortSpecs":["22"]
}
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
**Example response**:
@@ -1158,6 +1147,7 @@ Create a new image from a container's changes
:query tag: tag
:query m: commit message
:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
:statuscode 201: no error
:statuscode 404: no such container
:statuscode 500: server error
+31 -20
View File
@@ -29,9 +29,6 @@ To list available commands, either run ``docker`` with no parameters or execute
Attach to a running container.
-nostdin=false: Do not attach stdin
-sig-proxy=true: Proxify all received signal to the process (even in non-tty mode)
You can detach from the container again (and leave it running) with
``CTRL-c`` (for a quiet exit) or ``CTRL-\`` to get a stacktrace of
the Docker client when it quits.
@@ -399,10 +396,37 @@ Insert file from github
::
Usage: docker kill CONTAINER [CONTAINER...]
Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
Kill a running container
.. _cli_link:
``link``
--------
::
Usage: docker link CURRENT_NAME NEW_NAME
Link a container to a new name.
Examples:
~~~~~~~~~
.. code-block:: bash
$ docker link /59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc /redis
$ docker ls
NAME ID IMAGE
/redis 59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc crosbymichael/redis:latest
/59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc 59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc crosbymichael/redis:latest
This will create a new link for the existing name ``/59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc``
with the new name ``/redis`` so that we can new reference the same container under the new name ``/redis``.
.. _cli_login:
``login``
@@ -575,10 +599,8 @@ network communication.
-entrypoint="": Overwrite the default entrypoint set by the image
-w="": Working directory inside the container
-lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
-sig-proxy=true: Proxify all received signal to the process (even in non-tty mode)
-expose=[]: Expose a port from the container without publishing it to your host
-link="": Add link to another container (name:alias)
-name="": Assign the specified name to the container. If no name is specific docker will generate a random name
-link="": Add link to another container (containerid:alias)
Examples
--------
@@ -654,20 +676,12 @@ without publishing the port to the host system's interfaces.
.. code-block:: bash
docker run -name console -t -i ubuntu bash
This will create and run a new container with the container name
being ``console``.
.. code-block:: bash
docker run -link /redis:redis -name console ubuntu bash
docker run -link /redis:redis ubuntu bash
The ``-link`` flag will link the container named ``/redis`` into the
newly created container with the alias ``redis``. The new container
can access the network and environment of the redis container via
environment variables. The ``-name`` flag will assign the name ``console``
to the newly created container.
environment variables.
.. _cli_search:
@@ -692,9 +706,6 @@ to the newly created container.
Start a stopped container
-a=false: Attach container's stdout/stderr and forward all signals to the process
-i=false: Attach container's stdin
.. _cli_stop:
``stop``
+1 -14
View File
@@ -5,18 +5,5 @@
Contributing to Docker
======================
Want to hack on Docker? Awesome!
Want to hack on Docker? Awesome! The repository includes `all the instructions you need to get started <https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md>`_.
The repository includes `all the instructions you need to get
started <https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md>`_.
The developer environment `Dockerfile <https://github.com/dotcloud/docker/blob/master/Dockerfile>`_
specifies the tools and versions used to test and build Docker.
If you're making changes to the documentation, see the
`README.md <https://github.com/dotcloud/docker/blob/master/docs/README.md>`_.
The documentation environment `Dockerfile <https://github.com/dotcloud/docker/blob/master/docs/Dockerfile>`_
specifies the tools and versions used to build the Documentation.
Further interesting details can be found in the `Packaging hints <https://github.com/dotcloud/docker/blob/master/hack/PACKAGERS.md>`_.
+37 -37
View File
@@ -14,16 +14,12 @@ Building a redis container to link as a child of our web application.
Building the redis container
----------------------------
Lets build a redis image with the following Dockerfile.
We will use a pre-build version of redis from the index under
the name ``crosbymichael/redis``. If you are interested in the
Dockerfile that was used to build this container here it is.
.. code-block:: bash
git clone https://github.com/antirez/redis.git
cd redis
git checkout 2.6
# Save this Dockerfile to the root of the redis repository.
# Build redis from source
# Make sure you have the redis source code checked out in
# the same directory as this Dockerfile
@@ -38,6 +34,7 @@ Lets build a redis image with the following Dockerfile.
ADD . /redis
RUN (cd /redis && make)
RUN (cd /redis && make test)
RUN mkdir -p /redis-data
VOLUME ["/redis-data"]
@@ -46,9 +43,6 @@ Lets build a redis image with the following Dockerfile.
ENTRYPOINT ["/redis/src/redis-server"]
CMD ["--dir", "/redis-data"]
# docker build our new redis image from source
docker build -t redis-2.6 .
We need to ``EXPOSE`` the default port of 6379 so that our link knows what ports
to connect to our redis container on. If you do not expose any ports for the
@@ -60,39 +54,51 @@ Run the redis container
.. code-block:: bash
docker run -d -e PASSWORD=docker -name redis redis-2.6 --requirepass docker
docker run -d -e PASSWORD=docker crosbymichael/redis --requirepass docker
This will run our redis container wit the password docker
to secure our service. By specifying the ``-name`` flag on run
we will assign the name ``redis`` to this container. If we do not specify a name for
our container via the ``-name`` flag docker will automatically generate a name for us.
We can issue all the commands that you would expect; start, stop, attach, using the name for our container.
The name also allows us to link other containers into this one.
This will run our redis container using the default port of 6379 and using
as password to secure our service. Next we will link the redis container to
a new name using ``docker link``.
Linking an existing container
-----------------------------
Docker will automatically create an initial link with the container's id but
because the is long and not very user friendly we can link the container with
a new name.
.. code-block:: bash
docker link /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 /redis
Now we can reference our running redis service using the friendly name ``/redis``.
We can issue all the commands that you would expect; start, stop, attach, using the new name.
Linking redis as a child
------------------------
Next we can start a new web application that has a dependency on redis and apply a link
to connect both containers. If you noticed when running our redis server we did not use
the ``-p`` flag to publish the redis port to the host system. Redis exposed port 6379 via the Dockerfile
and this is all we need to establish a link.
to connect both containers. If you noticed when running our redis service we did not use
the ``-p`` option to publish the redis port to the host system. Redis exposed port 6379
but we did not publish the port. This allows docker to prevent all network traffic to
the redis container except when explicitly specified within a link. This is a big win
for security.
Now lets start our web application with a link into redis.
.. code-block:: bash
docker run -t -i -link redis:db -name webapp ubuntu bash
docker run -t -i -link /redis:db ubuntu bash
root@4c01db0b339c:/# env
HOSTNAME=4c01db0b339c
DB_NAME=/webapp/db
DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db
TERM=xterm
DB_PORT=tcp://172.17.0.8:6379
DB_PORT_6379_TCP=tcp://172.17.0.8:6379
DB_PORT_6379_TCP_PROTO=tcp
DB_PORT_6379_TCP_ADDR=172.17.0.8
DB_PORT_6379_TCP_PORT=6379
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
DB_ENV_PASSWORD=dockerpass
@@ -104,29 +110,23 @@ Now lets start our web application with a link into redis.
When we inspect the environment of the linked container we can see a few extra environment
variables have been added. When you specified ``-link redis:db`` you are telling docker
to link the container named ``redis`` into this new container with the alias ``db``.
variables have been added. When you specified ``-link /redis:db`` you are telling docker
to link the container named ``/redis`` into this new container with the alias ``db``.
Environment variables are prefixed with the alias so that the parent container can access
network and environment information from the containers that are linked into it.
network and environment information from the child.
.. code-block:: bash
# The name of the child container
DB_NAME=/webapp/db
DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db
# The default protocol, ip, and port of the service running in the container
DB_PORT=tcp://172.17.0.8:6379
# A specific protocol, ip, and port of various services
DB_PORT_6379_TCP=tcp://172.17.0.8:6379
DB_PORT_6379_TCP_PROTO=tcp
DB_PORT_6379_TCP_ADDR=172.17.0.8
DB_PORT_6379_TCP_PORT=6379
# Get environment variables of the container
DB_ENV_PASSWORD=dockerpass
Accessing the network information along with the environment of the child container allows
us to easily connect to the redis service on the specific ip and port and use the password
specified in the environment.
specified in the environment.
+1 -1
View File
@@ -69,7 +69,7 @@ to run LXC containers. Note that 2.6.32 has some documented issues regarding
network namespace setup and teardown; those issues are not a risk if you
run containers in a private environment, but can lead to denial-of-service
attacks if you want to run untrusted code in your containers. For more details,
see `LP#720095 <https://bugs.launchpad.net/ubuntu/+source/linux/+bug/720095>`_.
see `[LP#720095 <https://bugs.launchpad.net/ubuntu/+source/linux/+bug/720095>`_.
Kernels 2.6.38, and every version since 3.2, have been deployed successfully
to run containerized production workloads. Feature-wise, there is no huge
+2 -4
View File
@@ -78,8 +78,7 @@ Docker is available as a Debian package, which makes installation easy.
sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"
# Add the Docker repository to your apt sources list.
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
> /etc/apt/sources.list.d/docker.list"
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"
# Update your sources
sudo apt-get update
@@ -133,8 +132,7 @@ to follow them again.*
sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"
# Add the Docker repository to your apt sources list.
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main\
> /etc/apt/sources.list.d/docker.list"
sudo sh -c "echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"
# update
sudo apt-get update
+1 -1
View File
@@ -40,4 +40,4 @@ Docker Github Repo:
* `CentOS
<https://github.com/dotcloud/docker/blob/master/contrib/mkimage-centos.sh>`_
* `Debian/Ubuntu
<https://github.com/dotcloud/docker/blob/master/contrib/mkimage-debootstrap.sh>`_
<https://github.com/dotcloud/docker/blob/master/contrib/mkimage-debian.sh>`_
+1 -3
View File
@@ -67,8 +67,6 @@ you don't need to add ``sudo`` to all the client commands.
# Restart the docker daemon
sudo service docker restart
.. _bind_docker:
Bind Docker to another host/port or a Unix socket
-------------------------------------------------
@@ -144,7 +142,7 @@ Expose a service on a TCP port
.. code-block:: bash
# Expose port 4444 of this container, and tell netcat to listen on it
JOB=$(sudo docker run -d -p 4444 ubuntu:12.10 /bin/nc -l 4444)
JOB=$(sudo docker run -d -p 4444 ubuntu:12.10 /bin/nc -l -p 4444)
# Which public port is NATed to my container?
PORT=$(sudo docker port $JOB 4444)
+1 -1
View File
@@ -220,7 +220,7 @@ The copy obeys the following rules:
(``http://example.com`` will not work).
* If ``<src>`` is a directory, the entire directory is copied,
including filesystem metadata.
* If ``<src>`` is a tar archive in a recognized compression format
* 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
-59
View File
@@ -1,59 +0,0 @@
:title: Host Integration
:description: How to generate scripts for upstart, systemd, etc.
:keywords: systemd, upstart, supervisor, docker, documentation, host integration
Host Integration
================
You can use your Docker containers with process managers like ``upstart``,
``systemd`` and ``supervisor``.
Introduction
------------
If you want a process manager to manage your containers you will need to run
the docker daemon with the ``-r=false`` so that docker will not automatically
restart your containers when the host is restarted.
When you have finished setting up your image and are happy with your
running container, you may want to use a process manager to manage
it. When your run ``docker start -a`` docker will automatically attach
to the process and forward all signals so that the process manager can
detect when a container stops and correctly restart it.
Here are a few sample scripts for systemd and upstart to integrate with docker.
Sample Upstart Script
---------------------
.. code-block:: bash
description "Redis container"
author "Me"
start on filesystem and started lxc-net and started docker
stop on runlevel [!2345]
respawn
exec docker start -a 0a7e070b698b
Sample systemd Script
---------------------
.. code-block:: bash
[Unit]
Description=Redis container
Author=Me
After=docker.service
[Service]
Restart=always
ExecStart=/usr/bin/docker start -a 0a7e070b698b
ExecStop=/usr/bin/docker stop -t 2 0a7e070b698b
[Install]
WantedBy=local.target
-1
View File
@@ -18,4 +18,3 @@ Contents:
baseimages
port_redirection
puppet
host_integration
+1 -7
View File
@@ -13,12 +13,6 @@
{%- set url_root = pathto('', 1) %}
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
{%- if current_version == 'latest' %}
{% set github_tag = 'master' %}
{% else %}
{% set github_tag = current_version %}
{% endif %}
<script type="text/javascript">
// This is included here for Javascript that doesn't have access to the templates.
var doc_version = "{{ current_version }}";
@@ -113,7 +107,7 @@
{% block body %}{% endblock %}
</section>
<div class="pull-right"><a href="https://github.com/dotcloud/docker/blob/{{ github_tag }}/docs/sources/{{ pagename }}.rst" title="edit this article">Edit this article on GitHub</a></div>
<div class="pull-right"><a href="https://github.com/dotcloud/docker/blob/{{ current_version }}/docs/sources/{{ pagename }}.rst" title="edit this article">Edit this article on GitHub</a></div>
</div>
</div>
</div>
+120 -81
View File
@@ -1,8 +1,10 @@
package gograph
import (
_ "code.google.com/p/gosqlite/sqlite3"
"database/sql"
"fmt"
"os"
"path"
)
@@ -23,7 +25,7 @@ const (
`
createEdgeIndices = `
CREATE UNIQUE INDEX IF NOT EXISTS "name_parent_ix" ON "edge" (parent_id, name);
CREATE UNIQUE INDEX "name_parent_ix" ON "edge" (parent_id, name);
`
)
@@ -46,70 +48,74 @@ type WalkFunc func(fullPath string, entity *Entity) error
// Graph database for storing entities and their relationships
type Database struct {
conn *sql.DB
dbPath string
}
// Create a new graph database initialized with a root entity
func NewDatabase(conn *sql.DB, init bool) (*Database, error) {
if conn == nil {
return nil, fmt.Errorf("Database connection cannot be nil")
func NewDatabase(dbPath string) (*Database, error) {
db := &Database{dbPath}
if _, err := os.Stat(dbPath); err == nil {
return db, nil
}
db := &Database{conn}
conn, err := db.openConn()
if err != nil {
return nil, err
}
defer conn.Close()
if init {
if _, err := conn.Exec(createEntityTable); err != nil {
return nil, err
}
if _, err := conn.Exec(createEdgeTable); err != nil {
return nil, err
}
if _, err := conn.Exec(createEdgeIndices); err != nil {
return nil, err
}
if _, err := conn.Exec(createEntityTable); err != nil {
return nil, err
}
if _, err := conn.Exec(createEdgeTable); err != nil {
return nil, err
}
if _, err := conn.Exec(createEdgeIndices); err != nil {
return nil, err
}
rollback := func() {
conn.Exec("ROLLBACK")
}
rollback := func() {
conn.Exec("ROLLBACK")
}
// Create root entities
if _, err := conn.Exec("BEGIN"); err != nil {
return nil, err
}
if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
rollback()
return nil, err
}
// Create root entities
if _, err := conn.Exec("BEGIN"); err != nil {
return nil, err
}
if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("COMMIT"); err != nil {
return nil, err
}
if _, err := conn.Exec("COMMIT"); err != nil {
return nil, err
}
return db, nil
}
// Close the underlying connection to the database
func (db *Database) Close() error {
return db.conn.Close()
}
// Set the entity id for a given path
func (db *Database) Set(fullPath, id string) (*Entity, error) {
conn, err := db.openConn()
if err != nil {
return nil, err
}
defer conn.Close()
// FIXME: is rollback implicit when closing the connection?
rollback := func() {
db.conn.Exec("ROLLBACK")
conn.Exec("ROLLBACK")
}
if _, err := db.conn.Exec("BEGIN EXCLUSIVE"); err != nil {
// FIXME: use exclusive transactions to avoid race conditions
if _, err := conn.Exec("BEGIN"); err != nil {
return nil, err
}
var entityId string
if err := db.conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil {
if err := conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil {
if err == sql.ErrNoRows {
if _, err := db.conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
if _, err := conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
rollback()
return nil, err
}
@@ -121,24 +127,19 @@ func (db *Database) Set(fullPath, id string) (*Entity, error) {
e := &Entity{id}
parentPath, name := splitPath(fullPath)
if err := db.setEdge(parentPath, name, e); err != nil {
if err := db.setEdge(conn, parentPath, name, e); err != nil {
rollback()
return nil, err
}
if _, err := db.conn.Exec("COMMIT"); err != nil {
if _, err := conn.Exec("COMMIT"); err != nil {
return nil, err
}
return e, nil
}
// Return true if a name already exists in the database
func (db *Database) Exists(name string) bool {
return db.Get(name) != nil
}
func (db *Database) setEdge(parentPath, name string, e *Entity) error {
parent, err := db.get(parentPath)
func (db *Database) setEdge(conn *sql.DB, parentPath, name string, e *Entity) error {
parent, err := db.get(conn, parentPath)
if err != nil {
return err
}
@@ -146,7 +147,7 @@ func (db *Database) setEdge(parentPath, name string, e *Entity) error {
return fmt.Errorf("Cannot set self as child")
}
if _, err := db.conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
if _, err := conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
return err
}
return nil
@@ -161,14 +162,18 @@ func (db *Database) RootEntity() *Entity {
// Return the entity for a given path
func (db *Database) Get(name string) *Entity {
e, err := db.get(name)
conn, err := db.openConn()
if err != nil {
return nil
}
e, err := db.get(conn, name)
if err != nil {
return nil
}
return e
}
func (db *Database) get(name string) (*Entity, error) {
func (db *Database) get(conn *sql.DB, name string) (*Entity, error) {
e := db.RootEntity()
// We always know the root name so return it if
// it is requested
@@ -179,13 +184,10 @@ func (db *Database) get(name string) (*Entity, error) {
parts := split(name)
for i := 1; i < len(parts); i++ {
p := parts[i]
if p == "" {
continue
}
next := db.child(e, p)
next := db.child(conn, e, p)
if next == nil {
return nil, fmt.Errorf("Cannot find child for %s", name)
return nil, fmt.Errorf("Cannot find child")
}
e = next
}
@@ -197,22 +199,26 @@ func (db *Database) get(name string) (*Entity, error) {
// The key will be the full path of the entity
func (db *Database) List(name string, depth int) Entities {
out := Entities{}
e, err := db.get(name)
conn, err := db.openConn()
if err != nil {
return out
}
for c := range db.children(e, name, depth) {
defer conn.Close()
for c := range db.children(conn, name, depth) {
out[c.FullPath] = c.Entity
}
return out
}
func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
e, err := db.get(name)
conn, err := db.openConn()
if err != nil {
return err
}
for c := range db.children(e, name, depth) {
defer conn.Close()
for c := range db.children(conn, name, depth) {
if err := walkFunc(c.FullPath, c.Entity); err != nil {
return err
}
@@ -222,8 +228,14 @@ func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
// Return the refrence count for a specified id
func (db *Database) Refs(id string) int {
conn, err := db.openConn()
if err != nil {
return -1
}
defer conn.Close()
var count int
if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
if err := conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
return 0
}
return count
@@ -232,8 +244,13 @@ func (db *Database) Refs(id string) int {
// Return all the id's path references
func (db *Database) RefPaths(id string) Edges {
refs := Edges{}
conn, err := db.openConn()
if err != nil {
return refs
}
defer conn.Close()
rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
rows, err := conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
if err != nil {
return refs
}
@@ -259,14 +276,19 @@ func (db *Database) Delete(name string) error {
if name == "/" {
return fmt.Errorf("Cannot delete root entity")
}
conn, err := db.openConn()
if err != nil {
return err
}
defer conn.Close()
parentPath, n := splitPath(name)
parent, err := db.get(parentPath)
parent, err := db.get(conn, parentPath)
if err != nil {
return err
}
if _, err := db.conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil {
if _, err := conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name LIKE ?;", parent.id, n+"%"); err != nil {
return err
}
return nil
@@ -276,16 +298,22 @@ func (db *Database) Delete(name string) error {
// Walk the graph to make sure all references to the entity
// are removed and return the number of references removed
func (db *Database) Purge(id string) (int, error) {
conn, err := db.openConn()
if err != nil {
return -1, err
}
defer conn.Close()
rollback := func() {
db.conn.Exec("ROLLBACK")
conn.Exec("ROLLBACK")
}
if _, err := db.conn.Exec("BEGIN"); err != nil {
if _, err := conn.Exec("BEGIN"); err != nil {
return -1, err
}
// Delete all edges
rows, err := db.conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
rows, err := conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
if err != nil {
rollback()
return -1, err
@@ -297,12 +325,12 @@ func (db *Database) Purge(id string) (int, error) {
}
// Delete entity
if _, err := db.conn.Exec("DELETE FROM entity where id = ?;", id); err != nil {
if _, err := conn.Exec("DELETE FROM entity where id = ?;", id); err != nil {
rollback()
return -1, err
}
if _, err := db.conn.Exec("COMMIT"); err != nil {
if _, err := conn.Exec("COMMIT"); err != nil {
return -1, err
}
return int(changes), nil
@@ -317,12 +345,18 @@ func (db *Database) Rename(currentName, newName string) error {
return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
}
parent, err := db.get(parentPath)
conn, err := db.openConn()
if err != nil {
return err
}
defer conn.Close()
parent, err := db.get(conn, parentPath)
if err != nil {
return err
}
rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name)
rows, err := conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name LIKE ?;", newEdgeName, parent.id, name+"%")
if err != nil {
return err
}
@@ -343,15 +377,16 @@ type WalkMeta struct {
Edge *Edge
}
func (db *Database) children(e *Entity, name string, depth int) <-chan WalkMeta {
func (db *Database) children(conn *sql.DB, name string, depth int) <-chan WalkMeta {
out := make(chan WalkMeta)
if e == nil {
e, err := db.get(conn, name)
if err != nil {
close(out)
return out
}
go func() {
rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
rows, err := conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
if err != nil {
close(out)
}
@@ -385,7 +420,7 @@ func (db *Database) children(e *Entity, name string, depth int) <-chan WalkMeta
if depth != -1 {
nDepth -= 1
}
sc := db.children(child, meta.FullPath, nDepth)
sc := db.children(conn, meta.FullPath, nDepth)
for c := range sc {
out <- c
}
@@ -396,14 +431,18 @@ func (db *Database) children(e *Entity, name string, depth int) <-chan WalkMeta
}
// Return the entity based on the parent path and name
func (db *Database) child(parent *Entity, name string) *Entity {
func (db *Database) child(conn *sql.DB, parent *Entity, name string) *Entity {
var id string
if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil {
if err := conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name LIKE ?;", parent.id, name+"%").Scan(&id); err != nil {
return nil
}
return &Entity{id}
}
func (db *Database) openConn() (*sql.DB, error) {
return sql.Open("sqlite3", db.dbPath)
}
// Return the id used to reference this entity
func (e *Entity) ID() string {
return e.id
+45 -84
View File
@@ -1,40 +1,35 @@
package gograph
import (
_ "code.google.com/p/gosqlite/sqlite3"
"database/sql"
"os"
"path"
"strconv"
"testing"
)
func newTestDb(t *testing.T) (*Database, string) {
p := path.Join(os.TempDir(), "sqlite.db")
conn, err := sql.Open("sqlite3", p)
db, err := NewDatabase(conn, true)
func newTestDb(t *testing.T) *Database {
db, err := NewDatabase(path.Join(os.TempDir(), "sqlite.db"))
if err != nil {
t.Fatal(err)
}
return db, p
return db
}
func destroyTestDb(dbPath string) {
os.Remove(dbPath)
func destroyTestDb(db *Database) {
os.Remove(db.dbPath)
}
func TestNewDatabase(t *testing.T) {
db, dbpath := newTestDb(t)
db := newTestDb(t)
if db == nil {
t.Fatal("Database should not be nil")
}
db.Close()
defer destroyTestDb(dbpath)
defer destroyTestDb(db)
}
func TestCreateRootEnity(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
root := db.RootEntity()
if root == nil {
t.Fatal("Root entity should not be nil")
@@ -42,8 +37,8 @@ func TestCreateRootEnity(t *testing.T) {
}
func TestGetRootEntity(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
e := db.Get("/")
if e == nil {
@@ -55,8 +50,8 @@ func TestGetRootEntity(t *testing.T) {
}
func TestSetEntityWithDifferentName(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
db.Set("/test", "1")
if _, err := db.Set("/other", "1"); err != nil {
@@ -65,8 +60,8 @@ func TestSetEntityWithDifferentName(t *testing.T) {
}
func TestSetDuplicateEntity(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
if _, err := db.Set("/foo", "42"); err != nil {
t.Fatal(err)
@@ -77,8 +72,8 @@ func TestSetDuplicateEntity(t *testing.T) {
}
func TestCreateChild(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
child, err := db.Set("/db", "1")
if err != nil {
@@ -93,8 +88,8 @@ func TestCreateChild(t *testing.T) {
}
func TestListAllRootChildren(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
for i := 1; i < 6; i++ {
a := strconv.Itoa(i)
@@ -109,8 +104,8 @@ func TestListAllRootChildren(t *testing.T) {
}
func TestListAllSubChildren(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
_, err := db.Set("/webapp", "1")
if err != nil {
@@ -151,8 +146,8 @@ func TestListAllSubChildren(t *testing.T) {
}
func TestAddSelfAsChild(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
child, err := db.Set("/test", "1")
if err != nil {
@@ -164,8 +159,8 @@ func TestAddSelfAsChild(t *testing.T) {
}
func TestAddChildToNonExistantRoot(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
if _, err := db.Set("/myapp", "1"); err != nil {
t.Fatal(err)
@@ -177,8 +172,8 @@ func TestAddChildToNonExistantRoot(t *testing.T) {
}
func TestWalkAll(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
@@ -223,8 +218,8 @@ func TestWalkAll(t *testing.T) {
}
func TestGetEntityByPath(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
@@ -270,8 +265,8 @@ func TestGetEntityByPath(t *testing.T) {
}
func TestEnitiesPaths(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
@@ -314,8 +309,8 @@ func TestEnitiesPaths(t *testing.T) {
}
func TestDeleteRootEntity(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
if err := db.Delete("/"); err == nil {
t.Fatal("Error should not be nil")
@@ -323,8 +318,8 @@ func TestDeleteRootEntity(t *testing.T) {
}
func TestDeleteEntity(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
_, err := db.Set("/webapp", "1")
if err != nil {
t.Fatal(err)
@@ -370,8 +365,8 @@ func TestDeleteEntity(t *testing.T) {
}
func TestCountRefs(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
db.Set("/webapp", "1")
@@ -387,8 +382,8 @@ func TestCountRefs(t *testing.T) {
}
func TestPurgeId(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
db.Set("/webapp", "1")
@@ -409,8 +404,8 @@ func TestPurgeId(t *testing.T) {
}
func TestRename(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
db.Set("/webapp", "1")
@@ -438,8 +433,8 @@ func TestRename(t *testing.T) {
}
func TestCreateMultipleNames(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
db.Set("/db", "1")
if _, err := db.Set("/myapp", "1"); err != nil {
@@ -453,8 +448,8 @@ func TestCreateMultipleNames(t *testing.T) {
}
func TestRefPaths(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db := newTestDb(t)
defer destroyTestDb(db)
db.Set("/webapp", "1")
@@ -465,39 +460,5 @@ func TestRefPaths(t *testing.T) {
if len(refs) != 2 {
t.Fatalf("Expected reference count to be 2, got %d", len(refs))
}
}
func TestExistsTrue(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db.Set("/testing", "1")
if !db.Exists("/testing") {
t.Fatalf("/tesing should exist")
}
}
func TestExistsFalse(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db.Set("/toerhe", "1")
if db.Exists("/testing") {
t.Fatalf("/tesing should not exist")
}
}
func TestGetNameWithTrailingSlash(t *testing.T) {
db, dbpath := newTestDb(t)
defer destroyTestDb(dbpath)
db.Set("/todo", "1")
e := db.Get("/todo/")
if e == nil {
t.Fatalf("Entity should not be nil")
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ type pathSorter struct {
func sortByDepth(paths []string) {
s := &pathSorter{paths, func(i, j string) bool {
return PathDepth(i) > PathDepth(j)
return pathDepth(i) > pathDepth(j)
}}
sort.Sort(s)
}
+1 -1
View File
@@ -11,7 +11,7 @@ func split(p string) []string {
}
// Returns the depth or number of / in a given path
func PathDepth(p string) int {
func pathDepth(p string) int {
parts := split(p)
if len(parts) == 2 && parts[1] == "" {
return 1
+5 -2
View File
@@ -121,6 +121,9 @@ func TestRegister(t *testing.T) {
}
func TestMount(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
graph := tempGraph(t)
defer os.RemoveAll(graph.Root)
archive, err := fakeTar()
@@ -144,12 +147,12 @@ func TestMount(t *testing.T) {
if err := os.MkdirAll(rw, 0700); err != nil {
t.Fatal(err)
}
if err := image.Mount(rootfs, rw); err != nil {
if err := image.Mount(runtime, rootfs, rw, "testing"); err != nil {
t.Fatal(err)
}
// FIXME: test for mount contents
defer func() {
if err := Unmount(rootfs); err != nil {
if err := image.Unmount(runtime, rootfs, "testing"); err != nil {
t.Error(err)
}
}()
+12 -24
View File
@@ -32,12 +32,12 @@ the process.
## System build dependencies
To build docker, you will need the following system dependencies
To build docker, you will need the following system dependencies:
* An amd64 machine
* A recent version of git and mercurial
* Go version 1.2rc1 or later (see notes below regarding using Go 1.1.2 and dynbinary)
* SQLite version 3.7.9 or later
* Go version 1.2rc1
* A copy of libdevmapper.a (statically compiled), and associated headers
* A clean checkout of the source must be added to a valid Go [workspace](http://golang.org/doc/code.html#Workspaces)
under the path *src/github.com/dotcloud/docker*.
@@ -55,14 +55,13 @@ NOTE: if you''re not able to package the exact version (to the exact commit) of
please get in touch so we can remediate! Who knows what discrepancies can be caused by even the
slightest deviation. We promise to do our best to make everybody happy.
## Disabling CGO
## Disabling CGO for the net package
Make sure to disable CGO on your system, and then recompile the standard library on the build
machine:
Make sure to disable CGO on your system for the net package using `-tags netgo`,
and then recompile the standard library on the build machine:
```bash
export CGO_ENABLED=0
cd /tmp && echo 'package main' > t.go && go test -a -i -v
go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
```
## Building Docker
@@ -79,7 +78,7 @@ This will create a static binary under *./bundles/$VERSION/binary/docker-$VERSIO
You are encouraged to use ./hack/make.sh without modification. If you must absolutely write
your own script (are you really, really sure you need to? make.sh is really not that complicated),
then please take care the respect the following:
then please take care to respect the following:
* In *./hack/make.sh*: $LDFLAGS, $BUILDFLAGS, $VERSION and $GITCOMMIT
* In *./hack/make/binary*: the exact build command to run
@@ -91,17 +90,6 @@ You would do the users of your distro a disservice and "void the docker warranty
A good comparison is Busybox: all distros package it as a statically linked binary, because it just
makes sense. Docker is the same way.
If you *must* have a non-static Docker binary, or require Go 1.1.2 (since Go 1.2 is not yet officially
released at the time of this writing), please use:
```bash
./hack/make.sh dynbinary
```
This will create *./bundles/$VERSION/dynbinary/docker-$VERSION* and *./bundles/$VERSION/binary/dockerinit-$VERSION*.
The first of these would usually be installed at */usr/bin/docker*, while the second must be installed
at */usr/libexec/docker/dockerinit*.
## Testing Docker
Before releasing your binary, make sure to run the tests! Run the following command with the source
@@ -121,10 +109,10 @@ The test suite will also download a small test container, so you will need inter
To run properly, docker needs the following software to be installed at runtime:
* GNU Tar version 1.26 or later
* iproute2 version 3.5 or later (build after 2012-05-21), and specifically the "ip" utility
* iproute2 version 3.5 or later (build after 2012-05-21), and specifically the "ip" utility.
* iptables version 1.4 or later
* The LXC utility scripts (http://lxc.sourceforge.net) version 0.8 or later
* Git version 1.7 or later
* The lxc utility scripts (http://lxc.sourceforge.net) version 0.8 or later.
* Git version 1.7 or later
* XZ Utils 4.9 or later
## Kernel dependencies
@@ -142,6 +130,6 @@ for your distro''s process supervisor of choice.
Docker should be run as root, with the following arguments:
```bash
```
docker -d
```
+3 -29
View File
@@ -27,31 +27,6 @@ do
[ -d $CGROUP/$SUBSYS ] || mkdir $CGROUP/$SUBSYS
mountpoint -q $CGROUP/$SUBSYS ||
mount -n -t cgroup -o $SUBSYS cgroup $CGROUP/$SUBSYS
# The two following sections address a bug which manifests itself
# by a cryptic "lxc-start: no ns_cgroup option specified" when
# trying to start containers withina container.
# The bug seems to appear when the cgroup hierarchies are not
# mounted on the exact same directories in the host, and in the
# container.
# Named, control-less cgroups are mounted with "-o name=foo"
# (and appear as such under /proc/<pid>/cgroup) but are usually
# mounted on a directory named "foo" (without the "name=" prefix).
# Systemd and OpenRC (and possibly others) both create such a
# cgroup. To avoid the aforementioned bug, we symlink "foo" to
# "name=foo". This shouldn't have any adverse effect.
echo $SUBSYS | grep -q ^name= && {
NAME=$(echo $SUBSYS | sed s/^name=//)
ln -s $SUBSYS $CGROUP/$NAME
}
# Likewise, on at least one system, it has been reported that
# systemd would mount the CPU and CPU accounting controllers
# (respectively "cpu" and "cpuacct") with "-o cpuacct,cpu"
# but on a directory called "cpu,cpuacct" (note the inversion
# in the order of the groups). This tries to work around it.
[ $SUBSYS = cpuacct,cpu ] && ln -s $SUBSYS $CGROUP/cpu,cpuacct
done
# Note: as I write those lines, the LXC userland tools cannot setup
@@ -63,7 +38,7 @@ grep -qw devices /proc/1/cgroup ||
echo "WARNING: it looks like the 'devices' cgroup is not mounted."
# Now, close extraneous file descriptors.
pushd /proc/self/fd >/dev/null
pushd /proc/self/fd
for FD in *
do
case "$FD" in
@@ -76,10 +51,9 @@ do
;;
esac
done
popd >/dev/null
popd
# Mount /tmp
mount -t tmpfs none /tmp
[ "$1" ] && exec "$@"
echo "You probably want to run hack/make.sh, or maybe a shell?"
exec $*
+15 -23
View File
@@ -61,31 +61,23 @@ URL | Service
This should be the list of all services referenced above.
Service | Maintainer(s) | How to update | Source
--------------------|----------------------------|------------------|-------
blog | [@jbarbier] | dotcloud push | https://github.com/dotcloud/blog.docker.io
cdn | [@jpetazzo][] [@samalba][] | cloudflare panel | N/A
ci | [@mzdaniel] | See [docker-ci] | See [docker-ci]
docs | [@metalivedev] | github webhook | docker repo
docsproxy | [@dhrp] | dotcloud push | https://github.com/dotcloud/docker-docs-dotcloud-proxy
index | [@kencochrane] | dotcloud push | private
packages | [@jpetazzo] | hack/release | docker repo
registry | [@samalba] | dotcloud push | https://github.com/dotcloud/docker-registry
repository (github) | N/A | N/A | N/A
ssl (dotcloud) | [@jpetazzo] | dotcloud ops | N/A
ssl (cloudflare) | [@jpetazzo] | cloudflare panel | N/A
stackbrew | [@shin-] | manual | https://github.com/dotcloud/stackbrew/stackbrew
website | [@dhrp] | dotcloud push | https://github.com/dotcloud/www.docker.io
Service | Maintainer(s) | How to update | Source
--------------------|--------------------|------------------|-------
blog | @jbarbier | dotcloud push | https://github.com/dotcloud/blog.docker.io
cdn | @jpetazzo @samalba | cloudflare panel | N/A
ci | @mzdaniel | See [docker-ci] | See [docker-ci]
docs | @metalivedev | github webhook | docker repo
docsproxy | @dhrp | dotcloud push | https://github.com/dotcloud/docker-docs-dotcloud-proxy
index | @kencochrane | dotcloud push | private
packages | @jpetazzo | hack/release | docker repo
registry | @samalba | dotcloud push | https://github.com/dotcloud/docker-registry
repository (github) | N/A | N/A | N/A
ssl (dotcloud) | @jpetazzo | dotcloud ops | N/A
ssl (cloudflare) | @jpetazzo | cloudflare panel | N/A
stackbrew | @shin- | manual | https://github.com/dotcloud/stackbrew/stackbrew
website | @dhrp | dotcloud push | https://github.com/dotcloud/www.docker.io
*Ordered-by: lexicographic*
[docker-ci]: docker-ci.rst
[@dhrp]: https://github.com/dhrp
[@jbarbier]: https://github.com/jbarbier
[@jpetazzo]: https://github.com/jpetazzo
[@kencochrane]: https://github.com/kencochrane
[@metalivedev]: https://github.com/metalivedev
[@mzdaniel]: https://github.com/mzdaniel
[@samalba]: https://github.com/samalba
[@shin-]: https://github.com/shin-
+6 -25
View File
@@ -75,35 +75,16 @@ case "$lsb_dist" in
Ubuntu|Debian)
export DEBIAN_FRONTEND=noninteractive
did_apt_get_update=
apt_get_update() {
if [ -z "$did_apt_get_update" ]; then
( set -x; $sh_c 'sleep 3; apt-get update' )
did_apt_get_update=1
fi
}
# TODO remove this section once device-mapper lands
if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then
kern_extras="linux-image-extra-$(uname -r)"
apt_get_update
( set -x; $sh_c 'sleep 3; apt-get install -y -q '"$kern_extras" ) || true
if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then
echo >&2 'Warning: tried to install '"$kern_extras"' (for AUFS)'
echo >&2 ' but we still have no AUFS. Docker may not work. Proceeding anyways!'
( set -x; sleep 10 )
fi
fi
# TODO remove this comment/section once device-mapper lands
echo 'Warning: Docker currently requires AUFS support in the kernel.'
echo 'Please ensure that your kernel includes such support.'
( set -x; sleep 10 )
if [ ! -e /usr/lib/apt/methods/https ]; then
apt_get_update
( set -x; $sh_c 'sleep 3; apt-get install -y -q apt-transport-https' )
( set -x; $sh_c 'sleep 3; apt-get update; apt-get install -y -q apt-transport-https' )
fi
if [ -z "$curl" ]; then
apt_get_update
( set -x; $sh_c 'sleep 3; apt-get install -y -q curl' )
( set -x; $sh_c 'sleep 3; apt-get update; apt-get install -y -q curl' )
curl='curl -sL'
fi
(
+5 -8
View File
@@ -1,5 +1,4 @@
#!/bin/bash
set -e
# This script builds various binary artifacts from a checkout of the docker
# source code.
@@ -20,7 +19,7 @@ set -e
# "docker run hack/make.sh" in the resulting container image.
#
set -o pipefail
set -e
# We're a nice, sexy, little shell script, and people might try to run us;
# but really, they shouldn't. We want to be in a container!
@@ -33,10 +32,8 @@ grep -q "$RESOLVCONF" /proc/mounts || {
# List of bundles to create when no argument is passed
DEFAULT_BUNDLES=(
binary
test
dynbinary
dyntest
binary
ubuntu
)
@@ -47,10 +44,10 @@ if [ -n "$(git status --porcelain)" ]; then
fi
# Use these flags when compiling the tests and final binary
LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w'
LDFLAGS_STATIC='-X github.com/dotcloud/docker/utils.IAMSTATIC true -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"'
LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-all"'
BUILDFLAGS='-tags netgo'
bundle() {
bundlescript=$1
bundle=$(basename $bundlescript)
@@ -70,7 +67,7 @@ main() {
fi
SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
if [ $# -lt 1 ]; then
bundles=(${DEFAULT_BUNDLES[@]})
bundles=($DEFAULT_BUNDLES)
else
bundles=($@)
fi
+3 -2
View File
@@ -2,5 +2,6 @@
DEST=$1
go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS ./docker
echo "Created binary: $DEST/docker-$VERSION"
if go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" $BUILDFLAGS ./docker; then
echo "Created binary: $DEST/docker-$VERSION"
fi
-15
View File
@@ -1,15 +0,0 @@
#!/bin/sh
DEST=$1
# dockerinit still needs to be a static binary, even if docker is dynamic
CGO_ENABLED=0 go build -a -o $DEST/dockerinit-$VERSION -ldflags "$LDFLAGS -d" $BUILDFLAGS ./dockerinit
echo "Created binary: $DEST/dockerinit-$VERSION"
ln -sf dockerinit-$VERSION $DEST/dockerinit
# sha1 our new dockerinit to ensure separate docker and dockerinit always run in a perfect pair compiled for one another
export DOCKER_INITSHA1="$(sha1sum $DEST/dockerinit-$VERSION | cut -d' ' -f1)"
# exported so that "dyntest" can easily access it later without recalculating it
go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS -X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\"" $BUILDFLAGS ./docker
echo "Created binary: $DEST/docker-$VERSION"
-42
View File
@@ -1,42 +0,0 @@
#!/bin/sh
DEST=$1
INIT=$DEST/../dynbinary/dockerinit-$VERSION
set -e
if [ ! -x "$INIT" ]; then
echo >&2 'error: dynbinary must be run before dyntest'
false
fi
# Run Docker's test suite, including sub-packages, and store their output as a bundle
# If $TESTFLAGS is set in the environment, it is passed as extra arguments to 'go test'.
# You can use this to select certain tests to run, eg.
#
# TESTFLAGS='-run ^TestBuild$' ./hack/make.sh test
#
bundle_test() {
{
date
for test_dir in $(find_test_dirs); do (
set -x
cd $test_dir
go test -i -ldflags "$LDFLAGS" $BUILDFLAGS
export TEST_DOCKERINIT_PATH=$DEST/../dynbinary/dockerinit-$VERSION
go test -v -ldflags "$LDFLAGS -X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\"" $BUILDFLAGS $TESTFLAGS
) done
} 2>&1 | tee $DEST/test.log
}
# This helper function walks the current directory looking for directories
# holding Go test files, and prints their paths on standard output, one per
# line.
find_test_dirs() {
find . -name '*_test.go' | grep -v '^./vendor' |
{ while read f; do dirname $f; done; } |
sort -u
}
bundle_test
+23 -2
View File
@@ -16,8 +16,7 @@ bundle_test() {
for test_dir in $(find_test_dirs); do (
set -x
cd $test_dir
go test -i -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS
go test -v -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS $TESTFLAGS
go test -v -ldflags "$LDFLAGS" $BUILDFLAGS $TESTFLAGS
) done
} 2>&1 | tee $DEST/test.log
}
@@ -32,4 +31,26 @@ find_test_dirs() {
sort -u
}
check_test_garbage() {
nGarbage=`find /tmp/ -name 'docker-*' | wc -l`
if [ $nGarbage -gt 0 ]; then
(
cat <<EOF
Error: there are $nGarbage docker-related files in /tmp. Please clean them up and try again.
--------------------------------------------------------------------------------------------
EOF
find /tmp -name 'docker-*'
cat <<EOF
--------------------------------------------------------------------------------------------
If the error persists, your tests might leak temporary files. This is considered a test fail.
EOF
exit 1
) 2>&1
fi
}
check_test_garbage
bundle_test
check_test_garbage
+29 -30
View File
@@ -3,7 +3,8 @@
DEST=$1
PKGVERSION="$VERSION"
if [ -n "$(git status --porcelain)" ]; then
if test -n "$(git status --porcelain)"
then
PKGVERSION="$PKGVERSION-$(date +%Y%m%d%H%M%S)-$GITCOMMIT"
fi
@@ -18,17 +19,26 @@ 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."
UPSTART_SCRIPT='description "Docker daemon"
start on filesystem and started lxc-net
stop on runlevel [!2345]
respawn
script
/usr/bin/docker -d
end script
'
# Build docker as an ubuntu package using FPM and REPREPRO (sue me).
# bundle_binary must be called first.
bundle_ubuntu() {
DIR=$DEST/build
# Include our init scripts
mkdir -p $DIR/etc
cp -R contrib/init/upstart $DIR/etc/init
cp -R contrib/init/sysvinit $DIR/etc/init.d
mkdir -p $DIR/lib/systemd
cp -R contrib/init/systemd $DIR/lib/systemd/system
# Generate an upstart config file (ubuntu-specific)
mkdir -p $DIR/etc/init
echo "$UPSTART_SCRIPT" > $DIR/etc/init/docker.conf
# Copy the binary
# This will fail if the binary bundle hasn't been built
@@ -37,40 +47,29 @@ bundle_ubuntu() {
# This will fail if the binary bundle hasn't been built
cp $DEST/../binary/docker-$VERSION $DIR/usr/bin/docker
# Generate postinst/prerm scripts
cat >/tmp/postinst <<'EOF'
# Generate postinstall/prerm scripts
cat >/tmp/postinstall <<EOF
#!/bin/sh
service docker stop || true
grep -q '^docker:' /etc/group || groupadd --system docker || true
service docker start
/sbin/stop docker || true
/bin/grep -q "^docker:" /etc/group || /usr/sbin/addgroup --system docker || true
/sbin/start docker
EOF
cat >/tmp/prerm <<'EOF'
cat >/tmp/prerm <<EOF
#!/bin/sh
service docker stop || true
case "$1" in
purge|remove|abort-install)
groupdel docker || true
;;
upgrade|failed-upgrade|abort-upgrade)
# don't touch docker group
;;
esac
/sbin/stop docker || true
/usr/sbin/delgroup docker || true
EOF
chmod +x /tmp/postinst /tmp/prerm
chmod +x /tmp/postinstall /tmp/prerm
(
cd $DEST
fpm -s dir -C $DIR \
--name lxc-docker-$VERSION --version $PKGVERSION \
--after-install /tmp/postinst \
--after-install /tmp/postinstall \
--before-remove /tmp/prerm \
--architecture "$PACKAGE_ARCHITECTURE" \
--prefix / \
--depends lxc \
--depends aufs-tools \
--depends iptables \
--depends lxc --depends aufs-tools \
--description "$PACKAGE_DESCRIPTION" \
--maintainer "$PACKAGE_MAINTAINER" \
--conflicts lxc-docker-virtual-package \
@@ -81,7 +80,6 @@ EOF
--url "$PACKAGE_URL" \
--vendor "$PACKAGE_VENDOR" \
--config-files /etc/init/docker.conf \
--config-files /etc/init.d/docker \
-t deb .
mkdir empty
fpm -s dir -C empty \
@@ -92,6 +90,7 @@ EOF
--maintainer "$PACKAGE_MAINTAINER" \
--url "$PACKAGE_URL" \
--vendor "$PACKAGE_VENDOR" \
--config-files /etc/init/docker.conf \
-t deb .
)
}
+15 -60
View File
@@ -1,5 +1,4 @@
#!/bin/bash
set -e
#!/bin/sh
# This script looks for bundles built by make.sh, and releases them on a
# public S3 bucket.
@@ -10,11 +9,11 @@ set -e
# official Dockerfile at the root of the Docker source code. The Dockerfile,
# make.sh and release.sh should all be from the same source code revision.
set -o pipefail
set -e
# Print a usage message and exit.
usage() {
cat >&2 <<'EOF'
cat <<EOF
To run, I need:
- to be in a container generated by the Dockerfile at the top of the Docker
repository;
@@ -27,12 +26,11 @@ To run, I need:
- a generous amount of good will and nice manners.
The canonical way to run me is to run the image produced by the Dockerfile: e.g.:"
docker run -e AWS_S3_BUCKET=get-staging.docker.io \
-e AWS_ACCESS_KEY=AKI1234... \
-e AWS_SECRET_KEY=sEs4mE... \
-e GPG_PASSPHRASE=m0resEs4mE... \
-i -t -privileged \
docker ./hack/release.sh
docker run -e AWS_S3_BUCKET=get-staging.docker.io \\
AWS_ACCESS_KEY=AKI1234... \\
AWS_SECRET_KEY=sEs4mE... \\
GPG_PASSPHRASE=m0resEs4mE... \\
f0058411
EOF
exit 1
}
@@ -41,41 +39,8 @@ EOF
[ "$AWS_ACCESS_KEY" ] || usage
[ "$AWS_SECRET_KEY" ] || usage
[ "$GPG_PASSPHRASE" ] || usage
[ -d /go/src/github.com/dotcloud/docker ] || usage
cd /go/src/github.com/dotcloud/docker
[ -x hack/make.sh ] || usage
RELEASE_BUNDLES=(
binary
ubuntu
)
if [ "$1" != '--release-regardless-of-test-failure' ]; then
RELEASE_BUNDLES=( test "${RELEASE_BUNDLES[@]}" )
fi
if ! ./hack/make.sh "${RELEASE_BUNDLES[@]}"; then
echo >&2
echo >&2 'The build or tests appear to have failed.'
echo >&2
echo >&2 'You, as the release maintainer, now have a couple options:'
echo >&2 '- delay release and fix issues'
echo >&2 '- delay release and fix issues'
echo >&2 '- did we mention how important this is? issues need fixing :)'
echo >&2
echo >&2 'As a final LAST RESORT, you (because only you, the release maintainer,'
echo >&2 ' really knows all the hairy problems at hand with the current release'
echo >&2 ' issues) may bypass this checking by running this script again with the'
echo >&2 ' single argument of "--release-regardless-of-test-failure", which will skip'
echo >&2 ' running the test suite, and will only build the binaries and packages. Please'
echo >&2 ' avoid using this if at all possible.'
echo >&2
echo >&2 'Regardless, we cannot stress enough the scarcity with which this bypass'
echo >&2 ' should be used. If there are release issues, we should always err on the'
echo >&2 ' side of caution.'
echo >&2
exit 1
fi
[ -d /go/src/github.com/dotcloud/docker/ ] || usage
cd /go/src/github.com/dotcloud/docker/
VERSION=$(cat VERSION)
BUCKET=$AWS_S3_BUCKET
@@ -116,15 +81,11 @@ s3_url() {
# 1. A full APT repository is published at $BUCKET/ubuntu/
# 2. Instructions for using the APT repository are uploaded at $BUCKET/ubuntu/info
release_ubuntu() {
[ -e bundles/$VERSION/ubuntu ] || {
echo >&2 './hack/make.sh must be run before release_ubuntu'
exit 1
}
# Make sure that we have our keys
mkdir -p /.gnupg/
s3cmd sync s3://$BUCKET/ubuntu/.gnupg/ /.gnupg/ || true
gpg --list-keys releasedocker >/dev/null || {
gpg --gen-key --batch <<EOF
gpg --gen-key --batch <<EOF
Key-Type: RSA
Key-Length: 2048
Passphrase: $GPG_PASSPHRASE
@@ -138,7 +99,7 @@ EOF
# Sign our packages
dpkg-sig -g "--passphrase $GPG_PASSPHRASE" -k releasedocker \
--sign builder bundles/$VERSION/ubuntu/*.deb
--sign builder bundles/$VERSION/ubuntu/*.deb
# Setup the APT repo
APTDIR=bundles/$VERSION/ubuntu/apt
@@ -155,7 +116,8 @@ EOF
reprepro -b $APTDIR includedeb docker $DEBFILE
# Sign
for F in $(find $APTDIR -name Release); do
for F in $(find $APTDIR -name Release)
do
gpg -u releasedocker --passphrase $GPG_PASSPHRASE \
--armor --sign --detach-sign \
--output $F.gpg $F
@@ -175,20 +137,13 @@ echo deb $(s3_url)/ubuntu docker main > /etc/apt/sources.list.d/docker.list
curl $(s3_url)/gpg | apt-key add -
# Install docker
apt-get update ; apt-get install -y lxc-docker
#
# Alternatively, just use the curl-able install.sh script provided at $(s3_url)
#
EOF
echo "APT repository uploaded. Instructions available at $(s3_url)/ubuntu/info"
}
# Upload a static binary to S3
release_binary() {
[ -e bundles/$VERSION/binary/docker-$VERSION ] || {
echo >&2 './hack/make.sh must be run before release_binary'
exit 1
}
[ -e bundles/$VERSION ]
S3DIR=s3://$BUCKET/builds/Linux/x86_64
s3cmd --acl-public put bundles/$VERSION/binary/docker-$VERSION $S3DIR/docker-$VERSION
cat <<EOF | write_to_s3 s3://$BUCKET/builds/info
+367 -39
View File
@@ -5,16 +5,16 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/dotcloud/docker/devmapper"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
)
@@ -61,6 +61,7 @@ func LoadImage(root string) (*Image, error) {
}
// Check that the filesystem layer exists
// FIXME: once an image is added into device mapper, the layer is no longer needed
if stat, err := os.Stat(layerPath(root)); err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("Couldn't load image %s: no filesystem layer", img.ID)
@@ -92,7 +93,7 @@ func StoreImage(img *Image, jsonData []byte, layerData Archive, root string) err
if err := Untar(layerData, layer); err != nil {
return err
}
utils.Debugf("Untar time: %vs", time.Now().Sub(start).Seconds())
utils.Debugf("Untar time: %vs\n", time.Now().Sub(start).Seconds())
}
// If raw json is provided, then use it
@@ -136,29 +137,8 @@ func jsonPath(root string) string {
return path.Join(root, "json")
}
func MountAUFS(ro []string, rw string, target string) error {
// FIXME: Now mount the layers
rwBranch := fmt.Sprintf("%v=rw", rw)
roBranches := ""
for _, layer := range ro {
roBranches += fmt.Sprintf("%v=ro+wh:", layer)
}
branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)
branches += ",xino=/dev/shm/aufs.xino"
//if error, try to load aufs kernel module
if err := mount("none", target, "aufs", 0, branches); err != nil {
log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...")
if err := exec.Command("modprobe", "aufs").Run(); err != nil {
return fmt.Errorf("Unable to load the AUFS module")
}
log.Printf("...module loaded.")
if err := mount("none", target, "aufs", 0, branches); err != nil {
return fmt.Errorf("Unable to mount using aufs")
}
}
return nil
func mountPath(root string) string {
return path.Join(root, "mount")
}
// TarLayer returns a tar archive of the image's filesystem layer.
@@ -170,35 +150,383 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) {
return Tar(layerPath, compression)
}
func (image *Image) Mount(root, rw string) error {
if mounted, err := Mounted(root); err != nil {
return err
} else if mounted {
return fmt.Errorf("%s is already mounted", root)
}
layers, err := image.layers()
type TimeUpdate struct {
path string
time []syscall.Timeval
mode uint32
}
func (image *Image) applyLayer(layer, target string) error {
var updateTimes []TimeUpdate
oldmask := syscall.Umask(0)
defer syscall.Umask(oldmask)
err := filepath.Walk(layer, func(srcPath string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// Skip root
if srcPath == layer {
return nil
}
var srcStat syscall.Stat_t
err = syscall.Lstat(srcPath, &srcStat)
if err != nil {
return err
}
relPath, err := filepath.Rel(layer, srcPath)
if err != nil {
return err
}
targetPath := filepath.Join(target, relPath)
// Skip AUFS metadata
if matched, err := filepath.Match(".wh..wh.*", relPath); err != nil || matched {
if err != nil || !f.IsDir() {
return err
}
return filepath.SkipDir
}
// Find out what kind of modification happened
file := filepath.Base(srcPath)
// If there is a whiteout, then the file was removed
if strings.HasPrefix(file, ".wh.") {
originalFile := file[len(".wh."):]
deletePath := filepath.Join(filepath.Dir(targetPath), originalFile)
err = os.RemoveAll(deletePath)
if err != nil {
return err
}
} else {
var targetStat = &syscall.Stat_t{}
err := syscall.Lstat(targetPath, targetStat)
if err != nil {
if !os.IsNotExist(err) {
return err
}
targetStat = nil
}
if targetStat != nil && !(targetStat.Mode&syscall.S_IFDIR == syscall.S_IFDIR && srcStat.Mode&syscall.S_IFDIR == syscall.S_IFDIR) {
// Unless both src and dest are directories we remove the target and recreate it
// This is a bit wasteful in the case of only a mode change, but that is unlikely
// to matter much
err = os.RemoveAll(targetPath)
if err != nil {
return err
}
targetStat = nil
}
if f.IsDir() {
// Source is a directory
if targetStat == nil {
err = syscall.Mkdir(targetPath, srcStat.Mode&07777)
if err != nil {
return err
}
}
} else if srcStat.Mode&syscall.S_IFLNK == syscall.S_IFLNK {
// Source is symlink
link, err := os.Readlink(srcPath)
if err != nil {
return err
}
err = os.Symlink(link, targetPath)
if err != nil {
return err
}
} else if srcStat.Mode&syscall.S_IFBLK == syscall.S_IFBLK ||
srcStat.Mode&syscall.S_IFCHR == syscall.S_IFCHR ||
srcStat.Mode&syscall.S_IFIFO == syscall.S_IFIFO ||
srcStat.Mode&syscall.S_IFSOCK == syscall.S_IFSOCK {
// Source is special file
err = syscall.Mknod(targetPath, srcStat.Mode, int(srcStat.Rdev))
if err != nil {
return err
}
} else if srcStat.Mode&syscall.S_IFREG == syscall.S_IFREG {
// Source is regular file
fd, err := syscall.Open(targetPath, syscall.O_CREAT|syscall.O_WRONLY, srcStat.Mode&07777)
if err != nil {
return err
}
dstFile := os.NewFile(uintptr(fd), targetPath)
srcFile, err := os.Open(srcPath)
if err != nil {
_ = dstFile.Close()
return err
}
err = CopyFile(dstFile, srcFile)
_ = dstFile.Close()
_ = srcFile.Close()
if err != nil {
return err
}
} else {
return fmt.Errorf("Unknown type for file %s", srcPath)
}
err = syscall.Lchown(targetPath, int(srcStat.Uid), int(srcStat.Gid))
if err != nil {
return err
}
if srcStat.Mode&syscall.S_IFLNK != syscall.S_IFLNK {
err = syscall.Chmod(targetPath, srcStat.Mode&07777)
if err != nil {
return err
}
}
ts := []syscall.Timeval{
syscall.NsecToTimeval(srcStat.Atim.Nano()),
syscall.NsecToTimeval(srcStat.Mtim.Nano()),
}
u := TimeUpdate{
path: targetPath,
time: ts,
mode: srcStat.Mode,
}
// Delay time updates until all other changes done, or it is
// overwritten for directories (by child changes)
updateTimes = append(updateTimes, u)
}
return nil
})
if err != nil {
return err
}
// We do this in reverse order so that children are updated before parents
for i := len(updateTimes) - 1; i >= 0; i-- {
update := updateTimes[i]
O_PATH := 010000000 // Not in syscall yet
var err error
if update.mode&syscall.S_IFLNK == syscall.S_IFLNK {
// Update time on the symlink via O_PATH + futimes(), if supported by the kernel
fd, err := syscall.Open(update.path, syscall.O_RDWR|O_PATH|syscall.O_NOFOLLOW, 0600)
if err == syscall.EISDIR || err == syscall.ELOOP {
// O_PATH not supported by kernel, nothing to do, ignore
} else if err != nil {
return err
} else {
syscall.Futimes(fd, update.time)
syscall.Close(fd)
}
} else {
err = syscall.Utimes(update.path, update.time)
if err != nil {
return err
}
}
}
return nil
}
func (image *Image) ensureImageDevice(devices *devmapper.DeviceSet) error {
if devices.HasInitializedDevice(image.ID) {
return nil
}
if image.Parent != "" && !devices.HasInitializedDevice(image.Parent) {
parentImg, err := image.GetParent()
if err != nil {
return fmt.Errorf("Error while getting parent image: %v", err)
}
err = parentImg.ensureImageDevice(devices)
if err != nil {
return err
}
}
root, err := image.root()
if err != nil {
return err
}
mountDir := mountPath(root)
if err := os.Mkdir(mountDir, 0600); err != nil && !os.IsExist(err) {
return err
}
mounted, err := Mounted(mountDir)
if err == nil && mounted {
utils.Debugf("Image %s is unexpectedly mounted, unmounting...", image.ID)
err = syscall.Unmount(mountDir, 0)
if err != nil {
return err
}
}
if devices.HasDevice(image.ID) {
utils.Debugf("Found non-initialized demove-mapper device for image %s, removing", image.ID)
err = devices.RemoveDevice(image.ID)
if err != nil {
return err
}
}
utils.Debugf("Creating device-mapper device for image id %s", image.ID)
if err := devices.AddDevice(image.ID, image.Parent); err != nil {
utils.Debugf("Error add device: %s", err)
return err
}
if err := devices.MountDevice(image.ID, mountDir, false); err != nil {
utils.Debugf("Error mounting device: %s", err)
devices.RemoveDevice(image.ID)
return err
}
if err = image.applyLayer(layerPath(root), mountDir); err != nil {
utils.Debugf("Error applying layer: %s", err)
devices.UnmountDevice(image.ID, mountDir, true)
devices.RemoveDevice(image.ID)
return err
}
// The docker init layer is conceptually above all other layers, so we apply
// it for every image. This is safe because the layer directory is the
// definition of the image, and the device-mapper device is just a cache
// of it instantiated. Diffs/commit compare the container device with the
// image device, which will then *not* pick up the init layer changes as
// part of the container changes
dockerinitLayer, err := image.getDockerInitLayer()
if err != nil {
devices.UnmountDevice(image.ID, mountDir, true)
devices.RemoveDevice(image.ID)
return err
}
if err := image.applyLayer(dockerinitLayer, mountDir); err != nil {
devices.UnmountDevice(image.ID, mountDir, true)
devices.RemoveDevice(image.ID)
return err
}
if err := devices.UnmountDevice(image.ID, mountDir, true); err != nil {
devices.RemoveDevice(image.ID)
return err
}
devices.SetInitialized(image.ID)
return nil
}
func (image *Image) Mounted(runtime *Runtime, root, rw string) (bool, error) {
return Mounted(root)
}
func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error {
if mounted, _ := image.Mounted(runtime, root, rw); mounted {
return fmt.Errorf("%s is already mounted", root)
}
// Create the target directories if they don't exist
if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) {
return err
}
if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) {
devices, err := runtime.GetDeviceSet()
if err != nil {
return err
}
if err := MountAUFS(layers, rw, root); err != nil {
if err := image.ensureImageDevice(devices); err != nil {
return err
}
if !devices.HasDevice(id) {
utils.Debugf("Creating device %s for container based on image %s", id, image.ID)
err = devices.AddDevice(id, image.ID)
if err != nil {
return err
}
}
utils.Debugf("Mounting container %s at %s for container", id, root)
if err := devices.MountDevice(id, root, false); err != nil {
return err
}
return nil
}
func (image *Image) Changes(rw string) ([]Change, error) {
layers, err := image.layers()
func (image *Image) Unmount(runtime *Runtime, root string, id string) error {
// Try to deactivate the device as generally there is no use for it anymore
devices, err := runtime.GetDeviceSet()
if err != nil {
return err
}
if err = devices.UnmountDevice(id, root, true); err != nil {
return err
}
return nil
}
func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, error) {
devices, err := runtime.GetDeviceSet()
if err != nil {
return nil, err
}
return Changes(layers, rw)
if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) {
return nil, err
}
wasActivated := devices.HasActivatedDevice(image.ID)
// We re-use rw for the temporary mount of the base image as its
// not used by device-mapper otherwise
err = devices.MountDevice(image.ID, rw, true)
if err != nil {
return nil, err
}
changes, err := ChangesDirs(root, rw)
devices.UnmountDevice(image.ID, rw, !wasActivated)
if err != nil {
return nil, err
}
return changes, nil
}
func (image *Image) ExportChanges(runtime *Runtime, root, rw, id string) (Archive, error) {
changes, err := image.Changes(runtime, root, rw, id)
if err != nil {
return nil, err
}
files := make([]string, 0)
deletions := make([]string, 0)
for _, change := range changes {
if change.Kind == ChangeModify || change.Kind == ChangeAdd {
files = append(files, change.Path)
}
if change.Kind == ChangeDelete {
base := filepath.Base(change.Path)
dir := filepath.Dir(change.Path)
deletions = append(deletions, filepath.Join(dir, ".wh."+base))
}
}
return TarFilter(root, Uncompressed, files, false, deletions)
}
func (image *Image) ShortID() string {
@@ -335,7 +663,7 @@ func (img *Image) getParentsSize(size int64) int64 {
func NewImgJSON(src []byte) (*Image, error) {
ret := &Image{}
utils.Debugf("Json string: {%s}", src)
utils.Debugf("Json string: {%s}\n", src)
// FIXME: Is there a cleaner way to "purify" the input json?
if err := json.Unmarshal(src, ret); err != nil {
return nil, err
-5
View File
@@ -92,11 +92,6 @@ func (c *Chain) Remove() error {
return nil
}
// Check if an existing rule exists
func Exists(args ...string) bool {
return Raw(append([]string{"-C"}, args...)...) == nil
}
func Raw(args ...string) error {
path, err := exec.LookPath("iptables")
if err != nil {
+1
View File
@@ -0,0 +1 @@
Joffrey Fuhrer <joffrey@dotcloud.com> (@shin-)
+1
View File
@@ -0,0 +1 @@
latest git://github.com/dotcloud/docker-busybox
+2
View File
@@ -0,0 +1,2 @@
latest git://github.com/dotcloud/hipache C:7362ff5b812f93eceafbdbf5e5959f676f731f80
0.2.4 git://github.com/dotcloud/hipache C:7362ff5b812f93eceafbdbf5e5959f676f731f80
+7
View File
@@ -0,0 +1,7 @@
latest git://github.com/dotcloud/ubuntu-quantal
quantal git://github.com/dotcloud/ubuntu-quantal
12.10 git://github.com/dotcloud/ubuntu-quantal
precise git://github.com/shin-/ubuntu B:precise
12.04 git://github.com/shin-/ubuntu B:precise
raring git://github.com/shin-/ubuntu B:raring
13.04 git://github.com/shin-/ubuntu B:raring
+1 -4
View File
@@ -22,7 +22,7 @@ func NewLink(parent, child *Container, name, bridgeInterface string) (*Link, err
return nil, fmt.Errorf("Cannot link to self: %s == %s", parent.ID, child.ID)
}
if !child.State.Running {
return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, name)
return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.ID, name)
}
ports := make([]Port, len(child.Config.ExposedPorts))
@@ -60,9 +60,6 @@ func (l *Link) ToEnv() []string {
// Load exposed ports into the environment
for _, p := range l.Ports {
env = append(env, fmt.Sprintf("%s_PORT_%s_%s=%s://%s:%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto(), l.ChildIP, p.Port()))
env = append(env, fmt.Sprintf("%s_PORT_%s_%s_ADDR=%s", alias, p.Port(), strings.ToUpper(p.Proto()), l.ChildIP))
env = append(env, fmt.Sprintf("%s_PORT_%s_%s_PORT=%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Port()))
env = append(env, fmt.Sprintf("%s_PORT_%s_%s_PROTO=%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto()))
}
// Load the linked container's name into the environment
+1 -10
View File
@@ -90,20 +90,11 @@ func TestLinkEnv(t *testing.T) {
env[parts[0]] = parts[1]
}
if env["DOCKER_PORT"] != "tcp://172.0.17.2:6379" {
t.Fatalf("Expected 172.0.17.2:6379, got %s", env["DOCKER_PORT"])
t.Fatalf("Expected tcp://172.0.17.2:6379, got %s", env["DOCKER_PORT"])
}
if env["DOCKER_PORT_6379_TCP"] != "tcp://172.0.17.2:6379" {
t.Fatalf("Expected tcp://172.0.17.2:6379, got %s", env["DOCKER_PORT_6379_TCP"])
}
if env["DOCKER_PORT_6379_TCP_PROTO"] != "tcp" {
t.Fatalf("Expected tcp, got %s", env["DOCKER_PORT_6379_TCP_PROTO"])
}
if env["DOCKER_PORT_6379_TCP_ADDR"] != "172.0.17.2" {
t.Fatalf("Expected 172.0.17.2, got %s", env["DOCKER_PORT_6379_TCP_ADDR"])
}
if env["DOCKER_PORT_6379_TCP_PORT"] != "6379" {
t.Fatalf("Expected 6379, got %s", env["DOCKER_PORT_6379_TCP_PORT"])
}
if env["DOCKER_NAME"] != "/db/docker" {
t.Fatalf("Expected /db/docker, got %s", env["DOCKER_NAME"])
}
-2
View File
@@ -81,8 +81,6 @@ lxc.cgroup.devices.allow = c 10:200 rwm
{{end}}
# standard mount point
# Use mnt.putold as per https://bugs.launchpad.net/ubuntu/+source/lxc/+bug/986385
lxc.pivotdir = lxc_putold
# 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
-29
View File
@@ -1,40 +1,11 @@
package docker
import (
"fmt"
"github.com/dotcloud/docker/utils"
"os"
"os/exec"
"path/filepath"
"syscall"
"time"
)
func Unmount(target string) error {
if err := exec.Command("auplink", target, "flush").Run(); err != nil {
utils.Errorf("[warning]: couldn't run auplink before unmount: %s", err)
}
if err := syscall.Unmount(target, 0); err != nil {
return err
}
// Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint
// for some time. We'll just keep retrying until it succeeds.
for retries := 0; retries < 1000; retries++ {
err := os.Remove(target)
if err == nil {
// rm mntpoint succeeded
return nil
}
if os.IsNotExist(err) {
// mntpoint doesn't exist anymore. Success.
return nil
}
// fmt.Printf("(%v) Remove %v returned: %v\n", retries, target, err)
time.Sleep(10 * time.Millisecond)
}
return fmt.Errorf("Umount: Failed to umount %v", target)
}
func Mounted(mountpoint string) (bool, error) {
mntpoint, err := os.Stat(mountpoint)
if err != nil {
-30
View File
@@ -1,30 +0,0 @@
package namesgenerator
import (
"fmt"
"math/rand"
"time"
)
type NameChecker interface {
Exists(name string) bool
}
var (
colors = [...]string{"white", "silver", "gray", "black", "blue", "green", "cyan", "yellow", "gold", "orange", "brown", "red", "violet", "pink", "magenta", "purple"}
animals = [...]string{"ant", "bird", "cat", "chicken", "cow", "dog", "fish", "fox", "horse", "lion", "monkey", "pig", "sheep", "tiger", "whale", "wolf"}
)
func GenerateRandomName(checker NameChecker) (string, error) {
retry := 5
rand.Seed(time.Now().UnixNano())
name := fmt.Sprintf("%s_%s", colors[rand.Intn(len(colors))], animals[rand.Intn(len(animals))])
for checker != nil && checker.Exists(name) && retry > 0 {
name = fmt.Sprintf("%s%d", name, rand.Intn(10))
retry = retry - 1
}
if retry == 0 {
return name, fmt.Errorf("Error generating random name")
}
return name, nil
}
-28
View File
@@ -1,28 +0,0 @@
package namesgenerator
import (
"testing"
)
type FalseChecker struct{}
func (n *FalseChecker) Exists(name string) bool {
return false
}
type TrueChecker struct{}
func (n *TrueChecker) Exists(name string) bool {
return true
}
func TestGenerateRandomName(t *testing.T) {
if _, err := GenerateRandomName(&FalseChecker{}); err != nil {
t.Error(err)
}
if _, err := GenerateRandomName(&TrueChecker{}); err == nil {
t.Error("An error was expected")
}
}
+10 -17
View File
@@ -145,6 +145,16 @@ func CreateBridgeIface(config *DaemonConfig) error {
"!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil {
return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
}
if !config.InterContainerCommunication {
utils.Debugf("Disable inter-container communication")
if err := iptables.Raw("-A", "FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP"); err != nil {
return fmt.Errorf("Unable to prevent intercontainer communication: %s", err)
}
} else {
utils.Debugf("Enable inter-container communication")
iptables.Raw("-D", "FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP")
}
}
return nil
}
@@ -649,23 +659,6 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) {
}
network := addr.(*net.IPNet)
// Configure iptables for link support
if config.EnableIptables {
args := []string{"FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP"}
if !config.InterContainerCommunication {
if !iptables.Exists(args...) {
utils.Debugf("Disable inter-container communication")
if err := iptables.Raw(append([]string{"-A"}, args...)...); err != nil {
return nil, fmt.Errorf("Unable to prevent intercontainer communication: %s", err)
}
}
} else {
utils.Debugf("Enable inter-container communication")
iptables.Raw(append([]string{"-D"}, args...)...)
}
}
ipAllocator := newIPAllocator(network)
tcpPortAllocator, err := newPortAllocator()
+6 -1
View File
@@ -44,6 +44,7 @@ func NewEchoServer(t *testing.T, proto, address string) EchoServer {
}
server = &UDPEchoServer{conn: socket, testCtx: t}
}
t.Logf("EchoServer listening on %v/%v\n", proto, server.LocalAddr().String())
return server
}
@@ -55,7 +56,10 @@ func (server *TCPEchoServer) Run() {
return
}
go func(client net.Conn) {
if _, err := io.Copy(client, client); err != nil {
server.testCtx.Logf("TCP client accepted on the EchoServer\n")
written, err := io.Copy(client, client)
server.testCtx.Logf("%v bytes echoed back to the client\n", written)
if err != nil {
server.testCtx.Logf("can't echo to the client: %v\n", err.Error())
}
client.Close()
@@ -75,6 +79,7 @@ func (server *UDPEchoServer) Run() {
if err != nil {
return
}
server.testCtx.Logf("Writing UDP datagram back")
for i := 0; i != read; {
written, err := server.conn.WriteTo(readBuf[i:read], from)
if err != nil {
+2 -3
View File
@@ -499,12 +499,11 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData
}
u := fmt.Sprintf("%srepositories/%s/%s", indexEp, remote, suffix)
utils.Debugf("[registry] PUT %s", u)
utils.Debugf("Image list pushed to index:\n%s", imgListJSON)
utils.Debugf("Image list pushed to index:\n%s\n", imgListJSON)
req, err := r.reqFactory.NewRequest("PUT", u, bytes.NewReader(imgListJSON))
if err != nil {
return nil, err
}
req.Header.Add("Content-type", "application/json")
req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
req.ContentLength = int64(len(imgListJSON))
req.Header.Set("X-Docker-Token", "true")
@@ -520,7 +519,7 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData
// Redirect if necessary
for res.StatusCode >= 300 && res.StatusCode < 400 {
utils.Debugf("Redirected to %s", res.Header.Get("Location"))
utils.Debugf("Redirected to %s\n", res.Header.Get("Location"))
req, err = r.reqFactory.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJSON))
if err != nil {
return nil, err
+218 -98
View File
@@ -1,10 +1,9 @@
package docker
import (
_ "code.google.com/p/gosqlite/sqlite3"
"container/list"
"database/sql"
"fmt"
"github.com/dotcloud/docker/devmapper"
"github.com/dotcloud/docker/gograph"
"github.com/dotcloud/docker/utils"
"io"
@@ -13,11 +12,16 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strings"
"time"
)
const (
DefaultFilesystemType = "devicemapper"
)
var defaultDns = []string{"8.8.8.8", "8.8.4.4"}
type Capabilities struct {
@@ -34,12 +38,36 @@ type Runtime struct {
repositories *TagStore
idIndex *utils.TruncIndex
capabilities *Capabilities
kernelVersion *utils.KernelVersionInfo
volumes *Graph
srv *Server
Dns []string
deviceSet *devmapper.DeviceSet
config *DaemonConfig
containerGraph *gograph.Database
}
var sysInitPath string
func init() {
env := os.Getenv("_DOCKER_INIT_PATH")
if env != "" {
sysInitPath = env
} else {
selfPath := utils.SelfPath()
// If we have a separate dockerinit, use that, otherwise use the
// main docker binary
dir := filepath.Dir(selfPath)
dockerInitPath := filepath.Join(dir, "dockerinit")
if _, err := os.Stat(dockerInitPath); err != nil {
sysInitPath = selfPath
} else {
sysInitPath = dockerInitPath
}
}
}
// List returns an array of all containers registered in the runtime.
func (runtime *Runtime) List() []*Container {
containers := new(History)
@@ -59,9 +87,19 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element {
return nil
}
func (runtime *Runtime) GetDeviceSet() (*devmapper.DeviceSet, error) {
if runtime.deviceSet == nil {
return nil, fmt.Errorf("No device set available")
}
return runtime.deviceSet, nil
}
// Get looks for a container by the specified ID or name, and returns it.
// If the container is not found, or if an error occurs, nil is returned.
func (runtime *Runtime) Get(name string) *Container {
if name[0] != '/' {
name = "/" + name
}
if c, _ := runtime.GetByName(name); c != nil {
return c
}
@@ -218,6 +256,24 @@ func (runtime *Runtime) Destroy(container *Container) error {
if err := os.RemoveAll(container.root); err != nil {
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err)
}
if runtime.deviceSet.HasDevice(container.ID) {
if err := runtime.deviceSet.RemoveDevice(container.ID); err != nil {
return fmt.Errorf("Unable to remove device for %v: %v", container.ID, err)
}
}
return nil
}
func (runtime *Runtime) DeleteImage(id string) error {
err := runtime.graph.Delete(id)
if err != nil {
return err
}
if runtime.deviceSet.HasDevice(id) {
if err := runtime.deviceSet.RemoveDevice(id); err != nil {
return fmt.Errorf("Unable to remove device for %v: %v", id, err)
}
}
return nil
}
@@ -230,7 +286,11 @@ func (runtime *Runtime) restore() error {
if err != nil {
return err
}
containers := make(map[string]*Container)
var (
containers []*Container
containersToMigrate []*Container
)
for i, v := range dir {
id := v.Name()
@@ -243,37 +303,38 @@ func (runtime *Runtime) restore() error {
continue
}
utils.Debugf("Loaded container %v", container.ID)
containers[container.ID] = container
containers = append(containers, container)
if container.FilesystemType != DefaultFilesystemType {
containersToMigrate = append(containersToMigrate, container)
}
}
register := func(container *Container) {
// Migrate containers to the default filesystem type
if len(containersToMigrate) > 0 {
if err := migrateToDeviceMapper(runtime, containersToMigrate); err != nil {
return err
}
}
sortContainers(containers, func(i, j *Container) bool {
ic, _ := i.ReadHostConfig()
jc, _ := j.ReadHostConfig()
if ic == nil || ic.Links == nil {
return true
}
if jc == nil || jc.Links == nil {
return false
}
return len(ic.Links) < len(jc.Links)
})
for _, container := range containers {
if err := runtime.Register(container); err != nil {
utils.Debugf("Failed to register container %s: %s", container.ID, err)
continue
}
}
if entities := runtime.containerGraph.List("/", -1); entities != nil {
for _, p := range entities.Paths() {
e := entities[p]
if container, ok := containers[e.ID()]; ok {
register(container)
delete(containers, e.ID())
}
}
}
// Any containers that are left over do not exist in the graph
for _, container := range containers {
// Try to set the default name for a container if it exists prior to links
name := generateRandomName(runtime)
container.Name = name
if _, err := runtime.containerGraph.Set(name, container.ID); err != nil {
utils.Debugf("Setting default id - %s", err)
}
register(container)
}
if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" {
fmt.Printf("\bdone.\n")
}
@@ -281,6 +342,74 @@ func (runtime *Runtime) restore() error {
return nil
}
func migrateToDeviceMapper(runtime *Runtime, containers []*Container) error {
var (
image *Image
contents []os.FileInfo
err error
)
fmt.Printf("Migrating %d containers to new storage backend\n", len(containers))
for _, container := range containers {
if container.State.Running {
fmt.Printf("WARNING - Cannot migrate %s because the container is running. Please stop the container and relaunch the daemon!")
continue
}
fmt.Printf("Migrating %s\n", container.ID)
if contents, err = ioutil.ReadDir(container.rwPath()); err != nil {
if !os.IsNotExist(err) {
fmt.Printf("Error reading rw dir %s\n", err)
}
continue
}
if len(contents) == 0 {
fmt.Printf("Skipping migration of %s because rw layer contains no changes\n")
continue
}
if image, err = runtime.graph.Get(container.Image); err != nil {
fmt.Printf("Failed to fetch image %s\n", err)
continue
}
unmount := func() {
if err = image.Unmount(runtime, container.RootfsPath(), container.ID); err != nil {
fmt.Printf("Failed to unmount image %s\n", err)
}
}
if err = image.Mount(runtime, container.RootfsPath(), container.rwPath(), container.ID); err != nil {
fmt.Printf("Failed to mount image %s\n", err)
continue
}
if err = image.applyLayer(container.rwPath(), container.RootfsPath()); err != nil {
fmt.Printf("Failed to apply layer in storage backend %s\n", err)
unmount()
continue
}
unmount()
if err = os.RemoveAll(container.rwPath()); err != nil {
fmt.Printf("Failed to remove rw layer %s\n", err)
}
container.FilesystemType = DefaultFilesystemType
if err := container.ToDisk(); err != nil {
fmt.Printf("Failed to save filesystem type to disk %s\n", err)
}
fmt.Printf("Successful migration for %s\n", container.ID)
}
fmt.Printf("Migration complete\n")
return nil
}
// FIXME: comment please!
func (runtime *Runtime) UpdateCapabilities(quiet bool) {
if cgroupMemoryMountpoint, err := utils.FindCgroupMountpoint("memory"); err != nil {
@@ -309,33 +438,26 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) {
}
}
// Create creates a new container from the given configuration with a given name.
func (runtime *Runtime) Create(config *Config, name string) (*Container, []string, error) {
// Create creates a new container from the given configuration.
func (runtime *Runtime) Create(config *Config) (*Container, []string, error) {
// Lookup image
img, err := runtime.repositories.LookupImage(config.Image)
if err != nil {
return nil, nil, err
}
checkDeprecatedExpose := func(config *Config) bool {
if config != nil {
if config.PortSpecs != nil {
for _, p := range config.PortSpecs {
if strings.Contains(p, ":") {
return true
}
warnings := []string{}
if img.Config != nil {
if img.Config.PortSpecs != nil && warnings != nil {
for _, p := range img.Config.PortSpecs {
if strings.Contains(p, ":") {
warnings = append(warnings, "This image expects private ports to be mapped to public ports on your host. "+
"This has been deprecated and the public mappings will not be honored."+
"Use -p to publish the ports.")
break
}
}
}
return false
}
warnings := []string{}
if checkDeprecatedExpose(img.Config) || checkDeprecatedExpose(config) {
warnings = append(warnings, "The mapping to public ports on your host has been deprecated. Use -p to publish the ports.")
}
if img.Config != nil {
if err := MergeConfig(config, img.Config); err != nil {
return nil, nil, err
}
@@ -347,26 +469,11 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
return nil, nil, fmt.Errorf("No command specified")
}
sysInitPath := utils.DockerInitPath()
if sysInitPath == "" {
return nil, nil, fmt.Errorf("Could not locate dockerinit: This usually means docker was built incorrectly. See http://docs.docker.io/en/latest/contributing/devenvironment for official build instructions.")
}
// Generate id
id := GenerateID()
if name == "" {
name = generateRandomName(runtime)
}
if name[0] != '/' {
name = "/" + name
}
// Set the enitity in the graph using the default name specified
if _, err := runtime.containerGraph.Set(name, id); err != nil {
if strings.HasSuffix(err.Error(), "name are not unique") {
return nil, nil, fmt.Errorf("Conflict, %s already exists.", name)
}
// Set the default enitity in the graph
if _, err := runtime.containerGraph.Set(fmt.Sprintf("/%s", id), id); err != nil {
return nil, nil, err
}
@@ -397,8 +504,8 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
Image: img.ID, // Always use the resolved image id
NetworkSettings: &NetworkSettings{},
// FIXME: do we need to store this in the container?
SysInitPath: sysInitPath,
Name: name,
SysInitPath: sysInitPath,
FilesystemType: DefaultFilesystemType,
}
container.root = runtime.containerRoot(container.ID)
// Step 1: create the container directory.
@@ -504,15 +611,12 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a
return img, nil
}
func (runtime *Runtime) getFullName(name string) string {
if name[0] != '/' {
name = "/" + name
}
return name
}
func (runtime *Runtime) GetByName(name string) (*Container, error) {
entity := runtime.containerGraph.Get(runtime.getFullName(name))
if id, err := runtime.idIndex.Get(name); err == nil {
name = id
}
entity := runtime.containerGraph.Get(name)
if entity == nil {
return nil, fmt.Errorf("Could not find entity for %s", name)
}
@@ -524,7 +628,6 @@ func (runtime *Runtime) GetByName(name string) (*Container, error) {
}
func (runtime *Runtime) Children(name string) (map[string]*Container, error) {
name = runtime.getFullName(name)
children := make(map[string]*Container)
err := runtime.containerGraph.Walk(name, func(p string, e *gograph.Entity) error {
@@ -542,13 +645,43 @@ func (runtime *Runtime) Children(name string) (map[string]*Container, error) {
return children, nil
}
func (runtime *Runtime) RegisterLink(parent, child *Container, alias string) error {
fullName := path.Join(parent.Name, alias)
if !runtime.containerGraph.Exists(fullName) {
_, err := runtime.containerGraph.Set(fullName, child.ID)
func (runtime *Runtime) RenameLink(oldName, newName string) error {
if id, err := runtime.idIndex.Get(oldName); err == nil {
oldName = id
}
entity := runtime.containerGraph.Get(oldName)
if entity == nil {
return fmt.Errorf("Could not find entity for %s", oldName)
}
// This is not rename but adding a new link for the default name
// Strip the leading '/'
if strings.HasPrefix(entity.ID(), oldName[1:]) {
_, err := runtime.containerGraph.Set(newName, entity.ID())
return err
}
return nil
return runtime.containerGraph.Rename(oldName, newName)
}
func (runtime *Runtime) Link(parentName, childName, alias string) error {
if id, err := runtime.idIndex.Get(parentName); err == nil {
parentName = id
}
parent := runtime.containerGraph.Get(parentName)
if parent == nil {
return fmt.Errorf("Could not get container for %s", parentName)
}
if id, err := runtime.idIndex.Get(childName); err == nil {
childName = id
}
child := runtime.containerGraph.Get(childName)
if child == nil {
return fmt.Errorf("Could not get container for %s", childName)
}
cc := runtime.Get(child.ID())
_, err := runtime.containerGraph.Set(path.Join(parentName, alias), cc.ID)
return err
}
// FIXME: harmonize with NewGraph()
@@ -561,6 +694,7 @@ func NewRuntime(config *DaemonConfig) (*Runtime, error) {
if k, err := utils.GetKernelVersion(); err != nil {
log.Printf("WARNING: %s\n", err)
} else {
runtime.kernelVersion = k
if utils.CompareKernelVersion(k, &utils.KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 {
log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String())
}
@@ -596,24 +730,14 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
return nil, err
}
gographPath := path.Join(config.GraphPath, "linkgraph.db")
initDatabase := false
if _, err := os.Stat(gographPath); err != nil {
if os.IsNotExist(err) {
initDatabase = true
} else {
return nil, err
}
}
conn, err := sql.Open("sqlite3", gographPath)
if err != nil {
return nil, err
}
graph, err := gograph.NewDatabase(conn, initDatabase)
graph, err := gograph.NewDatabase(path.Join(config.GraphPath, "linkgraph.db"))
if err != nil {
return nil, err
}
// Initialize devicemapper deviceSet
deviceSet := devmapper.NewDeviceSet(config.GraphPath)
runtime := &Runtime{
repository: runtimeRepo,
containers: list.New(),
@@ -625,6 +749,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
volumes: volumes,
config: config,
containerGraph: graph,
deviceSet: deviceSet,
}
if err := runtime.restore(); err != nil {
@@ -633,11 +758,6 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
return runtime, nil
}
func (runtime *Runtime) Close() error {
runtime.networkManager.Close()
return runtime.containerGraph.Close()
}
// History is a convenience type for storing a list of containers,
// ordered by creation date.
type History []*Container

Some files were not shown because too many files have changed in this diff Show More